HTTPS and TLS termination

NGINX · Practical · 14 min

Every public site must use HTTPS, and NGINX is usually where TLS is terminated.

A TLS server needs listen 443 ssl;, a certificate chain and its private key:

  • ssl_certificate must contain the server certificate followed by intermediate certificates (a "fullchain" file). Missing intermediates work in some browsers and fail in others and in curl.
  • ssl_certificate_key is the private key; keep it readable only by root.

Since NGINX 1.25.1, HTTP/2 is enabled with the separate directive http2 on; (the old listen ... http2 parameter is deprecated). HTTP/3 uses listen 443 quic; and needs a build with QUIC support.

Recommended settings:

  • ssl_protocols TLSv1.2 TLSv1.3; (the modern default).
  • ssl_session_cache shared:SSL:10m; to resume sessions cheaply.
  • An add_header Strict-Transport-Security "max-age=31536000" always; header (HSTS) once you are sure HTTPS works everywhere.

Redirect plain HTTP with a small server: return 301 https://$host$request_uri;. Keep a location /.well-known/acme-challenge/ on port 80 if you use Let's Encrypt with the HTTP challenge; certbot can edit NGINX configuration and renew certificates automatically, after which NGINX needs a reload to pick up the new files.

Test with curl -vI https://... and an external scanner such as SSL Labs.

Example

server {
    listen 80;
    server_name example.com;
    location /.well-known/acme-challenge/ { root /var/www/certbot; }
    location / { return 301 https://$host$request_uri; }
}

server {
    listen 443 ssl;
    http2 on;
    server_name 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_session_cache   shared:SSL:10m;
    add_header Strict-Transport-Security "max-age=31536000" always;

    location / { proxy_pass http://127.0.0.1:8000; }
}

Remember

  • listen 443 ssl plus a fullchain certificate and key
  • http2 on; enables HTTP/2 since 1.25.1
  • Redirect port 80 with return 301 https://$host$request_uri

Watch out for

  • Configuring only the leaf certificate without intermediates; some clients then fail verification.

Check yourself

  1. Browsers accept your site but curl reports 'unable to get local issuer certificate'. Most likely cause?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: nginx.org/en/docs/http/configuring_https_servers.html