Configuring and hardening Nginx

SSL

Let’s Encrypt

apt install certbot python-certbot-nginx
certbot certonly --nginx -d example.com --rsa-key-size 4096

Self signed

Generate a self-signed certificate for 5 years:

openssl req -x509 -nodes -newkey rsa:4096 -keyout /etc/nginx/key.pem -out /etc/nginx/cert.pem -days 1825

Diffie-Hellman parameters

openssl dhparam -out /etc/nginx/dhparam.pem 4096

Basic authentication

apt install apache2-utils
htpasswd -c /etc/nginx/.htpasswd user

Ciphers

List ciphers:

openssl ciphers -ciphersuites `openssl ciphers -s -tls1_3`

Total

TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES256-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES128-SHA:RSA-PSK-AES256-GCM-SHA384:DHE-PSK-AES256-GCM-SHA384:RSA-PSK-CHACHA20-POLY1305:DHE-PSK-CHACHA20-POLY1305:ECDHE-PSK-CHACHA20-POLY1305:AES256-GCM-SHA384:PSK-AES256-GCM-SHA384:PSK-CHACHA20-POLY1305:RSA-PSK-AES128-GCM-SHA256:DHE-PSK-AES128-GCM-SHA256:AES128-GCM-SHA256:PSK-AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:ECDHE-PSK-AES256-CBC-SHA384:ECDHE-PSK-AES256-CBC-SHA:SRP-RSA-AES-256-CBC-SHA:SRP-AES-256-CBC-SHA:RSA-PSK-AES256-CBC-SHA384:DHE-PSK-AES256-CBC-SHA384:RSA-PSK-AES256-CBC-SHA:DHE-PSK-AES256-CBC-SHA:AES256-SHA:PSK-AES256-CBC-SHA384:PSK-AES256-CBC-SHA:ECDHE-PSK-AES128-CBC-SHA256:ECDHE-PSK-AES128-CBC-SHA:SRP-RSA-AES-128-CBC-SHA:SRP-AES-128-CBC-SHA:RSA-PSK-AES128-CBC-SHA256:DHE-PSK-AES128-CBC-SHA256:RSA-PSK-AES128-CBC-SHA:DHE-PSK-AES128-CBC-SHA:AES128-SHA:PSK-AES128-CBC-SHA256:PSK-AES128-CBC-SHA

Strongest ciphers

  • TLS_AES_256_GCM_SHA384
  • TLS_CHACHA20_POLY1305_SHA256
  • ECDHE-RSA-AES256-GCM-SHA384
  • ECDHE-RSA-CHACHA20-POLY1305
  • DHE-RSA-AES256-GCM-SHA384
  • DHE-RSA-CHACHA20-POLY1305

Priority:

  1. AES over ChaCha
  2. Keysize >= 256

A sample Nginx config

nginx.conf

user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
        worker_connections 1024;
}

http {
        # Basic Settings
        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        keepalive_timeout 65;
        types_hash_max_size 2048;

        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 off;	

		# Virtual Host Configs
		include /etc/nginx/conf.d/*.conf;
		include /etc/nginx/sites-enabled/*;
}

sites-enabled/example.com

# https
server {

        # Enable SSL and HTTP2
        listen [::]:443 ssl http2;
        listen 443 ssl http2;

        # Set certificate path
        ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
        ssl_trusted_certificate /etc/letsencrypt/live/example.com/fullchain.pem;


        # Set root path
        root /var/www/html;
        index index.php index.html index.htm;
        server_name example.com;

        location / {
                try_files $uri $uri/ =404;
        }

        # Set basic authentication to /admin
        location /admin {
                # Basic authentication
                auth_basic "Message";
                auth_basic_user_file /etc/nginx/.htpasswd;
        }

        # Set FastCGI
        location ~ \.php$ {
                include snippets/fastcgi-php.conf;
                include fastcgi_params;
                fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
                fastcgi_pass unix:/var/run/php/php-fpm.sock;
        }

        # Reverse proxy
        location /proxy {
                proxy_pass http://127.0.0.1:8080;
        }

        # Disable accessing hidden files except .well-known
        location ~ /\.(?!well-known).* {
                deny all;
        }

        # Disable unused methods
        if ($request_method !~ ^(GET|HEAD|POST)$ ) {
                return 405;
        }

		# Custom error pages
		error_page 404 /404.html;
		error_page 500 /500.html;
}

# http
server {

        listen 80;
        listen [::]:80;
        server_name  example.com;

        # Redirect http to https
        return 301 https://$host$request_uri;
}

sites-enabled/default

server {

	listen 80 default_server;
        listen [::]:80 default_server;

        server_name _;

        # Return an empty reply to every other request
        return 444;
}

conf.d/security.conf

# Do not send server version
server_tokens off;

# Set TLS ciphers
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers on;

# Diffie-Hellman parameters
ssl_dhparam /etc/nginx/dhparam.pem;
ssl_ecdh_curve secp521r1:secp384r1;

# SSL session reuse
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;

# Enable OCSP
ssl_stapling on;
ssl_stapling_verify on;
resolver 95.217.213.52 [2a01:4f9:c010:95bb::1];
resolver_timeout 5s;

# Reduce Time To First Byte
ssl_buffer_size 4k;

# Add security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy 'strict-origin' always;
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains" always;

/etc/ssl/openssl.cnf

TLv1.3 has a different configuration interface, which is not implemented until Nginx 1.14.2.

A workaround to configure TLSv1.3 ciphers:

Ciphersuites = TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
Options = ServerPreference

Modules

Disable all modules:

mv /etc/nginx/modules-enabled/*.conf /etc/nginx/modules-available/

To enable a module just create a link to it.

SSL check: https://www.ssllabs.com/ssltest/index.html

Headers: https://wiki.owasp.org/index.php/OWASP_Secure_Headers_Project

Header check: https://securityheaders.com/

Mozilla Observatory: https://observatory.mozilla.org/

Check Nginx config: https://github.com/yandex/gixy

SSL Configuration generator: https://ssl-config.mozilla.org/