Site icon GetPageSpeed

NGINX TLS Vulnerability Fixes: BREACH, SWEET32, ROBOT

NGINX TLS Vulnerability Fixes: BREACH, Lucky13, SWEET32, ROBOT

Four classic TLS vulnerabilities dominate scan reports to this day: BREACH, Lucky13, SWEET32, and ROBOT. Here is the short version of NGINX TLS vulnerability fixes: one directive, ssl_protocols TLSv1.3;, eliminates three of the four outright. Only BREACH survives, because it lives in HTTP compression rather than in TLS itself, and it needs a scoped gzip off;. This guide walks through each fix, shows how to verify it with openssl s_client, and then goes one step further than fixing old attacks: enabling post-quantum key exchange.

Scanner findings usually trace back to a distribution NGINX build frozen years ago, linked against an equally frozen OpenSSL. Start from a current build instead. GetPageSpeed repositories ship NGINX-MOD, an NGINX build linked against OpenSSL 3.5 with post-quantum X25519MLKEM768 key exchange:

RHEL / CentOS / AlmaLinux / Rocky Linux / Amazon Linux

sudo dnf install https://extras.getpagespeed.com/release-latest.rpm
sudo dnf config-manager --set-enabled getpagespeed-extras-nginx-mod
sudo dnf install nginx-mod

Debian / Ubuntu

First, set up the GetPageSpeed APT repository, then add the nginx-mod suite and install (it auto-swaps the stock nginx package):

echo "deb [signed-by=/etc/apt/keyrings/getpagespeed.gpg] https://extras.getpagespeed.com/${distro} ${codename}-nginx-mod main" | sudo tee /etc/apt/sources.list.d/getpagespeed-nginx-mod.list
sudo apt-get update
sudo apt-get install nginx-mod

Full installation and rollback details live on the NGINX-MOD page.

Every configuration block below was runtime-tested on a clean Rocky Linux 10 server, not just passed through nginx -t.

One directive fixes three of the four

TLS 1.3 removed the building blocks these attacks need at the protocol level. Therefore, restricting NGINX to TLS 1.3 does not merely mitigate ROBOT, SWEET32, and Lucky13. It makes them impossible to express:

ssl_protocols TLSv1.3;
Vulnerability What it exploits Status under TLS 1.3
ROBOT RSA key-exchange padding oracle Gone. TLS 1.3 removed static RSA key exchange entirely.
SWEET32 3DES 64-bit block collisions Gone. TLS 1.3 defines no 3DES cipher suite.
Lucky13 CBC padding timing side-channel Gone. TLS 1.3 is AEAD-only by specification.
BREACH HTTP compression length leak Still exploitable. Compression is above the TLS layer.

The trade-off is client compatibility: TLS 1.3-only cuts off legacy clients such as old Java 8 runtimes and end-of-life Android versions. For the full compatibility discussion, cipher list rationale, and hardened server defaults, see the NGINX TLS 1.3 hardening guide.

If you must keep TLS 1.2

Many fleets still need TLS 1.2 for API clients. In that case, keep it AEAD-only and ECDHE-only. This is the exact remediation the GetPageSpeed SSL Test hands out on its vulnerability findings:

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;
ssl_prefer_server_ciphers off;

This list fixes all three TLS-layer vulnerabilities at once, because each one depends on a cipher class the list simply does not contain: no RSA key exchange (ROBOT), no 3DES (SWEET32), no CBC (Lucky13).

ROBOT: RSA key-exchange padding oracle

ROBOT is a return of the 1998 Bleichenbacher attack. When a server supports RSA key exchange, subtle differences in how it rejects malformed PKCS#1 v1.5 padding act as an oracle. With enough queries, an attacker can decrypt recorded sessions or forge signatures without ever holding the private key.

The fix is removing RSA key-exchange suites, which the cipher list above already does: every suite in it is ECDHE. Verify from any machine with OpenSSL installed:

openssl s_client -connect example.com:443 -tls1_2 -cipher 'AES128-GCM-SHA256:AES256-GCM-SHA384' </dev/null

A fixed server rejects the handshake with ssl/tls alert handshake failure (older OpenSSL clients word it sslv3 alert handshake failure), because those suites use RSA key exchange. If the connection succeeds, the oracle surface is still there.

SWEET32: 3DES birthday attack

3DES encrypts in 64-bit blocks. On a long-lived HTTPS connection pushing tens of gigabytes, block collisions become statistically likely, and each collision leaks plaintext structure. That is the SWEET32 birthday attack.

No modern client needs 3DES, and current OpenSSL builds have removed it outright: on a stock OpenSSL 3.5 client, openssl s_client -cipher 'DES-CBC3-SHA' fails client-side with no cipher match before anything ever reaches your server, even at @SECLEVEL=0. Consequently, you cannot probe SWEET32 from a current workstation at all. Instead, confirm your ssl_ciphers list contains no 3DES suite (the AEAD list above contains none), or run the full scan below: it probes 3DES support with a legacy-capable TLS stack that can still offer these ciphers.

Lucky13: CBC timing side-channel

