Nginx SSL/TLS Configuration

Best practices for configuring SSL/TLS in nginx with modern security standards, cipher suites, and performance optimizations.

Basic HTTPS Configuration

Minimal HTTPS server block

Basic SSL/TLS configuration:

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name example.com;

    ssl_certificate /etc/nginx/ssl/example.com.crt;
    ssl_certificate_key /etc/nginx/ssl/example.com.key;

    root /var/www/html;
    index index.html index.htm;

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

HTTP to HTTPS redirect

Force all traffic to HTTPS:

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

    return 301 https://$server_name$request_uri;
}

Certificate chain

Include intermediate certificates:

# Concatenate certificate + intermediate chain
ssl_certificate /etc/nginx/ssl/example.com-fullchain.crt;
ssl_certificate_key /etc/nginx/ssl/example.com.key;

# Or build manually:
# cat example.com.crt intermediate.crt > fullchain.crt

Modern Security Configuration

TLS protocols

Enable only TLS 1.2 and 1.3:

# Modern configuration (TLS 1.2 and 1.3 only)
ssl_protocols TLSv1.2 TLSv1.3;

# If you must support older clients:
# ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;

Modern cipher suites

Recommended cipher configuration for 2024+:

# Mozilla Modern profile (TLS 1.3 only)
ssl_protocols TLSv1.3;
ssl_prefer_server_ciphers off;

# Mozilla Intermediate profile (TLS 1.2 + 1.3)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers off;

Diffie-Hellman parameters

Generate and use strong DH parameters:

# Generate DH params (one-time setup):
# openssl dhparam -out /etc/nginx/dhparam.pem 4096

ssl_dhparam /etc/nginx/dhparam.pem;

HSTS and Security Headers

HTTP Strict Transport Security

Force HTTPS for all future requests:

# HSTS with 2-year max-age, includeSubDomains, and preload
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

Complete security headers

Comprehensive security header configuration:

# HSTS
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

# Prevent MIME type sniffing
add_header X-Content-Type-Options "nosniff" always;

# XSS Protection (legacy browsers)
add_header X-XSS-Protection "1; mode=block" always;

# Clickjacking protection
add_header X-Frame-Options "SAMEORIGIN" always;

# Referrer policy
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

# Content Security Policy (customize for your site)
add_header Content-Security-Policy "default-src 'self' https:; script-src 'self'; style-src 'self' 'unsafe-inline'" always;

# Permissions Policy (formerly Feature-Policy)
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;

OCSP Stapling

Enable OCSP stapling

Improve performance and privacy:

ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/nginx/ssl/ca-chain.crt;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;

Test OCSP stapling

Verify OCSP stapling is working:

# Using OpenSSL
echo QUIT | openssl s_client -connect example.com:443 -status 2>&1 | grep -A 17 'OCSP response:'

# Should show: OCSP Response Status: successful (0x0)

SSL Session Management

Session cache optimization

Improve performance with session caching:

# Session cache for faster reconnections
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
ssl_session_tickets off;  # Disable for forward secrecy

Session tickets (alternative)

If session tickets are needed, rotate keys:

# Generate session ticket keys:
# openssl rand 80 > /etc/nginx/ssl/ticket.key

ssl_session_tickets on;
ssl_session_ticket_key /etc/nginx/ssl/ticket.key;

# Rotate keys periodically (e.g., daily) for forward secrecy

Client Certificate Authentication (mTLS)

Require client certificates

Enable mutual TLS authentication:

ssl_client_certificate /etc/nginx/ssl/client-ca.crt;
ssl_verify_client on;
ssl_verify_depth 2;

Optional client certificates

Make client certs optional, check in application:

ssl_client_certificate /etc/nginx/ssl/client-ca.crt;
ssl_verify_client optional;

# Pass client cert info to application
location / {
    proxy_set_header X-Client-Cert $ssl_client_cert;
    proxy_set_header X-Client-Verify $ssl_client_verify;
    proxy_pass http://backend;
}

CRL checking

Validate client certificates against CRL:

ssl_client_certificate /etc/nginx/ssl/client-ca.crt;
ssl_crl /etc/nginx/ssl/ca-crl.pem;
ssl_verify_client on;

SNI and Multiple Certificates

Multiple server blocks

Different certificates for different domains:

