Skip to content

Last updated: 2026-06-25

Nginx TLS/SSL Configuration Guide

This guide provides recommended TLS/SSL settings for the Nginx web server. These settings are designed to achieve an A+ rating on Qualys SSL Labs while maintaining compatibility with modern clients.

Prerequisites

  • Nginx 1.19.4 or later (for ssl_conf_command)
  • Nginx 1.23.2 or later (for automatic session ticket key rotation)
  • OpenSSL 1.1.1 or later
  • A valid SSL/TLS certificate from a trusted CA

Protocol Versions

Disable all legacy protocols and allow only TLS 1.2 and TLS 1.3. Older protocols (SSL 2.0, SSL 3.0, TLS 1.0, TLS 1.1) have known vulnerabilities and are deprecated by RFC 8996.

ssl_protocols TLSv1.2 TLSv1.3;

Cipher Suites

Use only AEAD cipher suites with ECDHE key exchange. This ensures forward secrecy and protection against known attacks. All ciphers below use authenticated encryption (GCM or POLY1305), and none rely on CBC mode or static RSA key exchange.

ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;

When TLS 1.3 is enabled, set ssl_prefer_server_ciphers to off. TLS 1.3 handles cipher negotiation differently, and all ciphers in the list above are equally strong, so client preference is appropriate:

ssl_prefer_server_ciphers off;

TLS 1.3 cipher suites are configured automatically by OpenSSL and do not need to be specified. If you want to explicitly set them (requires Nginx 1.19.4+):

ssl_conf_command Ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256;

Note: On Debian 11 (Nginx 1.18) and Ubuntu 22.04 (Nginx 1.18), ssl_conf_command is not available. It can safely be omitted since OpenSSL enables all TLS 1.3 ciphers by default.

Certificate Configuration

Point Nginx to your certificate, private key, and the full certificate chain (for OCSP stapling):

ssl_certificate     /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
ssl_trusted_certificate /etc/nginx/ssl/chain.pem;

Session Settings

Configure session caching to improve performance for returning clients, and disable session tickets for forward secrecy:

ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
  • ssl_session_cache - stores TLS session parameters in shared memory, reducing the cost of repeated handshakes. 10m provides space for roughly 40,000 sessions. Since nginx 1.23.2, the shared cache also automatically generates and rotates session ticket encryption keys in shared memory, so tickets are safe to enable on single-server deployments running 1.23.2+.
  • ssl_session_tickets off - disables TLS session tickets entirely. Session resumption still works via the shared session cache (session IDs). This is the strictest option; if you need ticket-based resumption across a cluster, configure ssl_session_ticket_key with a shared key rotated externally.

OCSP Stapling

OCSP stapling attaches the certificate's revocation status to the TLS handshake, eliminating the need for clients to contact the CA independently. This improves connection speed and user privacy.

ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 1.0.0.1 valid=300s;
resolver_timeout 5s;

HTTP Strict Transport Security (HSTS)

HSTS instructs browsers to only connect over HTTPS for the specified duration. The includeSubDomains flag extends this to all subdomains, and preload allows submission to browser HSTS preload lists.

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

Only enable includeSubDomains if all subdomains support HTTPS. Only add preload if you intend to submit your domain to the HSTS preload list, as this is difficult to reverse.

HTTPS Redirect

Redirect all HTTP traffic to HTTPS:

server {
    listen 80;
    listen [::]:80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

Complete Configuration Example

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

    # Certificates
    ssl_certificate     /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/privkey.pem;
    ssl_trusted_certificate /etc/nginx/ssl/chain.pem;

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

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

    # OCSP stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 1.1.1.1 1.0.0.1 valid=300s;
    resolver_timeout 5s;

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

    # ... your site configuration
}

Mutual TLS (mTLS)

Standard TLS authenticates only the server: the client verifies the server's certificate, but the server does not verify the client. Mutual TLS adds client authentication, requiring the connecting client to also present a certificate. This is an optional hardening step, not required for standard web deployments. It is most useful for internal APIs, admin endpoints, and service-to-service communication where you control all connecting clients.

