Aller au contenu
EdgeServers

Nginx, HTTP/3, and a TLS config that's actually current for 2026

QUIC support, TLS 1.3, OCSP stapling, cipher hardening, and the small details that decide whether your edge gets an A+ or a C on every TLS scanner.

22 mai 2026 · 9 min · par Sudhanshu K.

Nginx, HTTP/3, and a TLS config that's actually current for 2026

HTTP/3 stopped being optional somewhere around 2024. By 2026, every major browser negotiates it where available, Cloudflare-fronted traffic uses it by default, and a measurable slice of mobile traffic gets visibly faster page loads because of it. If your Nginx edge still terminates only HTTP/2, you're leaving real performance on the table — particularly for users on lossy networks where QUIC's loss recovery shines.

This post is the TLS and HTTP/3 config we ship by default for managed Nginx customers. It's specific, it's current, and it scores A+ on Qualys SSL Labs and the equivalent scanners without you having to hand-tune anything.

What you need on the box

Two prerequisites:

  1. Nginx 1.25.0 or later built with the --with-http_v3_module flag. The stock Nginx packages on most distros have shipped this since mid-2024.
  2. A modern OpenSSL — OpenSSL 3.2+ for built-in QUIC support, or BoringSSL/quictls if you're on an older Nginx build. We standardise on OpenSSL 3.3 across our customer estate.

Verify:

nginx -V 2>&1 | grep -o 'with-http_v3_module'
nginx -V 2>&1 | grep -i 'openssl'

If those two return what you expect, you're ready. If you're on an older build and don't want to rebuild, that's exactly the kind of toil we handle as part of provisioning — fresh edges come with a tested, signed Nginx build out of the box.

Listening on QUIC

QUIC runs over UDP/443, alongside the existing TCP/443 listener. Both have to coexist because the first request from a new client always comes in over HTTP/2 (TCP); QUIC is advertised via Alt-Svc and used for subsequent connections.

server {
    # HTTP/2 over TCP — the bootstrap path
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
 
    # HTTP/3 over UDP — the upgrade path
    listen 443 quic reuseport;
    listen [::]:443 quic reuseport;
    http3 on;
    http3_hq off;
 
    server_name app.example.com;
 
    # Advertise HTTP/3 to clients that just connected via HTTP/2
    add_header Alt-Svc 'h3=":443"; ma=86400' always;
    add_header X-Quic-Available '1' always;
 
    # ... TLS config below
}

A few things to call out:

reuseport on the QUIC listeners. This distributes incoming UDP datagrams across workers via the kernel's SO_REUSEPORT mechanism. Without it, one worker handles all QUIC traffic and you waste cores. Mandatory for any non-trivial load.

Alt-Svc with ma=86400. Tells the client "for the next 86400 seconds, you can use HTTP/3 on this hostname". The client caches this and switches to QUIC for subsequent visits. Without Alt-Svc, no browser will ever use HTTP/3 against your site — discovery is opt-in.

http3_hq off. Disables HTTP/0.9-over-QUIC, which exists only for niche test tooling. You don't want it on a production listener.

Open UDP/443 in the firewall. This is the most common HTTP/3 deployment bug. The TCP/443 listener works, the UDP/443 listener silently doesn't because the cloud security group only opened TCP. For GCP-hosted customers, we generate the firewall rules from the Nginx config so the two can't drift.

The TLS block — current as of 2026

This is the TLS config we ship on every customer edge. It's deliberately strict — TLS 1.2 minimum, modern ciphers only, OCSP stapling on, session resumption tuned, HSTS preloaded.

# Certificate (RSA + ECDSA dual-cert recommended)
ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.ecdsa.pem;
ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.ecdsa.pem;
ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.rsa.pem;
ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.rsa.pem;
 
# Protocol versions
ssl_protocols TLSv1.2 TLSv1.3;
 
# Cipher suites (TLS 1.2; TLS 1.3 ciphers are negotiated separately)
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;
 
# TLS 1.3 cipher suites
ssl_conf_command Ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256;
 
# Elliptic curves (X25519 preferred, P-256 fallback)
ssl_ecdh_curve X25519:secp384r1:secp256r1;
 
# Session resumption
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
ssl_session_tickets off;   # Forward secrecy: prefer cache to tickets
 
# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/app.example.com/chain.pem;
resolver 1.1.1.1 1.0.0.1 8.8.8.8 valid=300s;
resolver_timeout 5s;
 
# Early data (0-RTT) — only for idempotent requests
ssl_early_data on;
 
# HSTS
add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains; preload' always;
 
# Other security headers
add_header X-Content-Type-Options 'nosniff' always;
add_header X-Frame-Options 'SAMEORIGIN' always;
add_header Referrer-Policy 'strict-origin-when-cross-origin' always;

The non-obvious choices, with reasoning:

