| |

LFCE 48 ๐Ÿง Nginx โ€” TLS Configuration

โฑ๏ธ Estimated read: 14 min | |

TLS configuration is the step that turns an HTTP server into an HTTPS server. Nginx handles TLS through the ngx_http_ssl_module, which is compiled into the standard packages but must be enabled by adding the ssl parameter to the listen directive and pointing ssl_certificate and ssl_certificate_key at the certificate and private key files. The previous chapter covered virtual hosts; this chapter adds the encryption layer that protects the traffic those hosts serve.

The configuration has two parts that are often conflated. The first is the certificate itself: where it comes from, how it is chained with intermediate certificates, and how it is renewed. The second is the TLS policy: which protocol versions are permitted, which cipher suites are offered, and which security headers accompany the response. A certificate without a policy is a server that encrypts, but may encrypt with obsolete protocols or weak ciphers. A policy without a certificate is a server that refuses to start.

Key point: The ssl_certificate directive must point to the full certificate chain, with the server certificate first and the intermediate certificates after it. If the server certificate is not first, nginx fails to start with an SSL_CTX_use_PrivateKey_file error because it tries to match the private key against the wrong certificate . Modern nginx defaults to TLS 1.2 and TLS 1.3, but the protocol and cipher directives should be set explicitly for clarity and to exclude legacy clients that the default might otherwise accept .


Why TLS configuration requires certificate chain discipline

The chain-order problem.

A certificate authority signs the server certificate with an intermediate certificate, and the intermediate is signed by a root certificate that the browser trusts. The browser needs the intermediate to verify the chain, but many servers send only the server certificate. Some browsers cache intermediates from previous connections and accept the incomplete chain; others do not, and the user sees a warning. The fix is to concatenate the server certificate and the intermediate bundle into a single file, with the server certificate first, and to point ssl_certificate at that combined file . The order is not cosmetic: if the bundle comes first, nginx attempts to use the private key with the bundle’s first certificate and refuses to start.

The protocol-version problem.

TLS 1.0 and TLS 1.1 are deprecated and vulnerable to known attacks. TLS 1.2 is the minimum acceptable version for modern deployments, and TLS 1.3 is preferred because it removes obsolete cipher suites and reduces handshake latency. Nginx versions since 1.23.4 default to TLSv1.2 TLSv1.3, but older versions and some distribution builds still include TLS 1.0 and 1.1. The ssl_protocols directive should be set explicitly to TLSv1.2 TLSv1.3 so that the policy does not depend on the build’s default .

The cipher-suite problem.

TLS 1.2 allows the client and server to negotiate a cipher suite, and some suites are weak. The ssl_ciphers directive restricts the offered suites. For TLS 1.3, the cipher suites are fixed by the protocol and cannot be configured, so the ssl_ciphers directive affects only TLS 1.2 and earlier. A modern configuration uses a curated list that prefers ECDHE for forward secrecy and AES-GCM or ChaCha20-Poly1305 for authenticated encryption .


a. The basic TLS server block

The minimum TLS configuration adds three directives to an existing server block: the listen with ssl, the ssl_certificate, and the ssl_certificate_key.

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

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

    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;

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

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

The listen 443 ssl; directive binds the block to the HTTPS port and enables TLS processing. The ssl_certificate points to the chained certificate file, and ssl_certificate_key points to the private key. The ssl_protocols directive restricts the accepted versions, and ssl_ciphers restricts the TLS 1.2 suites .

A separate server block on port 80 handles the HTTP-to-HTTPS redirect. Without it, clients that type http://example.com receive a connection error or the default site instead of a redirect.

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

The return 301 directive sends a permanent redirect, and $host preserves the hostname the client requested. This is the standard pattern for forcing HTTPS while keeping the HTTP port open for the ACME challenge that certificate renewal requires.


b. TLS policy and security headers

The protocol and cipher directives set the encryption policy. The security headers tell the browser to enforce it.

