Post-quantum and TLS

Post-quantum SSH with mlkem768x25519, the secure way

Your SSH sessions carry root passwords, database dumps and deploy keys, and someone may be recording them today to read later. The fix is one line in sshd_config, but only if you know which line and what it breaks.

The short answer

Run OpenSSH 9.9 or newer on both sides; from 10.0 the default key exchange is mlkem768x25519-sha256, a hybrid of ML-KEM-768 and X25519. On servers, set KexAlgorithms to the ML-KEM hybrid plus both names of the sntrup761 hybrid, so no session falls back to classical ECDH. Verify with ssh -v and read the kex line.

Updated Houssam Hammoudi, CTOTested with OpenSSH 10.0p2 (alpine:3.22), OpenSSH 10.2p1 (alpine:3.23)

On this page
  1. What goes wrong
  2. What the docs say
  3. The secure configuration
  4. Prove it
  5. Mistakes people make
  6. Checklist

What goes wrong

SSH agrees on a key exchange before anything else. With classical curve25519-sha256 or ECDH over NIST curves, an attacker who records the session today can decrypt it once a large quantum computer exists. SSH sessions are a good target for that: they carry credentials, secrets pasted into a terminal and whole file transfers.

OpenSSH added the hybrid mlkem768x25519-sha256 in 9.9 and made it the default in 10.0. But the default is only a preference. A server built before 9.9, a network appliance, a Git host or a hardened config with an old KexAlgorithms line pulls every client back to classical ECDH, silently.

Many hardening guides written before 2025 pin KexAlgorithms to curve25519-sha256 and nothing else. Copying one of those onto a new server turns off the post-quantum default.

What the docs say

More recently, in OpenSSH 9.9, we have added a second post-quantum key agreement mlkem768x25519-sha256 and it was made the new default scheme in OpenSSH 10.0 (April 2025).

Source: OpenSSH, Post-Quantum Cryptography

The ideal solution is to update the server to use an SSH implementation that supports at least one of these.

Source: OpenSSH, Post-Quantum Cryptography

This warning may be controlled via a new WarnWeakCrypto ssh_config option, defaulting to on.

Source: OpenSSH release notes, 10.1

The OpenSSH page tells you to upgrade servers. It does not tell you that a pinned KexAlgorithms line from an old hardening guide overrides the new default on an up-to-date server.

The secure configuration

Server, /etc/ssh/sshd_config.d/10-kex.conf (OpenSSH 9.9 or newer):

conf
# Only hybrid post-quantum key exchange. mlkem768x25519-sha256 combines
# ML-KEM-768 with X25519. sntrup761x25519-sha512 is the older hybrid; OpenSSH
# 9.0 to 9.8 clients know it only by its @openssh.com name, so list both.
# No classical-only algorithm remains, so a session can never fall back to
# curve25519-sha256 or ecdh-sha2-*.
KexAlgorithms mlkem768x25519-sha256,sntrup761x25519-sha512,[email protected]

Apply and check the effective value:

bash
sshd -t                                  # syntax check before reload
systemctl reload ssh                     # or sshd, depending on the distribution
sshd -T | grep -i '^kexalgorithms'       # what the server really uses

Client, ~/.ssh/config or /etc/ssh/ssh_config.d/10-kex.conf:

conf
Host *
    # Same list on the client: refuse to talk classical to anything.
    # Remove this for hosts you do not control and cannot upgrade, or
    # scope it with a Host pattern.
    KexAlgorithms mlkem768x25519-sha256,sntrup761x25519-sha512,[email protected]
    # OpenSSH 10.1+: keep the warning for non post-quantum sessions (default on).
    WarnWeakCrypto yes

If you manage a fleet, set the server side first. Clients from OpenSSH 9.0 on support the sntrup761 hybrid under its @openssh.com name; 9.9 and newer also support mlkem768x25519-sha256.

Prove it

The test in secure-tests/post-quantum-ssh-mlkem768x25519/run.sh runs an OpenSSH 10.0p2 server and connects with ssh -v. No login is needed: the key exchange finishes before authentication.

Server defaults and a default client:

bash
sshd -T | grep -i '^kexalgorithms'
ssh -v -p 2222 probe@server true 2>&1 | grep 'kex: algorithm'
text
kexalgorithms mlkem768x25519-sha256
  sntrup761x25519-sha512
  [email protected]
  curve25519-sha256
  [email protected]
  ecdh-sha2-nistp256
  ecdh-sha2-nistp384
  ecdh-sha2-nistp521
kex: algorithm: mlkem768x25519-sha256

The defaults still list classical algorithms after the hybrids. A server with an old pinned list shows what happens next. An OpenSSH 10.2 client warns:

text
kex: algorithm: curve25519-sha256
** WARNING: connection is not using a post-quantum key exchange algorithm.
** This session may be vulnerable to "store now, decrypt later" attacks.
** The server may need to be upgraded. See https://openssh.com/pq.html

After the hardened KexAlgorithms line, a default client gets the hybrid and a classical-only client is refused:

text
-- client with defaults:
kex: algorithm: mlkem768x25519-sha256
-- client that only offers curve25519-sha256:
kex: algorithm: (no match)
Unable to negotiate with 127.0.0.1 port 2222: no matching key exchange method found. Their offer: mlkem768x25519-sha256,sntrup761x25519-sha512,[email protected],ext-info-s,[email protected]

To check a host you already run, from a client with OpenSSH 9.9 or newer:

bash
ssh -v [email protected] true 2>&1 | grep 'kex: algorithm'

Mistakes people make

Copying a 2023 hardening guide

Guides from before OpenSSH 9.9 often pin KexAlgorithms curve25519-sha256. On a new server that line removes the post-quantum default. Search your config management and sshd_config.d for KexAlgorithms before you trust the version number.

Checking the version instead of the session

A server can run OpenSSH 10 and still negotiate classical ECDH because of a pinned list, or because the client is old. Only the kex: algorithm line of ssh -v tells you what a session used.

Turning off the warning to make it quiet

WarnWeakCrypto no hides exactly the sessions you need to find. Leave it on and fix or list the hosts that trigger it.

Forgetting the machines that are not Linux servers

Git hosting, routers, switches, BMCs and jump hosts on appliances run their own SSH stacks. Many do not offer any hybrid yet. List them, because a classical-only jump host exposes every session that passes through it.

Locking out old clients by surprise

A client older than OpenSSH 9.0 supports neither hybrid and cannot connect to the hardened server. Check the client versions in your team and in your automation (CI runners, backup tools) before you roll out the server line.

Checklist

  • Upgrade servers and clients to OpenSSH 9.9 or newer.
  • Search every sshd_config and include file for a pinned KexAlgorithms.
  • Set KexAlgorithms mlkem768x25519-sha256,sntrup761x25519-sha512,[email protected] on servers.
  • Run sshd -t before reloading, and sshd -T after, to see the effective list.
  • Check a real session with ssh -v ... | grep 'kex: algorithm'.
  • Keep WarnWeakCrypto on and follow up every warning.
  • Inventory appliances, Git hosts and jump hosts for hybrid support.

One `KexAlgorithms` line, and "store now, decrypt later" goes home empty handed.

FND

Learn it on a live range

Linux 3: securing Linux, in Foundation: a real host in your browser, and every objective checked on the machine.

Start free

The Dome

Want it run for you?

The Dome puts post-quantum TLS, a WAF that blocks, signed DNS and a zero-trust mesh in front of your application. Tell us what you run.

See the Dome