Skip to content

Instantly share code, notes, and snippets.

@cedricwalter
Created January 16, 2012 11:08
Show Gist options
  • Save cedricwalter/1620307 to your computer and use it in GitHub Desktop.
Save cedricwalter/1620307 to your computer and use it in GitHub Desktop.
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
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