To require client certificates, specify the CA that signed the client certs and enable verification:

ssl_client_certificate /etc/nginx/ssl/client-ca.crt;
ssl_verify_client on;
  • ssl_client_certificate - CA certificate used to verify client certificates.
  • ssl_verify_client on - Require a valid client certificate. Connections without one are rejected.
  • ssl_verify_client optional - Request a client certificate but allow connections without one. Use $ssl_client_verify in location blocks to enforce per-path.

To enforce mTLS on specific locations only:

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

location /api/ {
    if ($ssl_client_verify != SUCCESS) {
        return 403;
    }
    proxy_set_header X-SSL-Client-DN $ssl_client_s_dn;
    proxy_pass http://backend;
}

Set ssl_verify_depth if client certificates are issued by an intermediate CA:

ssl_verify_depth 2;

See RFC 8446 ยง4.3.2 for the TLS Certificate Request specification, and Wikipedia: Mutual authentication for a general overview.

Security Notes

The cipher suite and protocol configuration in this guide addresses the following known TLS vulnerabilities:

  • POODLE (CVE-2014-3566, 2014): SSL 3.0 is disabled. TLS_FALLBACK_SCSV was added in OpenSSL 1.0.1j / 1.0.2 (October 2014); SSL 3.0 disabled by default in OpenSSL 1.1.0 (August 2016).
  • BEAST (CVE-2011-3389, 2011): Mitigated by recommending TLS 1.2 as the minimum; AEAD-only ciphers eliminate the CBC padding oracle.
  • CRIME (CVE-2012-4929, 2012): TLS compression is off by default in OpenSSL 1.1.0+; do not enable it.
  • Lucky13 (2013): AEAD-only cipher list eliminates CBC padding timing side-channels entirely.
  • FREAK (CVE-2015-0204, 2015): EXPORT-grade ciphers are excluded from the cipher string. Removed from OpenSSL 1.1.0 (August 2016).
  • LOGJAM (CVE-2015-4000, 2015): Short-key DHE is excluded; only ECDHE key exchange is recommended.
  • Sweet32 (CVE-2016-2183, 2016): 3DES is excluded from the cipher string.
  • ROBOT (2017): Static RSA key exchange is excluded; only ECDHE is recommended.
  • Downgrade attacks: TLS_FALLBACK_SCSV prevents protocol version rollback.
  • Renegotiation injection (CVE-2009-3555, 2009): Secure renegotiation is enforced by default in OpenSSL 0.9.8m+; TLS 1.3 removes renegotiation entirely.

The following are not addressable through TLS configuration alone:

  • Heartbleed (CVE-2014-0160, 2014): A memory disclosure bug in OpenSSL 1.0.1 through 1.0.1f. Fixed in OpenSSL 1.0.1g (April 7, 2014). Addressed by patching OpenSSL, not by TLS configuration.
  • BREACH (CVE-2013-3587, 2013): Exploits HTTP-level response compression (gzip/deflate on responses). Mitigated at the application layer by disabling HTTP compression or using BREACH countermeasures; TLS configuration cannot prevent it.
  • DROWN (CVE-2016-0800, 2016): Requires SSLv2 to be enabled on any server sharing the same private key. Ensure SSLv2 is disabled on all services that use the same certificate and key pair.

Verification

After applying your configuration, reload Nginx and verify:

nginx -t
systemctl reload nginx

Test your configuration externally with the Mr.DNS SSL/TLS Certificate Check. The settings above should produce a clean report with strong ciphers and a valid chain.

You can also test locally with OpenSSL:

openssl s_client -connect example.com:443 -tls1_2
openssl s_client -connect example.com:443 -tls1_3

Related Guides

View all Web Servers & Proxies guides →

Configured TLS? Now Monitor It.

Generator Labs alerts you before certificates expire, get revoked, or fail chain validation, across HTTPS, SMTPS, IMAPS, LDAPS, and more.

Certificate Monitoring →