Lucky13 exploits the MAC-then-encrypt construction of CBC cipher suites. The time a server takes to reject bad padding varies by a few clock cycles depending on the plaintext, and that timing difference is measurable enough to recover bytes. Patches in OpenSSL narrowed the timing gap, however the only complete fix is dropping CBC suites altogether in favor of AEAD ciphers (AES-GCM, ChaCha20-Poly1305). Additionally, AEAD is what TLS 1.3 mandates anyway.

Verify no CBC suite is accepted over TLS 1.2:

openssl s_client -connect example.com:443 -tls1_2 -cipher 'ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256' </dev/null

Expect a handshake failure here too.

BREACH: the one TLS 1.3 does not fix

BREACH does not attack TLS at all. It measures the length of compressed HTTP responses. Three conditions must line up on the same page: HTTP compression is enabled, the response reflects attacker-controlled input, and the response contains a secret such as a CSRF token. The attacker injects guesses through the reflected parameter and watches the compressed length shrink when a guess matches part of the secret. Since compression happens above the TLS layer, upgrading to TLS 1.3 changes nothing.

The tempting fix does not work: you cannot exclude HTML from compression with gzip_types, because NGINX always compresses text/html whenever gzip on is set. The directive that actually works is turning compression off exactly where secrets meet reflected input:

# NGINX always gzips text/html, so gzip_types can't exclude it.
# Turn compression off only where a secret meets reflected input:
location /account/ {
    gzip off;
}

Keep compression on everywhere else. Losing gzip on your CSS to defend a login form is pure waste. For applications where you cannot isolate the reflecting pages, there is a length-padding approach as well. We examined it in detail in NGINX Length Hiding Module: Does It Actually Prevent BREACH Attacks?. Application-level defenses like masked, per-request CSRF tokens close the hole for good.

Verify all four at once

Manual openssl s_client checks cover one vulnerability at a time. The GetPageSpeed SSL Test probes your server for all four of these, plus protocol versions, certificate chain issues, Encrypted ClientHello, certificate compression, and post-quantum readiness, and it hands out the exact NGINX fix snippet for each finding.

Beyond fixes: post-quantum key exchange

Fixing decade-old attacks brings a server to the present. The next step protects it against harvest-now-decrypt-later collection: traffic recorded today gets decrypted years later, once quantum computers can break X25519 and RSA. The defense is hybrid post-quantum key exchange, X25519MLKEM768, which Chrome and Firefox already negotiate by default.

For NGINX, post-quantum key exchange is purely an OpenSSL capability: the directive is one line, but it only works when NGINX is linked against OpenSSL 3.5 or newer.

ssl_ecdh_curve X25519MLKEM768:X25519:prime256v1;

Confirm the negotiation from any machine with an OpenSSL 3.5+ client:

openssl s_client -connect example.com:443 -tls1_3 -groups X25519MLKEM768 </dev/null 2>&1 | grep -i group

On success it prints Negotiated TLS1.3 group: X25519MLKEM768. A server without post-quantum support falls back to classical X25519 or refuses the handshake, depending on the client’s group list.

This is exactly where distribution packages fall short. Enterprise Linux 8 and 9 ship OpenSSL 3.0 or older, Ubuntu 24.04 LTS ships 3.0, and only the very newest point releases such as RHEL 10.1 have begun backporting ML-KEM support. NGINX-MOD from GetPageSpeed sidesteps the whole problem: it links against an ABI-isolated OpenSSL 3.5 build (the openssl35 package), so the same post-quantum configuration works on every supported distribution today, alongside HTTP/3 and Encrypted ClientHello. For the library-level background, including why we chose OpenSSL 3.5 LTS over AWS-LC, read aws-lc NGINX in 2026: Why we still ship quictls.

Keeping the fixes applied

Most NGINX TLS vulnerability fixes are one-line changes; keeping them applied is the harder half. Cipher regressions rarely arrive as deliberate edits. They creep in when a vhost gets copied from an old wiki page, when a load balancer terminates TLS in front of your hardened NGINX with its own defaults, or when a fresh server gets provisioned from a stale image. Each of those reintroduces the exact suites you removed, and nothing breaks visibly, so nobody notices. Treat TLS settings like code: keep them in one included snippet file rather than per-vhost copies, and rescan after every infrastructure change, not just once.

Conclusion

A TLS configuration is only as secure as its last audit. A routine package upgrade or a copy-pasted vhost can silently reintroduce the exact cipher class you just removed. GetPageSpeed Amplify runs scheduled gixy scans across every host and ties findings to live NGINX runtime metrics. Drop-in compatible with the deprecated nginx-amplify-agent (EOL January 2026).

The NGINX TLS vulnerability fixes come down to two moves: prefer ssl_protocols TLSv1.3; (or the AEAD-only TLS 1.2 list when compatibility demands it), and scope gzip off; to responses that reflect input next to secrets. Then run the SSL Test to confirm, and take the same server past the checklist into post-quantum territory with NGINX-MOD: a current NGINX with OpenSSL 3.5, X25519MLKEM768, HTTP/3, and Encrypted ClientHello on every major Linux distribution. One dnf install nginx-mod away.

D

Danila Vershinin

Founder & Lead Engineer

NGINX configuration and optimizationLinux system administrationWeb performance engineering

10+ years NGINX experience • Maintainer of GetPageSpeed RPM repository • Contributor to open-source NGINX modules

Exit mobile version