nginx Config

This article wasn't updated in the last 4 years. Please double check if the content is still up-to-date.

If you find any error, please send me a quick heads-up.

This is my commonly used config for nginx. It may be out of date, so just use it as a more general guide.

First set up DH params

mkdir -p /etc/nginx/ssl
cd /etc/nginx/ssl
openssl dhparam -out dhparam.pem 2048

Main Config (nginx.conf)

user www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
	worker_connections 768;
	# multi_accept on;
}

http {

	##
	# Basic Settings
	##

	sendfile on;
	tcp_nopush on;
	tcp_nodelay on;
	keepalive_timeout 65;
	types_hash_max_size 2048;
	server_tokens off;

	server_names_hash_bucket_size 64;
	server_names_hash_max_size 512;
	# server_name_in_redirect off;

	client_max_body_size 128M;

	include /etc/nginx/mime.types;
	default_type application/octet-stream;

	##
	# SSL Settings
	##

	ssl_protocols TLSv1.3 TLSv1.2;
	ssl_prefer_server_ciphers on;
	ssl_ciphers "EECDH+AESGCM:EDH+AESGCM";
	ssl_ecdh_curve secp384r1;
	ssl_dhparam /etc/nginx/ssl/dhparam.pem;
	ssl_session_cache shared:SSL:50m;
	ssl_session_timeout 5m;
	ssl_session_tickets off;
	ssl_stapling on;
	ssl_stapling_verify on;
	# every HTTPS host needs to configure its `ssl_trusted_certificate`

	##
	# Logging Settings
	##

	error_log /var/log/nginx/error.log;
	access_log off;

	##
	# Gzip Settings
	##

	gzip on;
	gzip_comp_level 2;
	gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;

	##
	# Application Security
	##

	include snippets/security.conf;

	##
	# Virtual Host Configs
	##

	# default config
	server {
		listen 80 default_server;
		listen [::]:80 default_server;
		server_name _;
		log_not_found off;

		# either show splash screen
		# index index.html;
		# root /var/www/__default;

		# or redirect to server main URL
		# return 303 https://main-domain.com;
	}

	include /etc/nginx/conf.d/*.conf;
	include /etc/nginx/servers/*;
}

PHP-FPM Snippet (snippets/php-fpm.conf)

fastcgi_buffer_size             128k;
fastcgi_buffers                 4 256k;
fastcgi_busy_buffers_size       256k;
fastcgi_temp_file_write_size    256k;
fastcgi_intercept_errors        on;

# Symfony-specific config
fastcgi_param APP_ENV "prod";

# prevent httpoxy vulnerability
fastcgi_param HTTP_PROXY "";

include snippets/fastcgi-php.conf;

Security-Snippet (snippets/security.conf)

Warning: you need to include this file everywhere, where you want to load additional headers via add_header. See for example the /assets/ location in the file below.

add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

Project Config (servers/example.org.conf)

# redirect HTTP to HTTPS
server {
    listen 80;
    listen [::]:80;
    server_name example.org www.example.org;

    return 301 https://www.example.org$request_uri;
}

# redirect non-WWW to WWW
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    server_name example.org;

    ssl_certificate         /etc/letsencrypt/live/example.org/fullchain.pem;
    ssl_certificate_key     /etc/letsencrypt/live/example.org/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/example.org/chain.pem;

    return 301 https://www.example.org$request_uri;
}

# actual server block
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    server_name www.example.org;
    root /var/www/example.org/public;

    ssl_certificate         /etc/letsencrypt/live/example.org/fullchain.pem;
    ssl_certificate_key     /etc/letsencrypt/live/example.org/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/example.org/chain.pem;

    error_log /var/log/nginx/example.org-error.log;

    location / {
        index index.php;
        try_files $uri /index.php$is_args$args;
    }

    location ~ ^/index.php {
        include snippets/php-fpm.conf;
        fastcgi_pass 127.0.0.1:9100;
        internal;
    }

    location ~ ^/assets/ {
        include snippets/security.conf;
        add_header Cache-Control "public, max-age=31536000, immutable";
        add_header Vary "Accept-Encoding";
        log_not_found off;
    }

    location ~ \.php$ {
        return 404;
    }

    location ~ /\. {
        deny all;
        log_not_found off;
    }
}

Additional Config / Comments

Additional Headers

You can set additional headers

# This header enables the Cross-site scripting (XSS) filter built into most recent web browsers.
# It's usually enabled by default anyway, so the role of this header is to re-enable the filter for
# this particular website if it was disabled by the user.
# https://www.owasp.org/index.php/List_of_useful_HTTP_headers
add_header X-XSS-Protection "1; mode=block";

# with Content Security Policy (CSP) enabled(and a browser that supports it(http://caniuse.com/#feat=contentsecuritypolicy),
# you can tell the browser that it can only download content from the domains you explicitly allow
# http://www.html5rocks.com/en/tutorials/security/content-security-policy/
# https://www.owasp.org/index.php/Content_Security_Policy
# I need to change our application code so we can increase security by disabling 'unsafe-inline' 'unsafe-eval'
# directives for css and js(if you have inline css or js, you will need to keep it too).
# more: http://www.html5rocks.com/en/tutorials/security/content-security-policy/#inline-code-considered-harmful
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://ssl.google-analytics.com https://assets.zendesk.com https://connect.facebook.net; img-src 'self' https://ssl.google-analytics.com https://s-static.ak.facebook.com https://assets.zendesk.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://assets.zendesk.com; font-src 'self' https://themes.googleusercontent.com; frame-src https://assets.zendesk.com https://www.facebook.com https://s-static.ak.facebook.com https://tautt.zendesk.com; object-src 'none'";

# config to enable HSTS(HTTP Strict Transport Security) https://developer.mozilla.org/en-US/docs/Security/HTTP_Strict_Transport_Security
# to avoid ssl stripping https://en.wikipedia.org/wiki/SSL_stripping#SSL_stripping
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";

Cipherlist

Use the cipherlist from https://cipherlist.eu/

OCSP stapling with DigiCert

OCSP stapling with the root certificate from DigiCert: https://www.digitalocean.com/community/tutorials/how-to-configure-ocsp-stapling-on-apache-and-nginx

wget -O - https://www.digicert.com/CACerts/DigiCertHighAssuranceEVRootCA.crt | openssl x509 -inform DER -outform PEM | tee -a ca-certs.pem> /dev/null
wget -O - https://www.digicert.com/CACerts/DigiCertHighAssuranceEVCA-1.crt | openssl x509 -inform DER -outform PEM | tee -a ca-certs.pem> /dev/null