server {
    listen 443 ssl;
    server_name example.com www.example.com;

    ssl_certificate /etc/nginx/ssl/example.com.crt;
    ssl_certificate_key /etc/nginx/ssl/example.com.key;

    # ... site config
}

server {
    listen 443 ssl;
    server_name api.example.com;

    ssl_certificate /etc/nginx/ssl/api.example.com.crt;
    ssl_certificate_key /etc/nginx/ssl/api.example.com.key;

    # ... API config
}

Wildcard certificate

Single certificate for all subdomains:

# Wildcard cert covers *.example.com
server {
    listen 443 ssl;
    server_name ~^(?<subdomain>.+)\.example\.com$;

    ssl_certificate /etc/nginx/ssl/wildcard.example.com.crt;
    ssl_certificate_key /etc/nginx/ssl/wildcard.example.com.key;

    # Route based on subdomain
    root /var/www/$subdomain;
}

Complete Production Configuration

Hardened nginx SSL config

Complete example with all best practices:

# HTTP redirect to HTTPS
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;
    return 301 https://example.com$request_uri;
}

# HTTPS server
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com www.example.com;

    # SSL Certificate
    ssl_certificate /etc/nginx/ssl/example.com-fullchain.crt;
    ssl_certificate_key /etc/nginx/ssl/example.com.key;

    # SSL Protocols and Ciphers
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305';
    ssl_prefer_server_ciphers off;

    # DH Parameters
    ssl_dhparam /etc/nginx/dhparam.pem;

    # SSL Session
    ssl_session_cache shared:SSL:50m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/nginx/ssl/ca-chain.crt;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;

    # Security Headers
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    # Logging
    access_log /var/log/nginx/example.com.access.log;
    error_log /var/log/nginx/example.com.error.log;

    # Document Root
    root /var/www/example.com;
    index index.html index.htm;

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

Performance Optimization

HTTP/2 and HTTP/3

Enable modern protocols for better performance:

# HTTP/2 (nginx 1.9.5+)
listen 443 ssl http2;

# HTTP/3 (nginx 1.25.0+, requires --with-http_v3_module)
listen 443 quic reuseport;
listen 443 ssl;
http3 on;
add_header Alt-Svc 'h3=":443"; ma=86400' always;

SSL buffer tuning

Optimize buffer sizes for performance:

# Optimize SSL buffer size (default 16k)
ssl_buffer_size 4k;  # Reduce latency for small responses

# For high-bandwidth transfers, increase buffer
# ssl_buffer_size 16k;

Testing and Validation

Test configuration

Validate nginx config before reload:

# Test configuration syntax
nginx -t

# Reload if successful
nginx -s reload

# Or restart
systemctl restart nginx

SSL Labs test

Comprehensive SSL configuration analysis:

# Visit: https://www.ssllabs.com/ssltest/
# Enter your domain for comprehensive analysis
# Target: A+ rating with modern configuration

Check TLS version

Verify enabled TLS protocols:

# Test TLS 1.2
openssl s_client -connect example.com:443 -tls1_2 < /dev/null

# Test TLS 1.3
openssl s_client -connect example.com:443 -tls1_3 < /dev/null

# Verify TLS 1.0/1.1 are disabled (should fail)
openssl s_client -connect example.com:443 -tls1 < /dev/null

Important Notes

Configuration Location:

SSL directives can be in http, server, or location contexts. Put shared settings in http block, override in server blocks as needed.

Testing Before Production:

Always test configuration changes with `nginx -t` before reloading. Invalid config can take down your entire site.

Certificate Updates:

When updating certificates (renewal), just reload nginx (`nginx -s reload`) - no need for full restart.

HSTS Preload:

Adding "preload" to HSTS header allows submission to browsers' HSTS preload list. This is permanent - carefully consider before enabling.

Session Tickets vs Cache:

Session cache provides better forward secrecy. Disable session tickets unless you have specific needs and rotate ticket keys frequently.

Cipher Suite Updates:

Cipher recommendations change over time. Check Mozilla SSL Configuration Generator regularly for updates.

Certificate Permissions:

Private keys should be readable only by nginx user (e.g., chmod 600, chown www-data). Never world-readable.

Documentation:

Official nginx SSL module docs: nginx.org/en/docs/http/ngx_http_ssl_module.html

Mozilla SSL Config Generator: ssl-config.mozilla.org

See Also