SSL/TLS
03 / 03

Configuration: Nginx, HSTS, mTLS & Pinning

SSL/TLS: Configuration & Hardening

Nginx TLS Configuration

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;                          # enable HTTP/2

    server_name example.com www.example.com;

    # Certificate chain (leaf + intermediates)
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # Modern TLS configuration (Mozilla Intermediate)
    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;     # TLS 1.3 ignores this; off = client chooses

    # Session resumption
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;           # disable for forward secrecy

    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
    resolver 1.1.1.1 8.8.8.8 valid=300s;

    # DH params (for DHE cipher suites)
    ssl_dhparam /etc/ssl/dhparam.pem;  # openssl dhparam -out dhparam.pem 2048

    # Security headers
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    add_header X-Frame-Options DENY always;
    add_header X-Content-Type-Options nosniff always;
    add_header Referrer-Policy strict-origin-when-cross-origin always;
    add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
}

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

HSTS & Preloading

Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

max-age=63072000       — browser remembers HTTPS-only for 2 years
includeSubDomains      — applies to all subdomains
preload                — eligible for browser preload list (hardcoded HTTPS, no HTTP ever)

Preload list:
  - Submit at https://hstspreload.org
  - Removes even the first-visit HTTP downgrade risk
  - Irreversible in the short term — ensure ALL subdomains support HTTPS first
  - Currently in Chrome, Firefox, Safari, Edge preload lists

Rollout strategy:
  1. Start: max-age=300 (5 min) — test, ensure no HTTP subdomains
  2. Increase: max-age=86400 (1 day)
  3. Add includeSubDomains: verify all subs have valid certs
  4. Production: max-age=63072000; includeSubDomains
  5. Submit for preload: add preload directive

Mutual TLS (mTLS)

mTLS requires both server and client to present certificates — not just the server. Used for service-to-service authentication in microservices (zero-trust networks), API authentication for partners.

# Server requires client certificate
server {
    ssl_client_certificate /etc/ssl/ca-chain.pem;  # CA that signed client certs
    ssl_verify_client on;
    ssl_verify_depth 2;

    location /api/ {
        # Client cert details in headers for upstream
        proxy_set_header X-Client-CN $ssl_client_s_dn_cn;
        proxy_set_header X-Client-Verify $ssl_client_verify;
        proxy_pass http://backend;
    }
}
# Generate client certificate (dev/testing)
# 1. Create CA
openssl genrsa -out ca.key 4096
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt -subj "/CN=My CA"

# 2. Create client key + CSR
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr -subj "/CN=service-a"

# 3. Sign with CA
openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key   -CAcreateserial -out client.crt

# 4. Test
curl --cert client.crt --key client.key https://api.example.com/

Testing & Debugging

# Check certificate details
openssl s_client -connect example.com:443 -servername example.com </dev/null
openssl s_client -connect example.com:443 </dev/null 2>/dev/null | openssl x509 -noout -text

# Check expiry
openssl s_client -connect example.com:443 </dev/null 2>/dev/null   | openssl x509 -noout -dates

# Test specific TLS version
openssl s_client -connect example.com:443 -tls1_2
openssl s_client -connect example.com:443 -tls1_3

# Online tools
# https://www.ssllabs.com/ssltest/         — full TLS config audit (A+ rating guide)
# https://securityheaders.com              — security headers check
# https://whatsmychaincert.com             — certificate chain validator

# Decode a certificate
openssl x509 -in cert.pem -noout -text
openssl x509 -in cert.pem -noout -subject -issuer -dates -fingerprint

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free