Post-quantum TLS.
PQ-TLS means running a TLS 1.3 handshake where the key exchange step uses a hybrid algorithm — a classical ECDH component combined with an ML-KEM component. Either component alone is sufficient to protect the session key; an attacker must break both to recover it.
This page covers the TLS mechanics. For the hybrid construction itself — key sizes, the combiner pseudocode, and the broader use case outside TLS — see hybrid key exchange.
Why TLS 1.3 specifically
Hybrid key exchange requires the TLS 1.3 key share extension (RFC 8446 §4.2.8). TLS 1.2 has no equivalent mechanism — it negotiates a key exchange algorithm by name but cannot carry an arbitrary-length key share in the same handshake. PQ-TLS therefore requires TLS 1.3 on both ends.
Named groups
The three hybrid groups in active deployment are registered in draft-ietf-tls-ecdhe-mlkem (an IETF draft; not yet an RFC as of mid-2026).
Name Group ID Classical PQ Security X25519MLKEM768 0x11EC X25519 ML-KEM-768 ~128-bit classical + NIST Level 3 SecP256r1MLKEM768 0x11EB ECDH P-256 ML-KEM-768 P-256 (FIPS-approved curve) + Level 3 SecP384r1MLKEM1024 0x11ED ECDH P-384 ML-KEM-1024 P-384 (FIPS-approved curve) + Level 5
X25519MLKEM768 is the browser default. The P-256 and P-384 variants use NIST-approved curves and are preferred in environments that restrict the classical component to FIPS-approved curves.
Key share sizes on the wire
| Group | Client key share | Server key share (response) | On wire (server, incl. 4 B header) |
|---|---|---|---|
X25519MLKEM768 |
32 + 1,184 = 1,216 B | 32 (X25519) + 1,088 (ML-KEM ciphertext) = 1,120 B | 1,124 B |
SecP256r1MLKEM768 |
65 + 1,184 = 1,249 B | 65 + 1,088 = 1,153 B | 1,157 B |
SecP384r1MLKEM1024 |
97 + 1,568 = 1,665 B | 97 + 1,568 = 1,665 B | 1,669 B |
The +4 B header is the TLS extension type (2 B) and
length (2 B) fields. The diagnostic line
TLS server extension "key share" (id=51), len=1124
from -tlsextdebug confirms X25519MLKEM768 was
negotiated; len=32 means classical-only.
Handshake flow
The hybrid key exchange fits entirely within the standard TLS 1.3 handshake — no extra round trips.
Client Server
| |
|--- ClientHello (key_share: MLKEM group) -->|
| supported_groups includes 0x11EC |
| key_share carries 1,216 B |
| |
|<-- ServerHello (key_share response) -------|
| chosen group: X25519MLKEM768 |
| server key share: 1,120 B |
| (X25519 ephemeral + ML-KEM ciphertext) |
| |
| Both sides derive: |
| x25519_ss from the X25519 exchange |
| mlkem_ss from ML-KEM Encaps/Decaps |
| combined = HKDF(mlkem_ss || x25519_ss)|
| |
|<-- {EncryptedExtensions, Certificate, |
| CertificateVerify, Finished} ----------|
|--- {Finished} ---------------------------->|
| |
|<=== Application data (TLS_AES_256_GCM) ===>|
The shared secret concatenation order for
X25519MLKEM768 per draft-ietf-tls-ecdhe-mlkem is
mlkem_ss || classical_ss. The server certificate uses
a classical signature algorithm for authentication; hybrid key
exchange protects only the session key derivation.
Local testing with OpenSSL
The most reliable way to verify your OpenSSL installation supports hybrid groups is a local loopback test — no external server required. OpenSSL 3.5+ includes native ML-KEM support in its default provider. For older OpenSSL 3.x, install the OQS provider (see below).
Check for ML-KEM support
openssl list -kem-algorithms | grep -i mlkem openssl version
Start a local test server
# Generate a self-signed certificate for the loopback server
openssl req -x509 -newkey ML-DSA-65 -keyout key.pem -out cert.pem \
-days 365 -nodes -subj "/CN=localhost"
# Start s_server advertising X25519MLKEM768
openssl s_server -accept 14433 -cert cert.pem -key key.pem \
-groups X25519MLKEM768 -www -tls1_3
Connect from a second terminal
openssl s_client -connect localhost:14433 \
-groups X25519MLKEM768 \
-tlsextdebug </dev/null 2>&1 | grep -E "key share|Negotiated|Cipher"
A successful hybrid negotiation shows
key share (id=51), len=1124 and
Negotiated TLS1.3 group: X25519MLKEM768.
If you see len=32, the server or client fell back to
classical X25519 only.
Test all three groups
# X25519 + ML-KEM-768 (default, widely supported) openssl s_client -connect localhost:14433 -groups X25519MLKEM768 -brief # P-256 + ML-KEM-768 (FIPS-approved curve for classical component) openssl s_client -connect localhost:14433 -groups SecP256r1MLKEM768 -brief # P-384 + ML-KEM-1024 (highest security, less widely deployed) openssl s_client -connect localhost:14433 -groups SecP384r1MLKEM1024 -brief
Third-party public endpoints
Cloudflare operates a public PQ research endpoint at pq.cloudflareresearch.com that can be used to test client-side hybrid support against a production server. Output will vary; do not rely on any specific response format.
OpenSSL: native vs. OQS provider
OpenSSL 3.5+ (released April 2025) includes ML-KEM, ML-DSA, and SLH-DSA natively in its default provider. No OQS provider needed for any of these algorithms if you are on 3.5 or later. The OQS provider is still useful for older OpenSSL 3.x installs or for accessing experimental algorithms not yet in OpenSSL upstream.
OpenSSL 3.5+ native ML-KEM, ML-DSA, SLH-DSA in default provider
use: openssl s_client -groups X25519MLKEM768 ...
OpenSSL 3.x requires oqs-provider for ML-KEM hybrid groups
use: OPENSSL_MODULES=/path/to/ossl-modules \
openssl s_client -provider oqsprovider -provider default \
-groups X25519MLKEM768 ...
If you need to build the OQS provider, use the latest releases (liboqs 0.15.x / oqs-provider 0.11.x as of early 2026) rather than pinning to older version tags.
Diagnosing fallback
| Symptom | Likely cause | Resolution |
|---|---|---|
no shared group |
Client or server does not recognise the hybrid group ID | Upgrade to OpenSSL 3.5+ or install the OQS provider on whichever side is older |
key share (id=51), len=32 |
Classical X25519 was negotiated instead of hybrid | Verify -groups X25519MLKEM768 on both sides; check the server config lists the hybrid group first |
| Connection refused on port | Server not started or firewall blocking the port | Confirm openssl s_server is running; use localhost for loopback tests |
| Certificate verify error | Self-signed certificate not trusted by client | Expected for loopback tests; add -CAfile cert.pem to s_client to suppress, or ignore for key-exchange testing |
| Large key shares blocked | Firewall or WAF drops packets larger than ~1 KB | Reconfigure the middlebox; hybrid shares are ~1.1–1.7 KB vs 32 B for classical X25519 |
Browser support
| Browser | Hybrid PQ-TLS | Default group | Notes |
|---|---|---|---|
| Chrome 131+ | Enabled by default | X25519MLKEM768 | Shipped final ML-KEM (FIPS 203). Chrome 124 shipped an earlier pre-standard Kyber draft — not the same codepoint. |
| Firefox 132+ | Enabled by default | X25519MLKEM768 | |
| Edge | Follows Chrome | X25519MLKEM768 | Chromium-based. |
| Safari 26+ | Enabled by default | X25519MLKEM768 |
curl PQ support depends on which TLS backend it
was compiled against (OpenSSL, GnuTLS, BoringSSL, etc.), not the
curl version number itself. Check curl --version for
the linked library.
Server configuration
To advertise hybrid groups on your server, add the hybrid group name to the curves / groups list. Placing it first signals that you prefer it, but the client's preference wins in TLS 1.3.
# Nginx (requires OpenSSL 3.5+ or OQS provider)
ssl_protocols TLSv1.3;
ssl_ecdh_curve X25519MLKEM768:X25519:P-256;
# Apache
SSLProtocol TLSv1.3
SSLOpenSSLConfCmd Groups X25519MLKEM768:X25519
# HAProxy
ssl-default-bind-ciphersuites TLS_AES_256_GCM_SHA384
ssl-default-bind-curves X25519MLKEM768:X25519
# Caddy 2.9+ (Go 1.24 crypto/tls)
tls {
curves x25519mlkem768 x25519
}
Caddy 2.9+ is required for hybrid group support;
that release moved to Go 1.24 which added the hybrid curves to
crypto/tls.
Obsolete draft groups
Before the final ML-KEM standard was published, some software
shipped provisional codepoints for pre-standard Kyber. The most
common was x25519_kyber768 (codepoint 0x6399), used by
older OQS provider releases. This group is
not interoperable with X25519MLKEM768 (0x11EC):
the codepoints differ, the KEM itself is pre-standard Kyber rather
than ML-KEM, and the share concatenation order is reversed. Do not
configure both groups on the same server expecting them to interop.
Further reading
- draft-ietf-tls-ecdhe-mlkem — the IETF draft defining X25519MLKEM768 and the other hybrid groups
- FIPS 203 — ML-KEM specification (published August 13, 2024)
- hybrid key exchange — the combiner construction, key sizes, and use outside TLS
- algorithms — ML-KEM parameter sets and security levels
- compatibility — platform support matrix