server {
    listen 443 ssl;
    server_name example.com;

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

    # Protocol policy
    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';
    ssl_prefer_server_ciphers off;

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

    # OCSP stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/nginx/ssl/example.com.chain.pem;
    resolver 8.8.8.8 8.8.4.4 valid=300s;

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

The ssl_prefer_server_ciphers off setting is correct for modern clients because it allows the client to choose from the server’s list, and TLS 1.3 clients ignore the list anyway. The session cache stores session parameters so that returning clients do not repeat the full handshake; shared:SSL:10m allocates a 10-megabyte cache shared across worker processes . The ssl_session_tickets off directive disables session tickets, which can be a security risk if the ticket key is not rotated.

OCSP stapling allows the server to fetch the certificate’s revocation status and include it in the handshake, so the client does not need to contact the certificate authority separately. The ssl_trusted_certificate directive is required for stapling verification; without it, nginx cannot validate the OCSP response it receives . The resolver directive tells nginx which DNS servers to use for the OCSP lookup.

The Strict-Transport-Security header tells the browser to use HTTPS for all future requests to the domain, even if the user types http://. The max-age value is in seconds; 63072000 is two years. The includeSubDomains directive extends the policy to subdomains. The preload token is omitted here because it requires submission to the browser preload list and is difficult to reverse .


c. Obtaining and renewing certificates with Certbot

The certificates themselves come from a certificate authority. Let’s Encrypt is the free, automated option, and Certbot is its client. The Certbot nginx plugin modifies the server block automatically, adding the ssl_certificate and ssl_certificate_key directives and the HTTP-to-HTTPS redirect.

# Debian / Ubuntu
sudo apt install certbot python3-certbot-nginx

# RHEL / Rocky (EPEL required)
sudo dnf install certbot python3-certbot-nginx

# Obtain and install the certificate
sudo certbot --nginx -d example.com -d www.example.com

The --nginx flag tells Certbot to use the nginx plugin, which reads the nginx configuration to find the server_name and modifies it in place. The -d flags specify the domain names; both the apex and the www subdomain should be included so that both are covered by the certificate. Certbot validates domain ownership by placing a file at /.well-known/acme-challenge/ and asking Let’s Encrypt to fetch it over HTTP on port 80 . This is why port 80 must remain open even after HTTPS is configured: the renewal uses the same challenge.

Certbot installs a systemd timer that runs twice daily and renews certificates that are within 30 days of expiry. The timer can be checked and tested manually.

# Check the timer
sudo systemctl status certbot-renew.timer

# Test renewal without consuming rate limits
sudo certbot renew --dry-run

The --dry-run flag performs the renewal process against the Let’s Encrypt staging environment, which has much higher rate limits than production. Running it before the first real renewal confirms that the challenge path and the plugin configuration are correct . If the dry run succeeds, the automatic renewal will succeed.


Complete Example Session

# ============================================
# PART 1: INSTALL CERTBOT
# ============================================
sudo apt install certbot python3-certbot-nginx

# ============================================
# PART 2: ENSURE THE SERVER BLOCK HAS A server_name
# ============================================
# Certbot needs a server_name to match the domain
# The block must be listening on port 80
sudo tee /etc/nginx/sites-available/example.com.conf > /dev/null <<'EOF'
server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/example;
    index index.html;
}
EOF
sudo ln -sf /etc/nginx/sites-available/example.com.conf /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

# ============================================
# PART 3: OBTAIN AND INSTALL THE CERTIFICATE
# ============================================
sudo certbot --nginx -d example.com -d www.example.com
# Certbot modifies the server block:
# - Adds listen 443 ssl
# - Adds ssl_certificate and ssl_certificate_key
# - Adds HTTP-to-HTTPS redirect

# ============================================
# PART 4: INSPECT THE CERTBOT-MODIFIED BLOCK
# ============================================
cat /etc/nginx/sites-available/example.com.conf
# Output includes:
# listen 443 ssl; # managed by Certbot
# ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# include /etc/letsencrypt/options-ssl-nginx.conf;
# ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

# ============================================
# PART 5: ADD EXPLICIT TLS POLICY
# ============================================
# The options-ssl-nginx.conf file sets ssl_protocols and ssl_ciphers
# For clarity, set them explicitly in the server block
sudo tee -a /etc/nginx/sites-available/example.com.conf > /dev/null <<'EOF'

# Explicit TLS policy
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';
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
resolver 8.8.8.8 8.8.4.4 valid=300s;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
EOF

# ============================================
# PART 6: VALIDATE AND RELOAD
# ============================================
sudo nginx -t
sudo systemctl reload nginx

# ============================================
# PART 7: TEST THE CERTIFICATE
# ============================================
curl -I https://example.com
# Output: HTTP/2 200 (or HTTP/1.1 200)
# The connection is encrypted with the Let's Encrypt certificate

# ============================================
# PART 8: VERIFY THE CHAIN
# ============================================
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -dates -subject
# Output shows the certificate subject and validity dates

# ============================================
# PART 9: TEST RENEWAL
# ============================================
sudo certbot renew --dry-run
# Output: Congratulations, all renewals succeeded

# ============================================
# PART 10: CHECK THE RENEWAL TIMER
# ============================================
sudo systemctl status certbot-renew.timer
# Output: active (waiting)

