Nginx SSL & HTTPS Configuration: The Complete Guide

By G. Bharat Kumar

Running an unencrypted web server on port 80 is no longer an option. In modern web deployment, configuring Nginx SSL/HTTPS is mandatory for security, user trust, and SEO. However, getting a green padlock is only the first step.

In this comprehensive guide, we will cover exactly how to configure an impenetrable Nginx HTTPS setup. From automating Let's Encrypt Nginx certificates to configuring modern Cipher Suites, Diffie-Hellman parameters, HTTP/2, and strict security headers, this 3000+ word tutorial covers everything a DevOps engineer needs to know.

Quick Solution: The Modern SSL Block

Need a secure Nginx configuration right now? Use this block to redirect HTTP to HTTPS and apply modern TLSv1.2/1.3 standards.

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

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

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    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;
    
    # HSTS (requires preloading)
    add_header Strict-Transport-Security "max-age=63072000" always;
}

Architecture of the SSL/TLS Handshake

Before we dive into generating certificates, it's essential to understand the underlying architecture of a TLS connection. When a client attempts to connect to your Nginx server over HTTPS, a cryptographic negotiation takes place before any application data is transferred.

Client                                               Nginx Server
  |                                                       |
  | -------- ClientHello (Supported Ciphers, SNI) ------> |
  |                                                       |
  | <------- ServerHello (Chosen Cipher, Certificate) --- |
  | <------- ServerKeyExchange (DH Parameters) ---------- |
  | <------- ServerHelloDone ---------------------------- |
  |                                                       |
  | -------- ClientKeyExchange (Pre-Master Secret) -----> |
  | -------- ChangeCipherSpec --------------------------> |
  | -------- Finished ----------------------------------> |
  |                                                       |
  | <------- ChangeCipherSpec --------------------------- |
  | <------- Finished ----------------------------------- |
  |                                                       |
  | ============ Secure Application Data ================ |

This process ensures both identity verification (via the Certificate Authority) and secure key exchange (via Diffie-Hellman). Modern TLSv1.3 dramatically reduces this to a single round trip (1-RTT), vastly improving performance.

1. Understanding the Authority Triangle

Now that you understand the handshake, it's important to see where SSL fits into the broader Nginx ecosystem. This article is part of our ZeroData Nginx Authority Triangle:

2. Automating Let's Encrypt in Nginx

Historically, obtaining an SSL certificate meant generating a CSR (Certificate Signing Request), paying a Certificate Authority (CA) hundreds of dollars, and manually placing .pem files on your server.

Today, Let's Encrypt has revolutionized this process by providing free, automated certificates. The standard tool for this is certbot.

Installing Certbot

On Ubuntu/Debian systems, you can install Certbot and the Nginx plugin with a single command:

sudo apt update
sudo apt install certbot python3-certbot-nginx

Obtaining the Certificate

Ensure you have a basic Nginx server block listening on port 80 for your domain:

server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/html;
}

Then, simply run Certbot. The Nginx plugin will automatically parse your config files, perform the ACME challenge, and rewrite your configuration to include the SSL directives.

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

Certbot automatically sets up a cron job (or systemd timer) to renew these certificates before their 90-day expiration window closes.

3. Deep Dive into ssl_certificate Directives

If you look closely at the Nginx configuration generated by Certbot, you will notice two critical directives:

  • ssl_certificate: Points to the fullchain.pem file. This file contains not just your domain's public key certificate, but also the intermediate certificates required to establish a chain of trust back to a root CA.
  • ssl_certificate_key: Points to the privkey.pem file. This is your server's private key. It must remain completely secret. If this key is compromised, attackers can impersonate your server and decrypt traffic.

Critical Security Warning

Never expose or share your privkey.pem file. If an attacker gains access to your private key, they can impersonate your server and decrypt all intercepted traffic. Ensure directory permissions for /etc/letsencrypt/archive are strictly set to 0700 and owned by the root user.

4. Hardening TLS Protocols and Ciphers

By default, old versions of Nginx might enable outdated protocols like TLSv1.0 or TLSv1.1, which are vulnerable to attacks like BEAST and POODLE. In 2026, you should strictly enforce TLSv1.2 and TLSv1.3.

ssl_protocols TLSv1.2 TLSv1.3;

Furthermore, you must define which encryption ciphers are acceptable. We strongly recommend using Mozilla's "Intermediate" or "Modern" cipher recommendations.

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;

Notice ssl_prefer_server_ciphers off;. In TLSv1.3, it is generally recommended to let the client dictate the cipher suite from the secure list you provide, especially to take advantage of ChaCha20-Poly1305 on mobile devices without hardware AES acceleration.

5. Perfect Forward Secrecy and Diffie-Hellman

Perfect Forward Secrecy (PFS) ensures that even if your server's private key is compromised in the future, past traffic cannot be decrypted. This is achieved using the Diffie-Hellman (DH) key exchange.

Nginx defaults to a 1024-bit DH prime, which is vulnerable to the Logjam attack. You must generate a custom, highly secure 2048 or 4096-bit prime:

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

Then, instruct Nginx to use it:

ssl_dhparam /etc/nginx/dhparam.pem;

6. Enabling HTTP/2 for Performance

HTTP/2 introduces multiplexing, allowing multiple requests to share a single TCP connection, drastically improving load times for sites with many assets. Nginx makes this trivial to enable, provided you are using SSL (browsers require HTTPS for HTTP/2).

