This site is an archive and is no longer updated.
    Back to Guides
    Web Servers

    nginx SSL Certificate Configuration

    The complete guide to configuring SSL/TLS in nginx - from basic setup through production hardening.

    15-20 min read
    LinkedInX
    nginx SSL Certificate Configuration Guide

    1. Quick Start (The 5-Minute Setup)

    For the impatient - get HTTPS working, then refine.

    server {
        listen 443 ssl;
        server_name example.com;
    
        ssl_certificate     /etc/nginx/ssl/example.com.crt;
        ssl_certificate_key /etc/nginx/ssl/example.com.key;
    
        # Your existing configuration
        root /var/www/html;
    }

    Pre-Flight Checklist:

    • Certificate file (.crt or .pem) - includes full chain
    • Private key file (.key)
    • Port 443 open in firewall
    • DNS pointing to server

    Test it:

    sudo nginx -t && sudo systemctl reload nginx
    curl -I https://example.com

    2. Understanding nginx SSL Directives

    Core Directives

    DirectivePurposeExample
    ssl_certificatePath to certificate (+ chain)/etc/nginx/ssl/cert.pem
    ssl_certificate_keyPath to private key/etc/nginx/ssl/key.pem
    ssl_protocolsAllowed TLS versionsTLSv1.2 TLSv1.3
    ssl_ciphersAllowed cipher suitesECDHE-ECDSA-AES128-GCM-SHA256:...
    ssl_prefer_server_ciphersServer chooses cipheron
    ssl_session_cacheSession resumption cacheshared:SSL:10m
    ssl_session_timeoutSession cache lifetime1d

    Certificate Chain Order

    Critical: nginx expects the certificate chain in a specific order

    -----BEGIN CERTIFICATE-----
    [Your server certificate]
    -----END CERTIFICATE-----
    -----BEGIN CERTIFICATE-----
    [Intermediate certificate]
    -----END CERTIFICATE-----
    -----BEGIN CERTIFICATE-----
    [Another intermediate if needed]
    -----END CERTIFICATE-----

    Do NOT include the root certificate - clients have it in their trust store.

    Combine files:

    cat server.crt intermediate.crt > /etc/nginx/ssl/example.com.crt

    3. Production-Ready Configuration

    Recommended settings for 2026 - achieves an A+ on SSL Labs.

    server {
        listen 443 ssl http2;
        listen [::]:443 ssl http2;
        server_name example.com www.example.com;
    
        # Certificate and key
        ssl_certificate     /etc/nginx/ssl/example.com.crt;
        ssl_certificate_key /etc/nginx/ssl/example.com.key;
    
        # Protocols - TLS 1.2 and 1.3 only
        ssl_protocols TLSv1.2 TLSv1.3;
    
        # Ciphers - Modern compatibility
        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;
    
        # OCSP Stapling
        ssl_stapling on;
        ssl_stapling_verify on;
        ssl_trusted_certificate /etc/nginx/ssl/chain.pem;
        resolver 8.8.8.8 8.8.4.4 valid=300s;
        resolver_timeout 5s;
    
        # Session settings
        ssl_session_cache shared:SSL:10m;
        ssl_session_timeout 1d;
        ssl_session_tickets off;
    
        # DH parameters (if using DHE ciphers)
        ssl_dhparam /etc/nginx/ssl/dhparam.pem;
    
        # Security headers
        add_header Strict-Transport-Security "max-age=63072000" always;
    
        # Your application config
        location / {
            # ...
        }
    }

    Generate DH Parameters

    openssl dhparam -out /etc/nginx/ssl/dhparam.pem 2048

    Note: 2048-bit is sufficient; 4096-bit adds latency with minimal security benefit.

    4. HTTP to HTTPS Redirect

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

    Why return 301 instead of rewrite?

    • • Faster (no regex processing)
    • • Clearer intent
    • • Proper HTTP redirect status

    5. Multiple Domains / SNI

    Multiple Certificates (Separate Blocks)

    server {
        listen 443 ssl;
        server_name site1.com;
        ssl_certificate     /etc/nginx/ssl/site1.com.crt;
        ssl_certificate_key /etc/nginx/ssl/site1.com.key;
        # ...
    }
    
    server {
        listen 443 ssl;
        server_name site2.com;
        ssl_certificate     /etc/nginx/ssl/site2.com.crt;
        ssl_certificate_key /etc/nginx/ssl/site2.com.key;
        # ...
    }

    nginx automatically uses SNI to serve the correct certificate.

    Wildcard Certificate

    server {
        listen 443 ssl;
        server_name *.example.com;
        ssl_certificate     /etc/nginx/ssl/wildcard.example.com.crt;
        ssl_certificate_key /etc/nginx/ssl/wildcard.example.com.key;
        # ...
    }

    Wildcard Warning: See Dangers of Wildcard Certificates for security considerations.

    6. Let's Encrypt with Certbot

    Initial Setup

    # Install certbot
    sudo apt install certbot python3-certbot-nginx
    
    # Get certificate and auto-configure nginx
    sudo certbot --nginx -d example.com -d www.example.com

    Manual Certificate Only (No Auto-Config)

    sudo certbot certonly --nginx -d example.com -d www.example.com

    Certificates saved to:

    • /etc/letsencrypt/live/example.com/fullchain.pem
    • /etc/letsencrypt/live/example.com/privkey.pem

    nginx Configuration for Let's Encrypt

    server {
        listen 443 ssl;
        server_name example.com;
    
        ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
        
        # Include recommended settings
        include /etc/letsencrypt/options-ssl-nginx.conf;
        ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
    }

    Auto-Renewal

    # Test renewal
    sudo certbot renew --dry-run
    
    # Cron job (usually auto-installed)
    0 12 * * * /usr/bin/certbot renew --quiet

    7. Reverse Proxy SSL Configuration

    Terminate SSL at nginx (Most Common)

    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;
    
        location / {
            proxy_pass http://backend:8080;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }

    SSL to Backend (End-to-End Encryption)

    location / {
        proxy_pass https://backend:8443;
        proxy_ssl_verify on;
        proxy_ssl_trusted_certificate /etc/nginx/ssl/backend-ca.crt;
        proxy_ssl_server_name on;
    }
    DirectivePurpose
    proxy_ssl_verifyVerify backend certificate
    proxy_ssl_trusted_certificateCA to verify backend
    proxy_ssl_server_nameSend SNI to backend

    8. Common Errors and Fixes

    Error: "ssl_certificate" directive missing

    Problem: Using ssl in listen directive without certificate config.

    Fix: Add certificate directives:

    ssl_certificate     /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    Error: "cannot load certificate key"

    Problem: Key file issues (wrong file, permissions, encrypted).

    Check permissions:

    ls -la /etc/nginx/ssl/
    # Should be readable by nginx (usually root:root 600 or 640)

    Check if key is encrypted:

    head -1 /etc/nginx/ssl/key.pem
    # "-----BEGIN ENCRYPTED PRIVATE KEY-----" = encrypted
    # "-----BEGIN PRIVATE KEY-----" = unencrypted (what you want)

    Decrypt if needed:

    openssl rsa -in encrypted.key -out decrypted.key

    Error: "key values mismatch"

    Problem: Certificate and private key don't match.

    Verify match:

    # These should output the same hash
    openssl x509 -noout -modulus -in cert.pem | openssl md5
    openssl rsa -noout -modulus -in key.pem | openssl md5

    Fix: Get the correct key that matches your certificate.

    Error: SSL Handshake Failed / ERR_SSL_PROTOCOL_ERROR

    Possible causes: TLS version mismatch, cipher incompatibility, incomplete chain.

    Debug:

    openssl s_client -connect example.com:443 -servername example.com

    Error: Certificate chain incomplete

    Symptoms: Works in some browsers, not others. Mobile often fails first.

    Diagnose:

    openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | grep -A2 "Certificate chain"

    Fix - concatenate intermediate certificates:

    cat server.crt intermediate.crt > fullchain.crt

    9. Testing Your Configuration

    SSL Labs Test

    Test your configuration at: SSL Labs

    Target: A or A+ grade

    Local Testing with OpenSSL

    # Basic connection test
    openssl s_client -connect example.com:443 -servername example.com
    
    # Check certificate dates
    echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates
    
    # Check certificate chain
    openssl s_client -connect example.com:443 -servername example.com -showcerts

    nginx Configuration Test

    sudo nginx -t

    Always run this before reloading!

    10. Security Checklist

    • TLS 1.2+ only (no SSLv3, TLS 1.0, TLS 1.1)
    • Strong cipher suites (ECDHE, no RC4, no 3DES)
    • Certificate chain complete
    • HSTS header enabled
    • HTTP redirects to HTTPS
    • OCSP stapling enabled
    • Private key permissions restricted (600 or 640)
    • DH parameters generated (if using DHE)
    • SSL Labs grade A or higher

    Frequently Asked Questions

    Related Resources