
TLSRef 6.0, released 3 May 2026, dropped the Old profile and still lists no DHE-RSA ciphers on Intermediate. The same page prepends X25519MLKEM768, then X25519, prime256v1, and secp384r1. A listener that still offers EXPORT, a 512-bit prime, or a dhparam.pem you generated in 2014 is not doing Diffie-Hellman. It is offering a group the 2015 Logjam paper already retired.
That is a lecture. The control in August 2026 is a named group the client and server both already implement, on a protocol that refused RSA encryption for the premaster secret. Pair this page with the TLS guide for HSTS and ACME. Pair the path around the handshake with the MITM page. Pair the process that binds 443 with the Ubuntu host guide.
TLS 1.3 already picked (EC)DHE
RFC 8446 is TLS 1.3. The handshake no longer has a static-RSA key-exchange mode. The non-PSK agreements are ephemeral Diffie-Hellman on a named group, elliptic or finite field. The certificate still authenticates the server. It does not encrypt the session keys. That split is the whole point of this page: DH is how the two sides contribute entropy. The cert is how you know who you contributed it with.
Forward secrecy here means a stolen server private key does not decrypt yesterday’s captures, because yesterday’s ECDHE secret is gone. A TLS 1.2 suite that starts with RSA- and not ECDHE-RSA- does not have that property. Version 1.3 suites always do, unless the implementation reused a static ECDH share. You cannot configure that reuse from nginx. You can still ship a broken library. Upgrade OpenSSL with the rest of the host.
PSK without DH exists in the RFC for some resumption and embedded cases. A public website should not offer a PSK-only handshake. If you do not know you need it, you do not.
Named groups you should offer in 2026
I opened TLSRef Server-Side TLS on 22 August 2026. Version 6.0 is dated 3 May 2026. Both Modern and Intermediate list the same curve line: X25519MLKEM768, X25519, prime256v1, secp384r1. Modern is TLS 1.3 only. Intermediate keeps 1.2 for leftover clients and still uses ECDHE plus AES-GCM or ChaCha20-Poly1305. Neither profile lists P-521. The rationale on that page is speed and support, not a new break.
| Name | What it is | Offer it? |
|---|---|---|
| X25519MLKEM768 | Hybrid: X25519 plus ML-KEM-768 | Yes, first, if OpenSSL knows it |
| X25519 | ECDH on Curve25519 | Yes. Default classical pick |
| prime256v1 / P-256 | NIST secp256r1 | Yes. Broad client coverage |
| secp384r1 / P-384 | NIST P-384 | Yes. After P-256 |
| secp521r1 / P-521 | NIST P-521 | No. TLSRef dropped it |
| ffdhe2048 | RFC 7919 finite field | Only if you still need DHE |
X25519 is the function name. Curve25519 is the curve. OpenSSL, nginx, and BoringSSL all say X25519 on the wire config. Use that name in files so the next person greps the same token.
If X25519MLKEM768 is unknown to the OpenSSL on the box, I could not confirm every Ubuntu 24.04 package shipped it as of this writing. Drop that token from ssl_ecdh_curve and keep X25519:prime256v1:secp384r1. Do not disable 1.3 to paper over a group name. Upgrade the library, or leave the hybrid off until the package has it.
Drop export, 512-bit, and homemade 1024-bit
Export suites were a 1990s policy leftover. DHE-EXPORT and EXP-EDH negotiate a 512-bit prime. The 2015 Logjam work, CVE-2015-4000, showed those primes were in reach and that a downgrade into export was a real client bug. A 2026 OpenSSL build should not even compile those names into a default server cipher string. Grep for EXPORT and EXP- anyway. A copied 2012 Apache stanza will put them back.
A custom dhparam.pem you generated with openssl dhparam 1024 is the other antique. Shared 1024-bit primes were the second half of Logjam. TLSRef’s history line for 5.8 changed the leftover Old size from 1024 to 2048 and told people to use ffdhe2048 instead of a local file. Intermediate no longer needs that file at all, because DHE-RSA is gone from the cipher list after the D(HE)at resource note, CVE-2002-20001.
# BAD: any of these still in a public server stanza
# ssl_ciphers ...:EDH-RSA-DES-CBC-SHA:EXP-EDH-RSA-DES-CBC-SHA;
# ssl_dhparam /etc/ssl/dhparam-1024.pem;
# SSLOpenSSLConfCmd DHParameters /etc/ssl/dhparam-1024.pem;
Delete the 1024-bit file after you remove the directive. Leaving it on disk is how it returns in the next copy-paste. If you must keep finite field DH for one ancient client, replace the file with the RFC 7919 2048-bit group, not with openssl dhparam 2048 you generated on a laptop and never published. Named groups are how two implementations agree they are doing the same math.
Keep ffdhe2048 off the public 443
Classical Diffie-Hellman over a prime field still exists as TLS named groups ffdhe2048 through ffdhe8192 in RFC 7919. TLS 1.3 can use those names. A modern browser will prefer X25519. The cost of keeping DHE-RSA on TLS 1.2 is CPU and the D(HE)at exhaustion shape: a client can make the server do a large modular exponentiation cheaply. TLSRef Intermediate removed DHE-RSA-AES128-GCM-SHA256, DHE-RSA-AES256-GCM-SHA384, and DHE-RSA-CHACHA20-POLY1305 for that reason, and because ECDHE is enough for the clients that table still lists.
If access logs for a quarter show no DHE handshake, do not add one. If a payment switch or a Java 7 box still cannot do ECDHE, put that client on a separate listener you own, serve ffdhe2048 there, and give that listener a retirement date. Do not weaken the public 443 to keep one batch job happy.
nginx and OpenSSL lines that match TLSRef
Terminate TLS on a process you admin. The TLS page has the full Intermediate cipher string and the HSTS header. This file is the group line. nginx 1.18+ with OpenSSL 1.1.1+:
# /etc/nginx/conf.d/kex.conf
ssl_protocols TLSv1.3 TLSv1.2;
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;
ssl_ecdh_curve X25519MLKEM768:X25519:prime256v1:secp384r1;
# No ssl_dhparam. Intermediate does not need DHE-RSA.
Modern is the same file with ssl_protocols TLSv1.3; and no ssl_ciphers line. TLS 1.3 suites are not selected with that directive. If the hybrid group fails to load, the shortened curve line is:
ssl_ecdh_curve X25519:prime256v1:secp384r1;
Caddy 2 on a host you own already prefers 1.3 and X25519. Do not add a custom DH file to a Caddyfile to “match the old Apache.” The headers guide is what you add after the handshake: HSTS, CSP, X-Content-Type-Options.
OpenSSL 3 on the CLI uses -groups for the same list:
# /etc/ssl/openssl-groups.cnf (only if you own a raw openssl s_server)
# Groups = X25519MLKEM768:X25519:P-256:P-384
Identifiers stay kex.conf, ssl_ecdh_curve, and the four group tokens. Do not mix secp256r1 in one file and prime256v1 in another as if they were different knobs. In OpenSSL they are the same curve. Pick prime256v1 in nginx because that is the token TLSRef prints.
CLIENTHELLO
groups: X25519MLKEM768, X25519, P-256, P-384
LISTEN TLS 1.3 (Modern)
or TLS 1.3 + 1.2 ECDHE (Intermediate)
pick X25519 or hybrid
certificate authenticates, does not encrypt the premaster
NEVER RSA kex
EXPORT / 512-bit
ssl_dhparam 1024
DHE-RSA on the public 443
Prove the group on your own 443
You are not attacking someone else’s handshake. You are proving your listener negotiated a named group you intended, and refused a protocol you turned off.
ssl_protocolslists 1.3, and 1.2 only if you chose Intermediate.ssl_ecdh_curveis the TLSRef list or the three-name fallback. Nossl_dhparamon the public vhost.openssl s_clientagainst your own name with-tls1_3prints a group of X25519 or the hybrid, notDH, 1024 bits.- The same command with
-tls1fails. A cipher dump does not containEXPORTor a bareTLS_RSA_WITH_.
# Expect TLS 1.3 and a named group, on a listener you admin.
echo | openssl s_client -connect 127.0.0.1:443 -tls1_3 \
-servername your-app.example 2>/dev/null | \
grep -E 'Protocol|Negotiated TLS1.3 group|Server Temp Key'
# Expect this to fail. 1.0 must not negotiate.
echo | openssl s_client -connect 127.0.0.1:443 -tls1 \
-servername your-app.example
# Expect empty.
openssl ciphers -v 'ALL:eNULL' | grep -E 'EXPORT|Kx=RSA( |$)' || true
testssl.sh against a name you own is a second opinion. A finding that still mentions Logjam or export is a stanza you have not reloaded. Reload only after nginx -t.
If Server Temp Key still prints DH, 1024 bits or DH, 512 bits, you have a leftover ssl_dhparam or an old cipher list. Remove the file path, drop DHE-RSA, reload. If it prints X25519 or X25519MLKEM768, the agreement step matches this page. Cipher suite names and HSTS stay on the TLS guide. Do not paste a second copy of that nginx cipher line here and drift.
Questions we keep getting
Is RSA in the certificate a problem?
No. An RSA certificate can still sign a TLS 1.3 handshake. The obsolete part is RSA key exchange, where the client encrypts the premaster to the cert. Intermediate uses ECDHE-RSA-* on 1.2, which is ECDHE authenticated by an RSA cert. Do not confuse those names.
Do I still need a dhparam file for Perfect Forward Secrecy?
Not on Intermediate or Modern. ECDHE supplies the ephemeral secret. A dhparam file is only for finite field DHE. TLSRef no longer puts DHE-RSA on the public profile. Skip the file.
Should I turn off X25519 because it is not a NIST curve?
No. TLSRef puts it first among the classical names. Chrome, Firefox, and current OpenSSL all speak it. P-256 stays on the list for clients that do not. Disabling X25519 for a policy memo from 2014 is how you leave performance on the table without a security gain the guide still claims.



