Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Trying to secure Nginx the most for Joomla CMS under Ubuntu 11.10
<!-- to be saved at /usr/share/nginx/html/404.html -->
<html lang="en"><head><meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, minimum-scale=1, width=device-width">
<title>Error 404 (Not Found)!!1</title>
<style>
*{margin:0;padding:0}html,code{font:15px/22px arial,sans-serif}html{background:#fff;color:#222;padding:15px}body{margin:7% auto 0;max-width:390px;min-height:180px;padding:30px 0 15px}* > body{background:url(//www.google.com/images/errors/robot.png) 100% 5px no-repeat;padding-right:205px}p{margin:11px 0 22px;overflow:hidden}ins{color:#777;text-decoration:none}a img{border:0}@media screen and (max-width:772px){body{background:none;margin-top:0;max-width:none;padding-right:0}}
</style>
<link type="text/css" rel="stylesheet" href="chrome-extension://cpngackimfmofbokmjmljamhdncknpmg/style.css"><script type="text/javascript" charset="utf-8" src="chrome-extension://cpngackimfmofbokmjmljamhdncknpmg/page_context.js"></script></head><body screen_capture_injected="true">
<!--
<a href="//www.google.com/">
<img src="//www.google.com/images/errors/logo_sm.gif" alt="Google"></a>
-->
<p><b>404.</b> <ins>That.s an error.</ins>
<h2>The page you were looking for appears to
have been moved, deleted or does not exist.</h2>
</p><p>The requested URL <code>/wfwefwefwef</code> was not found on this server. <ins>That.s all we know.</ins>
</p>
<p>
<a href="http://www.waltercedric.com/contact.html" target="new">Please contact us to report a problem.</a>
</p>
<script type="text/javascript">
var GOOG_FIXURL_LANG = 'en';
var GOOG_FIXURL_SITE = 'http://www.waltercedric.com'
</script>
<script type="text/javascript"
src="http://linkhelp.clients.google.com/tbproxy/lh/wm/fixurl.js">
</script>
<ul>
<li>Try returning to the <a href="http://www.waltercedric.com">home page</a> and finding the page you were looking for.</li>
</ul>
<br />
<h3>Sorry for your inconvenience</h3>
</body></html>
# dedicated server with 8GB of RAM and php5-fpm
# issue a 'service php5-fpm restart' after changes
extension=apc.so
apc.enabled = 1
apc.shm_segments = 1
apc.shm_size = 512M
apc.optimization = 0
apc.num_files_hint = 512
apc.user_entries_hint = 1024
apc.ttl = 0
apc.user_ttl = 0
apc.gc_ttl = 600
apc.cache_by_default = 1
apc.filters = "apc\.php$"
apc.slam_defense = 0
apc.use_request_time = 1
apc.mmap_file_mask = /tmp/apc.XXXXXX
;OR apc.mmap_file_mask = /dev/zero
apc.file_update_protection = 2
apc.enable_cli = 0
apc.max_file_size = 2M
apc.stat = 1
apc.write_lock = 1
apc.report_autofilter = 0
apc.include_once_override = 0
apc.rfc1867 = 0
apc.rfc1867_prefix = "upload_"
apc.rfc1867_name = "APC_UPLOAD_PROGRESS"
apc.rfc1867_freq = 0
apc.localcache = 1
apc.localcache.size = 512
apc.coredump_unmap = 0
apc.stat_ctime = 0
## Reusable settings for PHP CMS, targetted at joomla
#
# latest version at https://gist.github.com/1620307
# author cedric.walter, www.waltercedric.com
# to be saved in /etc/nginx/conf/joomla.conf
server_name_in_redirect off;
location / {
expires 1d;
# Note that nginx does not use .htaccess.
# This will allow for SEF URL’s
try_files $uri $uri/ /index.php?$args;
}
#################################################################
# Optimizations #
#################################################################
#################################################################
# Hardening #
#################################################################
server_tokens off;
# File uploads
client_max_body_size 10M;
client_body_buffer_size 128k;
# deny running scripts inside writable directories
location ~* /(images|cache|media|logs|tmp)/.*\.(php|pl|py|jsp|asp|sh|cgi)$ {
return 403;
error_page 403 /403_error.html;
}
# Deny all attempts to access hidden files such as .htaccess, .htpasswd, .DS_Store (Mac).
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
# Only allow these request methods GET|HEAD|POST
# Do not accept DELETE, SEARCH and other methods
if ($request_method !~ ^(GET|HEAD|POST)$ ) {
return 444;
}
#################################################################
# Error #
#################################################################
error_page 404 /404.html;
location = /404.html {
root /usr/share/nginx/html;
}
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
#################################################################
# Rewrite #
#################################################################
# Add www to all urls
if ($host ~* ^([a-z0-9\-]+\.(be|fr|nl|de))$) {
rewrite ^(.*)$ http://www.$host$1 permanent;
}
proxy_cache_key "$host$request_uri$cookie_sessioncookie";
# Pass all .php files onto a php-fpm/php-fcgi server.
# see https://nealpoole.com/blog/2011/04/setting-up-php-fastcgi-and-nginx-dont-trust-the-tutorials-check-your-configuration/
# see http://cnedelcu.blogspot.com/2010/05/nginx-php-via-fastcgi-important.html
location ~ \.php$ {
# Zero-day exploit defense.
# http://forum.nginx.org/read.php?2,88845,page=3
# Won't work properly (404 error) if the file is not stored on this server, which is entirely possible with php-fpm/php-fcgi.
# Comment the 'try_files' line out if you set up php-fpm/php-fcgi on another machine. And then cross your fingers that you won't get hacked.
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
include fastcgi_params;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# fastcgi_intercept_errors on;
#fastcgi_pass php;
fastcgi_pass 127.0.0.1:9000;
}
# These are my settings, run with the default mysql settings and then run after 24h tuning-primer.sh
# https://launchpad.net/mysql-tuning-primer
# and follow the recommendations!
#
# latest version at https://gist.github.com/1620307
# author cedric.walter, www.waltercedric.com
# to be saved in /etc/mysql/my.cnf
[client]
port = 3306
socket = /var/run/mysqld/mysqld.sock
[mysqld_safe]
socket = /var/run/mysqld/mysqld.sock
nice = 0
# * IMPORTANT
# If you make changes to these settings and your system uses apparmor, you may
# also need to also adjust /etc/apparmor.d/usr.sbin.mysqld.
#
[mysqld]
user = mysql
socket = /var/run/mysqld/mysqld.sock
port = 3306
basedir = /usr
datadir = /var/lib/mysql
tmpdir = /tmp
skip-external-locking
# Instead of skip-networking the default is now to listen only on
# localhost which is more compatible and is not less secure.
#bind-address = 127.0.0.1
key_buffer = 100M
max_allowed_packet = 16M
thread_stack = 192K
thread_cache_size = 8
myisam-recover = BACKUP
#max_connections = 100
table_cache = 1512
table_definition_cache = 1512
tmp_table_size = 64M
read_buffer_size = 32M
#thread_concurrency = 10
query_cache_limit = 1M
query_cache_size = 100M
log_error = /var/log/mysql/error.log
log_slow_queries = /var/log/mysql/mysql-slow.log
long_query_time = 2
#log-queries-not-using-indexes
# The following can be used as easy to replay backup logs or for replication.
# note: if you are setting up a replication slave, see README.Debian about
# other settings you may need to change.
#server-id = 1
#log_bin = /var/log/mysql/mysql-bin.log
expire_logs_days = 10
max_binlog_size = 100M
# * InnoDB
# InnoDB is enabled by default with a 10MB datafile in /var/lib/mysql/.
# Read the manual for more InnoDB related options. There are many!
#
# * Security Features
#
# Read the manual, too, if you want chroot!
# chroot = /var/lib/mysql/
#
# For generating SSL certificates I recommend the OpenSSL GUI "tinyca".
#
# ssl-ca=/etc/mysql/cacert.pem
# ssl-cert=/etc/mysql/server-cert.pem
# ssl-key=/etc/mysql/server-key.pem
[mysqldump]
quick
quote-names
max_allowed_packet = 16M
[mysql]
[isamchk]
key_buffer = 16M
#
# * IMPORTANT: Additional settings that can override those from this file!
# The files must end with '.cnf', otherwise they'll be ignored.
#
!includedir /etc/mysql/conf.d/
## latest version at https://gist.github.com/1620307
#
# author cedric.walter, www.waltercedric.com
# to be saved in /etc/nginx/nginx.conf
user www-data;
# = to the number of CPU - 1
# you may want to leave one core for the system and its interrupts
# and the other cores for the web server
worker_processes 4;
pid /var/run/nginx.pid;
events {
# worker_connections : This is the amount of client connections a
# single child process will handle by themselves at any one time.
# (default: 1024) Note: Multiply worker_processes times worker_connections
# for the total amount of connections Nginx will handle. Our example is
# setup to handle 3*64=192 concurrent connections in total. Clients who
# connect after the max has been reached will be denied access.
worker_connections 1024;
# multi_accept on;
}
http {
##
# Basic Settings
##
sendfile on;
tcp_nopush on;
tcp_nodelay on;
types_hash_max_size 2048;
## Hardening ####
server_tokens off;
### (default is 8k or 16k) The directive specifies the client request body buffer size.
# If the request body is more than the buffer, then the entire request body or some part is written in a temporary file.
client_body_buffer_size 8K;
### Directive sets the headerbuffer size for the request header from client. For the overwhelming
### majority of requests a buffer size of 1K is sufficient. Increase this if you have a custom header
### or a large cookie sent from the client (e.g., wap client).
client_header_buffer_size 1k;
### Directive assigns the maximum accepted body size of client request, indicated by the line Content-Length
### in the header of request. If size is greater the given one, then the client gets the error
### "Request Entity Too Large" (413). Increase this when you are getting file uploads via the POST method.
client_max_body_size 2m;
### Directive assigns the maximum number and size of buffers for large headers to read from client request.
### By default the size of one buffer is equal to the size of page, depending on platform this either 4K or 8K,
### if at the end of working request connection converts to state keep-alive, then these buffers are freed.
### 2x1k will accept 2kB data URI. This will also help combat bad bots and DoS attacks.
large_client_header_buffers 2 1k;
### The first parameter assigns the timeout for keep-alive connections with the client.
### The server will close connections after this time. The optional second parameter assigns
### the time value in the header Keep-Alive: timeout=time of the response. This header can
### convince some browsers to close the connection, so that the server does not have to. Without
### this parameter, nginx does not send a Keep-Alive header (though this is not what makes a connection "keep-alive").
keepalive_timeout 300 300;
### Directive sets the read timeout for the request body from client.
### The timeout is set only if a body is not get in one readstep. If after
### this time the client send nothing, nginx returns error "Request time out"
### (408). The default is 60.
client_body_timeout 10;
### Directive assigns timeout with reading of the title of the request of client.
### The timeout is set only if a header is not get in one readstep. If after this
### time the client send nothing, nginx returns error "Request time out" (408).
client_header_timeout 10;
### Directive assigns response timeout to client. Timeout is established not on entire
### transfer of answer, but only between two operations of reading, if after this time
### client will take nothing, then nginx is shutting down the connection.
send_timeout 10;
### Directive describes the zone, in which the session states are stored i.e. store in slimits. ###
### 1m can handle 32000 sessions with 32 bytes/session, set to 5m x 32000 session ###
limit_zone slimits $binary_remote_addr 5m;
### Control maximum number of simultaneous connections for one session i.e. ###
### restricts the amount of connections from a single ip address ###
limit_conn slimits 5;
### Solve upstream sent too big header while reading response header from upstream
### http://wiki.nginx.org/HttpProxyModule
# This directive set the buffer size, into which will be read the first part of the response, obtained from the proxied server.
# Default: 4k|8k
proxy_buffer_size 128k;
# This directive sets the number and the size of buffers, into which will be read the answer, obtained from the proxied server.
# By default, the size of one buffer is equal to the size of page. Depending on platform this is either 4K or 8K.
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
fastcgi_buffers 8 16k;
fastcgi_buffer_size 32k;
# server_names_hash_bucket_size 64;
# server_name_in_redirect off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
##
# Logging Settings
##
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
##
# Gzip Settings
##
gzip on;
gzip_http_version 1.1;
gzip_vary on;
gzip_comp_level 6;
gzip_proxied any;
gzip_types text/plain text/html text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript text/x-js;
gzip_buffers 16 8k;
gzip_disable "MSIE [1-6]\.(?!.*SV1)";
#################################################################
# Caching #
#################################################################
location ~* \.(ico|pdf|flv)$ {
access_log off;
expires 1y;
}
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
##
# http://code.google.com/speed/page-speed/docs/caching.html#LeverageBrowserCaching
##
location ~* \.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|doc|xls|exe|pdf|ppt|txt|tar|mid|midi|wav|bmp|rtf|js)$ {
access_log off;
expires 1y;
add_header Pragma public;
add_header Cache-Control "public, must-revalidate, proxy-revalidate";
}
##
# Virtual Host Configs
##
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
#mail {
# # See sample authentication script at:
# # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
#
# # auth_http localhost/auth.php;
# # pop3_capabilities "TOP" "USER";
# # imap_capabilities "IMAP4rev1" "UIDPLUS";
#
# server {
# listen localhost:110;
# protocol pop3;
# proxy on;
# }
#
# server {
# listen localhost:143;
# protocol imap;
# proxy on;
# }
#}
## latest version at https://gist.github.com/1620307
#
# author cedric.walter, www.waltercedric.com
# to be saved in /etc/php5/fpm/php5-fpm.conf
# 512MB of ram will offer you a maximum of 30 children and a minimum of 5.
# 2048MB of ram will offer you a maximum of 60 children and a minimum of 20.
# But be warned that these may not apply for the content you are processing with PHP. Image manipulation will require a far larger amount of ram and should be limited to less threads.
pm = dynamic
; The number of child processes to be created when pm is set to 'static' and the
; maximum number of child processes to be created when pm is set to 'dynamic'.
; This value sets the limit on the number of simultaneous requests that will be
; served. Equivalent to the ApacheMaxClients directive with mpm_prefork.
; Equivalent to the PHP_FCGI_CHILDREN environment variable in the original PHP
; CGI.
; Note: Used when pm is set to either 'static' or 'dynamic'
; Note: This value is mandatory.
pm.max_children = 120
; The number of child processes created on startup.
; Note: Used only when pm is set to 'dynamic'
; Default Value: min_spare_servers + (max_spare_servers - min_spare_servers) / 2
pm.start_servers = 30
; The desired minimum number of idle server processes.
; Note: Used only when pm is set to 'dynamic'
; Note: Mandatory when pm is set to 'dynamic'
pm.min_spare_servers = 30
; The desired maximum number of idle server processes.
; Note: Used only when pm is set to 'dynamic'
; Note: Mandatory when pm is set to 'dynamic'
pm.max_spare_servers = 60
; The number of requests each child process should execute before respawning.
; This can be useful to work around memory leaks in 3rd party libraries. For
; endless request processing specify '0'. Equivalent to PHP_FCGI_MAX_REQUESTS.
; Default Value: 0
;pm.max_requests = 500
server {
### The port number (80 for http)
listen 80;
### The domain name
server_name example.com www.example.com;
### The public root folder of the site
root /var/www/vhosts/example.com/httpdocs;
### The default index file.
index index.php;
### Where to write the site access log
access_log /var/www/vhosts/example.com/logs/example.log;
# Stop deep linking or hot linking
# apply this rule on any location that’s an image using Regexp
# block empty blocked or whiteliste referers
# test proper operation using http://altlab.com/hotlinkchecker.php
location ~* \.(png|gif|jpg|jpeg|swf|ico)(\?[0-9]+)?$ {
valid_referers none blocked example.com www.example.com ~\.google\. ~\.yahoo\. ~\.bing\. ~\.facebook\. ~\.fbcdn\.;
if ($invalid_referer) {
# return 403;
rewrite ^/images/(.*)\.(gif|jpg|jpeg|png)$ http://www.example.com/stop.jpg last;
}
}
### Password Protect /administrator area
location ~ /administrator/.*) {
auth_basic "Restricted";
auth_basic_user_file /var/www/vhosts/example.com/httpdocs/passwd;
}
include /etc/nginx/conf/joomla.conf;
}
@ddanila

This comment has been minimized.

Copy link

ddanila commented Jun 16, 2014

Well, everything is good except one issue: accessing /administrator URL I always got index.php downloaded instead of executed.
At the same time, accessing root of the site (I mean frontend) works fine.
Also "location ~ /administrator/.*)" looks like a bug -- I mean this lonely closing bracket.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.