Skip to main content
Post-quantum TLS in Node.js: which versions do it and how to check
Engineering Sep 27, 2026

Post-quantum TLS in Node.js: which versions do it and how to check

Node gets its TLS from OpenSSL, and OpenSSL 3.5 turned on X25519MLKEM768 by default. That is a hybrid of classic X25519 and the ML-KEM algorithm from FIPS 203, and it protects today’s traffic against being recorded now and decrypted once a quantum computer exists. Our post-quantum docs cover what it does and does not protect, and there is a Go version of this post.

Whether your Node app gets it comes down mostly to process.versions.openssl. We tested Node 20, 22, 24 and 26 against sslboard.com, which supports hybrid key exchange, and two results surprised us.

Which Node versions do it

Node bundles OpenSSL 3.5 from 24.5.0 and 22.20.0 onward. With default settings:

NodeBundled OpenSSLKey exchange
20.203.0.19X25519
22.233.5.8X25519MLKEM768
24.213.5.8X25519MLKEM768
26.83.5.7X25519MLKEM768

Node 20 is end of life and will never get it. Servers behave the same as clients: a default Node 26 HTTPS server negotiated X25519MLKEM768 with no configuration.

Check the OpenSSL version rather than the Node version, since distribution packages may link against an older system OpenSSL:

node -p process.versions.openssl

Check what a connection negotiated

On the client, getEphemeralKeyInfo() reports the group:

import tls from "node:tls";

const socket = tls.connect(443, "api.example.com", { servername: "api.example.com" }, () => {
  console.log(socket.getEphemeralKeyInfo()); // { type: 'TLSGroup', name: 'X25519MLKEM768' }
  socket.end();
});

The first surprise: on Node 22.23 this prints {}, even though the connection is post-quantum. Node 24 and 26 report the group properly. We confirmed Node 22’s result from the server side, using Cloudflare’s trace endpoint, which works for any site behind Cloudflare:

const trace = await (await fetch("https://example.com/cdn-cgi/trace")).text();
console.log(trace.match(/^kex=.*$/m)[0]); // kex=X25519MLKEM768

Node has no server-side API for the negotiated group, so test your own server from outside with OpenSSL 3.5+:

openssl s_client -connect api.example.com:443 -servername api.example.com </dev/null 2>/dev/null | grep "Negotiated TLS1.3 group"

If that line is missing, the handshake used a classic group, and Peer Temp Key shows which one.

The ecdhCurve trap

The second surprise is the one likely to hit a real codebase. ecdhCurve, and the global tls.DEFAULT_ECDH_CURVE, default to auto, which includes the hybrid group. A list of classic curves replaces that default:

// Both negotiate X25519. No post-quantum.
https.createServer({ key, cert, ecdhCurve: "X25519:P-256" }, handler);
tls.connect(443, host, { servername: host, ecdhCurve: "X25519:P-256" });

We tested both, and setting tls.DEFAULT_ECDH_CURVE the same way. Hardening guides recommended lists like this for years, so search your code for ecdhCurve and DEFAULT_ECDH_CURVE and remove them. If you need an explicit list, put X25519MLKEM768 first. On Node 20 that makes createServer or tls.connect throw Failed to set ECDH curve, so leaving the option unset is the only setting that works everywhere.

TLS 1.2 and the other side

Hybrid key exchange is TLS 1.3 only, so maxVersion: 'TLSv1.2' rules it out. Both ends also need support. The curl that ships with macOS (LibreSSL) got plain X25519 from the same host where Node 26 got X25519MLKEM768.

What this does not cover

Your certificate is still RSA or ECDSA, and post-quantum certificates are a separate migration. If a CDN terminates TLS, clients see the CDN’s handshake, and the hop to your Node server is a separate connection.

Check every endpoint

One Node service takes a minute to check. Every public hostname you own, across runtimes, CDNs and load balancers with their own OpenSSL versions and curve lists, takes longer.

SSLBoard finds every hostname under your domain through Certificate Transparency logs, connects to each one, and reports whether it negotiates a post-quantum key exchange, with the group where it can be observed. Run a free scan and look at the post-quantum section. A host on plain X25519 is worth checking for an old OpenSSL or a pinned curve list.