DEV Community

Cover image for Is Your SSH Connection Actually Post-Quantum?
Mustafa ERBAY
Mustafa ERBAY

Posted on Originally published at mustafaerbay.com.tr

Is Your SSH Connection Actually Post-Quantum?

I felt comfortable about post-quantum cryptography, and I had a reason to. OpenSSH has made hybrid post-quantum key exchange the default since version 9.0, released in April 2022. In other words, I was protected without doing anything, simply by keeping my packages up to date. That is a rare luxury in systems administration — security that arrives for free.

This morning, for a completely unrelated reason, I measured it. My comfort lasted fifteen seconds.

The session connecting to my own server was negotiating ecdh-sha2-nistp256. Not post-quantum, not hybrid — classical elliptic-curve Diffie-Hellman, which entered SSH with RFC 5656 back in 2009. And the warning OpenSSH added for exactly this situation, which was enabled on my system, had not said a word.

This piece is the story of chasing those fifteen seconds. Three things came out of it: measuring is itself full of traps, the warning has a reason to stay silent, and trusting the default breaks at a very specific point. I have written before about why the quantum threat matters; here I am after a single question: which algorithm is your connection using right now?

You have to learn how to measure first

My first attempt showed nothing at all. I ran ssh -vv server, fifteen lines scrolled past, and not one of them mentioned key exchange:

debug1: auto-mux: Trying existing master at '/Users/.../.ssh/cm-root@...'
debug2: mux_client_hello_exchange: master version 4
debug1: mux_client_request_session: master session id: 2
Enter fullscreen mode Exit fullscreen mode

The reason is embarrassingly simple. Years ago I had put ControlMaster auto and ControlPersist 15m into my own ~/.ssh/config — the alerting on that server sent a mail for every new login, and reusing the connection saved my inbox. The side effect is this: a second session opened within fifteen minutes performs no new key exchange. It rides through the existing tunnel. The thing you are trying to measure is not happening.

The correct way is to disable multiplexing:

ssh -S none -v server exit 2>&1 | grep "kex: algorithm"
Enter fullscreen mode Exit fullscreen mode

The line that came back on my machine:

debug1: kex: algorithm: ecdh-sha2-nistp256
Enter fullscreen mode Exit fullscreen mode

You also need to see what the server offered, because the client is not always the guilty party. For that, look at the peer server KEXINIT proposal block in the -vv output:

debug2: peer server KEXINIT proposal
debug2: KEX algorithms: sntrup761x25519-sha512@openssh.com,curve25519-sha256,...
Enter fullscreen mode Exit fullscreen mode

This is where it got interesting. My server was offering a post-quantum algorithm. My client had not taken it.

The culprit: a third config file nobody opens

The command ssh -G server prints the settings that actually apply to that connection once every config file has been merged. Mine said:

kexalgorithms ecdh-sha2-nistp256,mlkem768x25519-sha256,sntrup761x25519-sha512,...
Enter fullscreen mode Exit fullscreen mode

ecdh-sha2-nistp256 sits at the head of the list. Key exchange selection in SSH is a preference order: the algorithm that comes first in the client's list and also exists on the server wins. The post-quantum options were there; they had simply joined the queue from the back.

I had not written that. macOS ships /etc/ssh/ssh_config.d/100-macos.conf, which includes /etc/ssh/crypto.conf, and that file contains exactly this:

# Default algorithms favoring higher-performance FIPS algorithms
# in most cases.
#
# To configure these SSH algorithms, run the following command:
#
#   sudo ln -fs crypto/apple.conf /etc/ssh/crypto.conf
#
Ciphers ^aes128-gcm@openssh.com,aes256-gcm@openssh.com
KexAlgorithms ^ecdh-sha2-nistp256
MACs ^hmac-sha2-256-etm@openssh.com,hmac-sha2-256
Enter fullscreen mode Exit fullscreen mode

In OpenSSH, the ^ prefix means "prepend this to the front of the default list". The comment is honest about the intent: performance and FIPS alignment. A defensible choice. The consequence, though, is that a Mac out of the box falls back to a classical key exchange even when the server across from it can speak post-quantum. And it does not tell you.

I also looked in the /etc/ssh/crypto/ directory the comment points at. It holds two profiles: apple.conf (the contents above) and fips.conf. The second is worse — it says KexAlgorithms ecdh-sha2-nistp256, without the ^, replacing the list outright. So neither of the two options Apple ships restores OpenSSH's own ordering.

This is not peculiar to my setup. Enterprise environments are full of config fragments doing the same job: Ansible roles that pin KexAlgorithms to pass a compliance audit, fragments of ssh_config copied from a CIS guide five years ago, lists narrowed down because "we left only the secure ones". Every one of them was correct on the day it was written. The problem is that algorithm lists age.