The ten parts demonstrate the complete TLS configuration: Certbot installation, server block preparation, certificate issuance, inspection of the Certbot-modified block, explicit TLS policy addition, validation, HTTPS testing, chain verification, renewal testing, and timer verification. The critical steps are Part 5 (explicit policy) and Part 9 (renewal dry run), which confirm that the encryption is configured correctly and will remain valid.


Quick Reference

TLS Directives

DirectivePurposeExample
listen 443 sslEnable TLS on port 443listen 443 ssl;
ssl_certificateCertificate chain file/etc/letsencrypt/live/.../fullchain.pem
ssl_certificate_keyPrivate key file/etc/letsencrypt/live/.../privkey.pem
ssl_protocolsAccepted TLS versionsTLSv1.2 TLSv1.3
ssl_ciphersTLS 1.2 cipher suitesHIGH:!aNULL:!MD5
ssl_session_cacheSession cacheshared:SSL:10m
ssl_staplingOCSP staplingon
ssl_trusted_certificateChain for stapling verificationchain.pem
add_header HSTSForce HTTPS in browsermax-age=63072000

Certbot Commands

CommandPurpose
certbot --nginx -d <domain>Obtain and install certificate
certbot certificatesList managed certificates
certbot renew --dry-runTest renewal without rate limits
systemctl status certbot-renew.timerCheck renewal timer

Certificate Files

FileContent
fullchain.pemServer certificate + intermediates
privkey.pemPrivate key
chain.pemIntermediates only
cert.pemServer certificate only

Best Practices

โœ… Do This:

# Use the full chain for ssl_certificate
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;  # โœ…

# Set explicit protocol versions
ssl_protocols TLSv1.2 TLSv1.3;                                    # โœ…

# Enable OCSP stapling with trusted certificate
ssl_stapling on; ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem; # โœ…

# Add HSTS header
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always; # โœ…

# Keep port 80 open for ACME challenges
# The HTTP-to-HTTPS redirect must exempt /.well-known/acme-challenge/ # โœ…

# Test renewal before relying on it
certbot renew --dry-run                                           # โœ…

โŒ Don’t Do This:

# Use the bare server certificate without the chain
ssl_certificate /etc/letsencrypt/live/example.com/cert.pem;    # โŒ

# Allow obsolete TLS versions
ssl_protocols TLSv1 TLSv1.1;                                     # โŒ

# Enable OCSP stapling without a resolver
ssl_stapling on;    # No resolver โ†’ stapling fails               # โŒ

# Close port 80 after enabling HTTPS
# Renewal fails silently until the certificate expires            # โŒ

# Use the preload HSTS token without understanding the consequences
add_header Strict-Transport-Security "...; preload";             # โŒ (difficult to reverse)

Common Pitfalls

PitfallWhy It HappensFix
Browser warns about incomplete chainssl_certificate points to bare certUse fullchain.pem or concatenated chain
Nginx fails to start with key mismatchChain concatenated in wrong orderServer cert first, then intermediates
Renewal fails silentlyPort 80 closedKeep port 80 open for ACME challenge
OCSP stapling does not workNo resolver or ssl_trusted_certificateAdd both directives
Certbot cannot find server blockserver_name is _ or missingSet the actual domain in server_name
HSTS preload causes lockoutpreload submitted with misconfigurationOmit preload until confirmed stable

Real-World Examples

1. Basic TLS server block

server { listen 443 ssl; server_name example.com; ssl_certificate /path/fullchain.pem; ssl_certificate_key /path/privkey.pem; }

2. Protocol restriction

ssl_protocols TLSv1.2 TLSv1.3;

3. Cipher restriction

ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';

4. Session cache

ssl_session_cache shared:SSL:10m; ssl_session_timeout 1d;

5. OCSP stapling

ssl_stapling on; ssl_stapling_verify on; ssl_trusted_certificate /path/chain.pem; resolver 8.8.8.8;

6. HSTS header

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

7. HTTP redirect

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

8. Certbot issuance

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

9. Renewal test

certbot renew --dry-run

10. Verify chain

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

Visual

Certificate Chain Order

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  ssl_certificate MUST POINT TO THE CHAIN IN THIS ORDER       โ”‚
โ”‚                                                              โ”‚
โ”‚  fullchain.pem:                                              โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ 1. Server certificate (example.com)                    โ”‚  โ”‚
โ”‚  โ”‚ 2. Intermediate certificate (R3)                       โ”‚  โ”‚
โ”‚  โ”‚ 3. (Optional) Root certificate                         โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚                                                              โ”‚
โ”‚  โœ… Correct order: server cert first                         โ”‚
โ”‚  โŒ Wrong order: nginx fails to start with key mismatch      โ”‚
โ”‚                                                              โ”‚
โ”‚  The browser needs the intermediate to build the chain       โ”‚
โ”‚  from the server certificate to the trusted root.            โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