listen 443 ssl http2;

Note: If you are using Nginx 1.25.1 or later, the http2 parameter has been deprecated inside the listen directive in favor of a standalone http2 on; directive.

7. Security Headers: HSTS

Even if you redirect HTTP to HTTPS, a user typing `example.com` into their browser initially makes an insecure HTTP request. An attacker on the same network could intercept this initial request and strip the redirect (SSL Stripping).

HTTP Strict Transport Security (HSTS) solves this. It tells the browser "For the next X seconds, never attempt to connect to this site using insecure HTTP."

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

Use our Security Headers Builder to visually construct and validate your HSTS and Content-Security-Policy headers before deploying them.

8. Advanced Troubleshooting & Common Errors

Even with a perfect configuration, SSL/TLS deployments can fail due to network constraints, upstream proxy issues, or certificate chain problems. Here are the most common Nginx SSL errors and exactly how to resolve them.

Error: "SSL_do_handshake() failed"

If you look at your Nginx error log (/var/log/nginx/error.log) and see this error, it usually indicates a mismatch between the client's supported ciphers and your server's allowed ciphers.

2026/07/21 14:30:15 [crit] 14532#14532: *241 SSL_do_handshake() failed (SSL: error:1417D18C:SSL routines:tls_process_client_hello:version too low) while SSL handshaking

Solution: The client is attempting to connect using an outdated protocol (like TLSv1.0 or TLSv1.1), but your Nginx server is strictly enforcing TLSv1.2/1.3. If you must support legacy IoT devices, you may need to temporarily re-enable older protocols, though this is heavily discouraged.

Error: "cannot load certificate key"

This occurs when Nginx fails to restart because it cannot find or read the private key specified in your configuration.

root@server:~# nginx -t
nginx: [emerg] cannot load certificate key "/etc/letsencrypt/live/example.com/privkey.pem": BIO_new_file() failed (SSL: error:02001002:system library:fopen:No such file or directory)
nginx: configuration file /etc/nginx/nginx.conf test failed

Solution: Verify that the path to the certificate is absolutely correct. Check file permissions—Nginx typically starts as the root user, so it needs root access to read the /etc/letsencrypt/ directory during startup.

Testing Configuration with OpenSSL

You can manually verify your Nginx server's SSL configuration using the OpenSSL command-line client. This is incredibly useful for debugging certificate chains and SNI (Server Name Indication) issues.

# Test the connection and view the full certificate chain
openssl s_client -connect example.com:443 -servername example.com

# Verify which TLS protocol and cipher was negotiated
openssl s_client -connect example.com:443 -servername example.com < /dev/null | grep -E 'Protocol|Cipher'

Typical successful output will confirm the secure connection parameters:

New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 2048 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)

9. Nginx as a Secure Reverse Proxy

Often, Nginx terminates the SSL connection and proxies the traffic to an upstream application server (like Node.js, Python, or Go) running on a local port. You must ensure you pass the correct headers so the upstream application knows the original request was secure.

location /api/ {
    proxy_pass http://127.0.0.1:3000;
    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;
    
    # Required for WebSockets over HTTPS (WSS)
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

The X-Forwarded-Proto: $scheme; header is critical here. It informs the backend application that the connection was made via HTTPS, preventing infinite redirect loops if your application enforces HTTPS internally.

Conclusion

Configuring an Nginx SSL/HTTPS server goes far beyond simply turning on port 443. By combining Let's Encrypt automation, strict cipher suites, custom DH parameters, HTTP/2, and HSTS headers, you ensure your application meets the highest standards of modern cryptographic security.

Always remember to continuously test your endpoints. When diagnosing complex certificate chains, do not paste your private keys or sensitive certs into remote servers. Instead, utilize client-side tools like the X.509 Certificate Decoder to maintain absolute privacy.

Frequently Asked Questions

How do I redirect HTTP to HTTPS in Nginx?

To redirect all HTTP traffic to HTTPS, create a server block listening on port 80 and use the directive return 301 https://$host$request_uri;. This ensures all unencrypted traffic is permanently redirected.

What is Let's Encrypt and how does it work with Nginx?

Let's Encrypt is a free, automated, and open certificate authority. By installing Certbot and the python3-certbot-nginx plugin, you can automatically provision and renew trusted SSL certificates for your Nginx server.

What are the best SSL protocols and ciphers to use in 2026?

In 2026, you should exclusively use TLSv1.2 and TLSv1.3. Disable SSLv3, TLSv1.0, and TLSv1.1 entirely. Use modern AEAD ciphers like ECDHE-ECDSA-AES128-GCM-SHA256 and CHACHA20-POLY1305.

How do I enable HTTP/2 in Nginx?

To enable HTTP/2 in Nginx, simply add http2 to your listen directive (e.g., listen 443 ssl http2;). Note that in Nginx version 1.25.1 and later, the http2 on; directive is used instead if you don't append it to listen.

Why do I need a Diffie-Hellman parameter file (dhparam)?

A Diffie-Hellman parameter file ensures Perfect Forward Secrecy (PFS) using a strong, unique key exchange modulus (at least 2048-bit, preferably 4096-bit). Without it, Nginx uses a weak default 1024-bit key, making you vulnerable to Logjam attacks.