Why the warning stays quiet

OpenSSH 10.1 added a warning for precisely this case. A client that settles on a classical key exchange prints:

** 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
Enter fullscreen mode Exit fullscreen mode

Note the third line: the warning puts the blame on the server. In my case the client was the culprit.

Mine printed nothing. My client is 10.3, ssh -G reports warnweakcrypto yes, and the negotiated algorithm is classical. All three conditions hold, and still no warning. At that point there was nothing left but to read the source. Here is the condition from sshconnect.c in OpenSSH 10.5:

if (!options.kex_algorithms_set && ssh->kex != NULL &&
    ssh->kex->name != NULL && options.warn_weak_crypto &&
    !kex_is_pq_from_name(ssh->kex->name))
        warn_nonpq_kex();
Enter fullscreen mode Exit fullscreen mode

The first term explains everything: !options.kex_algorithms_set. In readconf.c, that flag is raised the moment any KexAlgorithms line is seen in the config — including the ^, + and - prefixed forms. OpenSSH's reasoning is defensible: if you touched the algorithm list, you are assumed to know what you are doing, and nobody lectures you.

In practice, though, this produces a perfect blind spot. The setup that most needs the warning — the one whose algorithm list has been tampered with — is the only setup the warning never reaches. I neither wrote that file nor ever looked at it; my operating system said "I know what I'm doing" on my behalf.

There is also a scope issue: WarnWeakCrypto lives on the client side only. There is not a single trace of the setting in OpenSSH 10.5's servconf.c — a server that settles on a classical key exchange with its peer tells nobody. If you run servers, do not wait for this warning; it will never reach you.

The lesson: the absence of this warning is not a guarantee. Silence can mean two different things, and they are opposites.

The server side: good, but uneven

After fixing my client I turned to the server. I tried to connect while forcing mlkem768x25519-sha256:

Unable to negotiate with ... no matching key exchange method found.
Their offer: sntrup761x25519-sha512@openssh.com,curve25519-sha256,...
Enter fullscreen mode Exit fullscreen mode

My server runs Ubuntu 24.04 LTS with OpenSSH 9.6p1. That version knows sntrup761x25519-sha512@openssh.com but not mlkem768x25519-sha256 — the ML-KEM based method landed in OpenSSH 9.9 (September 2024) and became the default in 10.0 (April 2025). So my server is not unprotected; it is simply using the previous generation of post-quantum algorithm. Miss that distinction and you panic for nothing.

The @openssh.com suffix is a trap of its own. The IANA-assigned plain name for the same algorithm — sntrup761x25519-sha512 — only reached OpenSSH in 9.9. Write the plain name into your config and the line falls flat against any pre-9.9 peer. In a mixed fleet you need both.

Roughly, the distribution picture looks like this:

Distribution OpenSSH Post-quantum status
Debian 11 (bullseye) 8.4p1 Effectively none — see the note below
Debian 12 (bookworm) 9.2p1 sntrup761 by default
Ubuntu 24.04 LTS 9.6p1 sntrup761 by default, no ML-KEM
Debian 13 (trixie) 10.0p1 ML-KEM by default
Ubuntu 26.04 LTS 10.2p1 ML-KEM by default

The Debian 11 row is a story of its own, and it lands squarely on this article's theme. 8.4p1 does in fact contain a post-quantum method: OpenSSH 8.0 added an experimental hybrid called sntrup4591761x25519-sha512@tinyssh.org back in 2019, and I checked the source — it is still there in 8.4's kex.h. But 8.5 (March 2021) dropped that method and replaced it with today's sntrup761x25519-sha512@openssh.com, because its designers had long since declared the older parameter set superseded.

The upshot: your Debian 11 server does show a post-quantum option in its list, but no client it meets today has a counterpart for it. The capability is there; the common ground is not. So if the traffic of a session you open to those servers today is recorded, it is open to being decrypted later. The whole "store now, decrypt later" attack rests on that sentence, and how long today's decision survives depends on how sensitive the data is.

Rather than counting ticked boxes, keep two thresholds in mind: 8.9, the release where post-quantum entered the default list, and 9.9, the release that brought ML-KEM. The 8.5–8.8 range in between knows the algorithm but does not enable it by default.

One point worth making clear: this is no longer a draft

The era in which these algorithms were "experimental" is over, and that is precisely this year's news. sntrup761x25519-sha512 was published as RFC 9941 in April 2026. The ML-KEM based hybrid methods — including mlkem768x25519-sha256 — were documented in RFC 10042 in August 2026. In IANA's SSH parameters registry, both are now marked at the SHOULD level.