Dual-certificate (ECDSA + RSA). Modern clients prefer ECDSA (smaller, faster handshakes), but ancient clients on legacy networks still need RSA. Loading both lets Nginx pick per-connection. ECDSA dominates by 95%+ of traffic for any consumer-facing site in 2026.

ssl_prefer_server_ciphers off. Counterintuitive but correct for TLS 1.3 — the client's preference is more likely to reflect its actual capabilities (battery, hardware AES-NI). For TLS 1.2 the cipher list above is already a safe ordering.

ssl_session_tickets off, cache on. Session tickets have a forward-secrecy footgun: the ticket-encryption key is long-lived, and if it leaks, all past sessions are decryptable. Session cache stores the resumption material server-side and rotates automatically. We accept a small RAM cost for the stronger guarantee.

ssl_early_data on. TLS 1.3 0-RTT, which lets returning clients send a request with the first packet. Only safe for idempotent endpoints (GET / HEAD); we configure the application to reject early-data POSTs. Browsers will use it automatically and pages-on-second-visit get visibly faster.

resolver for OCSP stapling. Without an explicit resolver, OCSP stapling silently fails on cold start. We see this on roughly a third of the configs we audit — the SSL Labs scanner reports "OCSP stapling: no" and nobody knows why. The fix is one line.

Why OCSP stapling actually matters

When a browser validates your certificate, it wants to know whether the cert has been revoked. Without stapling, it queries the CA's OCSP responder directly — a separate network round-trip, often to a slow endpoint, blocking page load. With stapling, your server pre-fetches the OCSP response and includes it in the TLS handshake. The browser is happy without the extra hop.

The performance win is measurable. On a cold connection from Sydney to a US-hosted CA's OCSP responder, the OCSP check adds 200-400ms to the first page load. Stapling removes that entirely. It's free to enable and free to operate, and yet a substantial fraction of production sites still don't have it on.

There's also a privacy dimension: without stapling, the CA can build a log of who visits your site (by tracking OCSP query patterns). Stapling severs that.

Certificate automation

We use Let's Encrypt for the vast majority of customer certificates, with the ACME client running on each edge node and renewing 30 days before expiry. Dual-cert (RSA + ECDSA) renewal is handled by running the ACME client twice with different --key-type flags.

# /etc/cron.d/certbot-renew
0 */12 * * * root certbot renew --quiet --key-type ecdsa --post-hook "nginx -s reload"
30 */12 * * * root certbot renew --quiet --key-type rsa --post-hook "nginx -s reload"

The nginx -s reload is graceful — existing connections complete on the old cert, new connections use the new one. We monitor expiry days remaining as a Prometheus metric and alert if any cert drops below 14 days (giving plenty of headroom if a renewal hiccups).

For customers with internal-only services that aren't publicly resolvable, we use ACME's DNS-01 challenge against their managed DNS provider, or an internal step-ca instance. The Nginx config is the same; the source of the cert changes.

Hardening beyond the cipher list

A correct TLS config is necessary but not sufficient. The full edge-hardening checklist we apply for cybersecurity-managed customers also covers:

  • Server tokens offserver_tokens off; so the response headers don't leak the exact Nginx version
  • TLS version monitoring — log the negotiated protocol and cipher; alert if any negotiation lands on TLS 1.2 with a non-PFS cipher (it shouldn't, but if a misconfig sneaks in, we want to know)
  • CT log monitoring — alert when a certificate is issued for our customer's domains by any CA, so misissuance shows up within minutes
  • DNS CAA records — explicitly list which CAs may issue for the domain, blocking unauthorized issuance
  • Quarterly scanner regression — automated Qualys SSL Labs scan, with a Slack alert if the grade drops

The cipher and protocol world moves; what's A+ today is B in two years. The point of the regression check is to catch the slow drift, not to one-shot it and forget.

What we ship by default

Every new edge from us comes with:

  • HTTP/3 + HTTP/2 dual-listeners, with Alt-Svc and firewall rules pre-configured
  • Dual-cert (ECDSA + RSA) issuance from Let's Encrypt, auto-renewing
  • The TLS config above as a standardised include
  • OCSP stapling with an explicit resolver, verified working
  • HSTS preload + DNS CAA + CT log monitoring
  • A scheduled monthly run of nmap --script ssl-enum-ciphers plus an SSL Labs scrape, results piped into the customer dashboard

It's the kind of setup that's easy to get 80% right and hard to get 100% right by hand. We've standardised so we don't have to relearn the cipher list every six months when the IETF moves something around. If you want this stamped onto an existing edge without rebuilding it, that's a half-day of work — message us and we'll do it.

Sudhanshu K. is a Principal Platform engineer at EdgeServers (RemotIQ Pty Ltd, ABN 91 682 628 128). He has written far too many certbot renewal hooks and considers Alt-Svc the most under-appreciated HTTP header of the decade.