TLS Handshake with OCSP Stapling

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  OCSP STAPLING AVOIDS A SEPARATE CA LOOKUP                   โ”‚
โ”‚                                                              โ”‚
โ”‚  Without stapling:                                           โ”‚
โ”‚  Client โ”€โ”€โ–ถ Server: "Here is my cert"                        โ”‚
โ”‚  Client โ”€โ”€โ–ถ CA: "Is this cert still valid?"                  โ”‚
โ”‚  CA โ”€โ”€โ–ถ Client: "Yes"                                        โ”‚
โ”‚                                                              โ”‚
โ”‚  With stapling:                                              โ”‚
โ”‚  Server โ”€โ”€โ–ถ CA (cached): "Is this cert still valid?"         โ”‚
โ”‚  Client โ”€โ”€โ–ถ Server: "Here is my cert + OCSP response"        โ”‚
โ”‚  Client: verifies the stapled response locally               โ”‚
โ”‚                                                              โ”‚
โ”‚  Requires: ssl_stapling on, ssl_trusted_certificate, resolverโ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

HTTP-to-HTTPS Redirect with ACME Exemption

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  PORT 80 MUST SERVE THE ACME CHALLENGE, THEN REDIRECT        โ”‚
โ”‚                                                              โ”‚
โ”‚  server {                                                    โ”‚
โ”‚      listen 80;                                              โ”‚
โ”‚      server_name example.com;                                โ”‚
โ”‚                                                              โ”‚
โ”‚      location /.well-known/acme-challenge/ {                 โ”‚
โ”‚          root /var/www/html;    โ† Serve challenge file       โ”‚
โ”‚      }                                                       โ”‚
โ”‚                                                              โ”‚
โ”‚      location / {                                            โ”‚
โ”‚          return 301 https://$host$request_uri;               โ”‚
โ”‚      }                                                       โ”‚
โ”‚  }                                                           โ”‚
โ”‚                                                              โ”‚
โ”‚  Without the exemption, renewal fails because the redirect   โ”‚
โ”‚  prevents Let's Encrypt from fetching the challenge file.    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
TLS modulengx_http_ssl_module
Enable TLSlisten 443 ssl;
Certificatessl_certificate (full chain)
Private keyssl_certificate_key
Protocol versionsssl_protocols TLSv1.2 TLSv1.3;
Cipher suitesssl_ciphers (TLS 1.2 only)
Session cachessl_session_cache shared:SSL:10m;
OCSP staplingssl_stapling on; + ssl_trusted_certificate
HSTSadd_header Strict-Transport-Security ...
Certificate toolCertbot (certbot --nginx)
Renewal testcertbot renew --dry-run
HTTP redirectreturn 301 https://$host$request_uri;

Key takeaways:

  • The certificate chain must be in the correct order. ssl_certificate must point to a file where the server certificate is first and the intermediates follow. The wrong order prevents nginx from starting .
  • TLS 1.2 and TLS 1.3 only. The ssl_protocols directive should explicitly exclude TLS 1.0 and 1.1. Modern nginx defaults are secure, but the explicit directive makes the policy independent of the build .
  • OCSP stapling requires a resolver and a trusted certificate. The ssl_stapling on directive is not sufficient on its own; the ssl_trusted_certificate and resolver directives are required for the stapling verification to function .
  • HSTS tells the browser to always use HTTPS. The Strict-Transport-Security header with max-age and includeSubDomains enforces HTTPS for future requests. The preload token should be omitted until the configuration is confirmed stable.
  • Certbot automates issuance and renewal. The --nginx plugin modifies the server block and installs a systemd timer. The --dry-run flag tests renewal without consuming the production rate limits .
  • Port 80 must remain open. The ACME HTTP-01 challenge requires port 80, and renewal fails silently if the port is closed. The HTTP-to-HTTPS redirect must exempt the /.well-known/acme-challenge/ path .

Remember: TLS configuration is two layers. The certificate layer provides the identity and the chain. The policy layer controls the protocol versions, the cipher suites, and the security headers. The certificate comes from Certbot or another ACME client; the policy is written in the server block. Point ssl_certificate at the full chain, set ssl_protocols to TLS 1.2 and 1.3, enable OCSP stapling with the trusted certificate, and add the HSTS header. Keep port 80 open for renewal, and test with certbot renew --dry-run before relying on the automatic timer.


Was this helpful?


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!