That sentence should not be read as more than it is. Both RFCs are Informational; neither is on the standards track. And the SHOULD marking in IANA is not a preference ranking but an "OK to implement" note — curve25519-sha256 and ecdh-sha2-nistp256 carry exactly the same marking. What changed is not where these algorithms sit in the hierarchy; it is that there is now a citable document behind them rather than a draft.

A small detail, but an important one: OpenSSH's own post-quantum page still describes these algorithms as "being standardised". Documentation moves at a different speed than RFC publication; that happens. Make your decision based on the RFC number.

The standards side also supplies a timeline. NIST's draft IR 8547 (November 2024, still a draft) keeps two separate rows for elliptic-curve Diffie-Hellman: those at the 112-bit security level are proposed as deprecated after 2030 and disallowed after 2035; those at 128 bits and above go straight to disallowed after 2035. So the ecdh-sha2-nistp256 I was connecting with sits in the second row — the door closes in 2035, not 2030. A sentence in the same document's introduction was more useful to me: historically, the journey from algorithm standardization to full integration into information systems takes 10 to 20 years. If 2035 looks far away, think about where those ten years go.

What about signatures?

Once the key exchange is fixed, host keys are the reasonable next thought. In my session, the signature algorithms the server offered were rsa-sha2-512, rsa-sha2-256, ecdsa-sha2-nistp256 and ssh-ed25519. All classical. All breakable by a sufficiently powerful quantum computer.

There is no need to panic here, because the threat model is different. Recording a signature today and breaking it ten years from now buys you nothing — that session ended long ago. The urgency with signatures is retiring classical keys before a quantum computer arrives. So it is not a "do it today" job; it is an inventory and planning job.

OpenSSH is on that road, but early on it. Version 10.1 removed experimental XMSS support; 10.4 (July 2026) added a composite signature scheme combining ML-DSA-44 with Ed25519. It is not enabled by default, you have to turn it on by hand, and it is experimental in name as well as nature. I see no reason to switch a production host key to it today. But if you run a setup built on short-lived certificates — like the one in my OpenSSH CA piece — changing algorithms will cost you far less. That was not on my mind while writing it; looking back, I now think it is the best thing about that design.

What to do with your own setup

Diagram

In order, the things to do:

  1. Measure with multiplexing off. Any result you find without ssh -S none -v is the result of a past session. A long-lived session does rekey along the way, but it keeps the same algorithm — so the measurement belongs to the moment the connection was established.
  2. Run ssh -G server | grep -i kexalgorithms. The first element of the list is the closest candidate to winning on that connection. If a classical algorithm sits at the front, that is your finding.
  3. Hunt down pinned lists — but read the ssh -v output before you go looking by hand. The client prints every config file it reads, includes and all, as debug1: Reading configuration data .... My /etc/ssh/crypto.conf was named right there in plain sight; I had simply never read it. After that, go through your Ansible roles and golden images.
  4. Put the fix in your own config. The client reads ~/.ssh/config before the system files, and the first value obtained for a setting wins. So one line overrides the system file without needing root:
   Host *
       KexAlgorithms ^mlkem768x25519-sha256,sntrup761x25519-sha512@openssh.com,sntrup761x25519-sha512
Enter fullscreen mode Exit fullscreen mode

After that line, my ssh -G output started with mlkem768x25519-sha256 instead of ecdh-sha2-nistp256. Because you used ^, nothing breaks with older peers either: the list is unchanged, you have only moved to the front of the queue. In return you silence the warning again — but at least the silence will be earned.

  1. Inventory your servers by version. On the server, sshd -T | grep -i kexalgorithms gives you the effective list. The thresholds: anything below 8.9 does not enable post-quantum by default, anything below 8.5 ships an old method nobody speaks any more, and anything below 9.9 does not know ML-KEM. Build your upgrade plan around those.
  2. If you have to disable the warning, be surgical. Instead of WarnWeakCrypto no, which silences everything, use WarnWeakCrypto no-pq-kex under a Match host old.example.com block for just that host.

And there is one thing not to do: pulling your client backwards for a server you cannot upgrade. Turning off the warning records the risk; downgrading the algorithm enlarges it.

Closing

The real subject of this piece is not post-quantum cryptography, I think. It is defaults.

OpenSSH did this right: four years ago it quietly turned the protection on, and then added a warning that speaks up when it breaks. I did my part as a user and kept my versions current. Even so, for four years I connected with a classical key exchange, because one of the layers in between — a file I never wrote and never opened — inserted its own priority, and in doing so closed the warning mechanism along with it.

Black boxes get through their worst nights without telling anyone. What we trust in security defaults is usually not the code itself but the assumption that the code reached our environment intact. Verifying that assumption takes fifteen seconds. Run it once on your own machine today; either you will be reassured, or you will lose a morning like I did. Both are a good deal.

Official Sources

Top comments (0)