The message, exactly
This is the whole of it, printed by ssh after the key exchange completes and before you get a prompt:
** 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
It comes from a function called warn_nonpq_kex() in sshconnect.c, and it arrived in OpenSSH 10.1, released 6 October 2025, filed by the project under "Potentially-incompatible changes". The current release at the time of writing is 10.4, from 6 July 2026.
Three things about it are commonly assumed and are wrong. It is not an error and nothing was refused - the session continues normally. It is not really about your client's version, except that older clients do not print it. And the phrase "may need to be upgraded" is doing a lot of work: an unchanged, years-old server can satisfy this warning without an upgrade, while a brand-new one can trigger it.
The short version. Your client negotiated a key exchange with the server, the pair settled on a classical algorithm, and your client is telling you so. Fix it by making the server offer a post-quantum algorithm. If you cannot change the server, the honest options are to accept the risk or to stop sending secrets over that link - not to silence the message and call it done.
The three conditions, in the order sshconnect.c checks them. Gate 1 is the one that surprises people.
The condition that hides the warning
The call site is four lines, immediately after the key exchange finishes:
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();
Read the first condition again: !options.kex_algorithms_set. If you have set KexAlgorithms anywhere that applies to this host, the warning never prints - regardless of what you set it to. Pin your client to a purely classical list and OpenSSH says nothing at all.
This matters more than it sounds, because pinning KexAlgorithms is exactly what a decade of hardening guides told people to do. A ssh_config containing a curated 2018-era list - typically curve25519-sha256 and a couple of diffie-hellman-group-exchange entries - is both quantum-vulnerable and permanently silent about it. The people most likely to have hardened their config are the least likely to be told.
The reasoning is defensible: if you told OpenSSH exactly which algorithms to use, it assumes you meant it and does not second-guess you. It is still worth knowing that a quiet terminal is not evidence of a post-quantum connection.
If you pin KexAlgorithms, audit it by hand. The warning will not do it for you. The command in the next section reports what was actually negotiated and does not care how the list was built.
Four names count as post-quantum, not two
Most write-ups on this warning name two algorithms. The table that kex_is_pq_from_name() consults flags four entries, and the lookup is a plain string match - a name it does not recognise is treated as not post-quantum.
| Name as it appears on the wire | Available from | Notes |
|---|---|---|
| mlkem768x25519-sha256 | OpenSSH 9.9 | Default since 10.0. ML-KEM hybridised with X25519. |
| mlkem768nistp256-sha256 | OpenSSH 9.9 | Same ML-KEM, NIST P-256 instead of X25519. |
| sntrup761x25519-sha512 | OpenSSH 9.0 | The current spelling of the older scheme. |
| sntrup761x25519-sha512@openssh.com | OpenSSH 9.0 | The legacy spelling of the same thing, and still what many servers advertise. |
The last row is worth pausing on, because it is easy to state backwards. In the OpenSSH source the vendor-namespaced @openssh.com form is the one marked as old, and the bare sntrup761x25519-sha512 is the current name. Both are flagged post-quantum, so a server advertising only the legacy spelling is fine.
The dates behind the table, from the OpenSSH project's own page: post-quantum key agreement has been offered by default since 9.0, April 2022, initially as sntrup761x25519-sha512. 9.9 added mlkem768x25519-sha256, and 10.0, April 2025 made it the default. So a server running anything from April 2022 onwards, with a stock configuration, already satisfies this warning. That is the practical reason "the server may need to be upgraded" is often not the actual problem.
Why it happens to you specifically
In rough order of how often it turns out to be the cause:
- • The server has an explicit KexAlgorithms line. A hardening template, a CIS benchmark run, or a config copied between machines years ago. The server supports post-quantum algorithms perfectly well and has been told not to offer them.
- • The server is genuinely old. Anything before OpenSSH 9.0, which includes long-lived enterprise distributions still inside their support window.
- • It is not OpenSSH at all. Dropbear on an embedded box, a router or switch management plane, a storage appliance, or a Git hosting service with its own SSH implementation. These move on their own schedules.
- • You are landing somewhere other than you think. A jump host, a bastion, a container, or a port-forward can put a different SSH implementation in front of the machine you have in mind. The warning describes the hop your client actually negotiated with.
Note what is not on that list: your own client's version. A newer client does not make a connection weaker, it just tells you about a weakness that was already there. If the warning appeared the day you upgraded your laptop, the connection was equally exposed the day before.
Check what the server actually offers
Two commands, both of which report facts rather than configuration. The first shows what was negotiated and what the far end proposed, in one connection:
# what got negotiated, and everything the server was willing to use
ssh -vv user@host exit 2>&1 | grep -E 'kex: algorithm|peer server KEXINIT' -A1
Run against the machine this article was written on, that prints:
debug1: kex: algorithm: sntrup761x25519-sha512@openssh.com
debug2: peer server KEXINIT proposal
debug2: KEX algorithms: sntrup761x25519-sha512@openssh.com,curve25519-sha256,
curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,
ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,...
The negotiated name is on the kex: algorithm line. Compare it against the four-row table above; that is the entire test. This box runs OpenSSH_9.6p1 and lands on the legacy sntrup761x25519 spelling, which is post-quantum - so a 10.1 client connecting here gets no warning from a server two years older than the warning itself.
The second command is for when you have no account on the machine, or do not want to complete a connection. Ask for a key exchange the server certainly does not have, and the failure enumerates its entire offer, before authentication:
ssh -o KexAlgorithms=diffie-hellman-group-exchange-sha1 user@host
Unable to negotiate with 192.0.2.10 port 22: no matching key exchange
method found. Their offer: sntrup761x25519-sha512@openssh.com,
curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,...
"Their offer" is the server's real list. Two caveats, both learned the annoying way. The old name has to be one your client still recognises, or ssh rejects the option locally and never contacts the server at all - the error then reads Unsupported KEX algorithm, which looks similar and means something completely different. And if the server does happen to accept the deliberately-weak algorithm you asked for, you have just made a weak connection rather than learned anything; pick something safely obsolete.
To see what your own client is capable of offering, independent of any config: ssh -Q kex.
Fixing the server
If the server is OpenSSH 9.0 or newer and has no explicit KexAlgorithms line, there is nothing to do - it already offers a post-quantum algorithm. Check before you edit:
# version, and the effective list after all config files are merged
sshd -V
sshd -T | grep -i '^kexalgorithms'
If a hardening template has pinned the list, the smallest correct change is to prepend the post-quantum names rather than replace the line, which is what the ^ prefix does:
# /etc/ssh/sshd_config - OpenSSH 9.9 and newer
KexAlgorithms ^mlkem768x25519-sha256,sntrup761x25519-sha512@openssh.com
# /etc/ssh/sshd_config - OpenSSH 9.0 to 9.8, which has no mlkem
KexAlgorithms ^sntrup761x25519-sha512@openssh.com
Naming an algorithm the build does not have stops sshd from starting. It is a fatal config error, not a warning, and the ^ prefix does not soften it. On the 9.6 box here:
$ sshd -T -o 'KexAlgorithms=^mlkem768x25519-sha256'
Unsupported KEX algorithm "mlkem768x25519-sha256"
command-line line 0: Bad SSH2 KexAlgorithms '^mlkem768x25519-sha256'.
Which gives the safe order of operations for a machine you reach over the network and cannot walk to:
- • Edit the config, then validate with sshd -t and read back the merged result with sshd -T | grep -i kexalgorithms. A bad line fails here, harmlessly.
- • Keep your current session open. Restart the daemon from it.
- • Open a second session before closing the first. If the restart broke something, the session you are still holding is what saves you.
Worth saying while you are in sshd_config: the key exchange decides whether a recorded session can be read later, and it does nothing about who can reach port 22 in the first place. Those are separate problems with separate fixes - keeping SSH off the public internet entirely is the other half.
For anything that is not OpenSSH - appliances, routers, embedded Dropbear, hosted Git - there is no local fix. Check the vendor's release notes for post-quantum key agreement, and until it arrives, treat those sessions as recordable.
The client side, including how to turn it off
If a server cannot be fixed and you have accepted that, OpenSSH 10.1 added a dedicated option. WarnWeakCrypto takes a flag list, and the post-quantum warning has its own flag, so you can silence exactly this one and keep every future weak-crypto warning:
# ~/.ssh/config - silence it for one host only
Host legacy-appliance.internal
WarnWeakCrypto no-pq-kex
The default is yes, meaning all warnings enabled. Setting it to no disables all of them, including ones OpenSSH has not shipped yet - the release notes say plainly that the option "is likely to control additional weak crypto warnings in the future". Scoping it to a Host block and using the specific no-pq-kex flag costs one extra word and does not blind you everywhere else.
What is not a fix, despite being widely suggested: setting KexAlgorithms on the client to make the message go away. It does work, for the reason covered above - but it suppresses the check for that host permanently, including on the day the server is finally fixed and would have started passing. You lose the signal instead of the weakness.
The same transition, one layer over: DoH and TLS
SSH is the noisy case because OpenSSH decided to print a warning. The identical migration has been running quietly in TLS, which is what carries every encrypted DNS transport, your browsing, and everything else on 443. The mechanics match: a hybrid key exchange that runs ML-KEM alongside a classical curve, so the result is no weaker than the curve alone even if the lattice scheme is later broken. The reasoning behind hybrids is covered in harvest now, decrypt later.
In TLS the name is X25519MLKEM768 - the same ML-KEM-768 as SSH's mlkem768x25519-sha256, the same X25519, a different protocol wrapping them. Browsers turned it on by default through 2024 and 2025, which is why most of the web moved without anyone seeing a message.
There is no warning to wait for, so you have to ask. The reliable test is to offer only the post-quantum group and see whether the handshake completes:
# success means the server supports it; a TLS error means it does not
curl -sI --tlsv1.3 --curves X25519MLKEM768 https://example.com/
Against this site's DoH endpoint that returns HTTP/2 200. To confirm the flag is really restricting the offer rather than being ignored - a control worth running once, on any tool you trust with a claim like this - ask for a group the server does not have. Against the same endpoint, X25519, P-256 and P-384 all return 200, while P-521 and ffdhe2048 both fail with curl exit 35. The flag restricts, so the success means what it appears to.
Your TLS tool can misreport the server. The openssl shipped with Ubuntu 24.04 is 3.0, which has no ML-KEM support at all. Point it at an endpoint that does offer the post-quantum group and it negotiates the classical one and reports exactly that:
$ echo | openssl s_client -connect dnsdoh.art:443 -tls1_3 2>/dev/null | grep 'Temp Key'
Server Temp Key: X25519, 253 bits
Nothing is wrong with the server. A key exchange is a negotiation, and a client that cannot do ML-KEM will never see it offered. The same logic applies to the SSH warning in reverse: an old client stays silent on a connection a new one would flag. Check your client's capability before you conclude anything about the far end.
What this does not cover
Key exchange is the urgent half because it is the half exposed to "store now, decrypt later": an attacker who records a session today needs only a future quantum computer to derive the session keys and read it. Nothing about that attack requires them to act while you are connected.
Authentication is the other half, and it is not urgent in the same way. Your SSH host keys and user keys are still Ed25519 or RSA, and a recorded handshake cannot be retroactively forged - breaking a signature only helps an attacker who is impersonating a party in real time, at a point when the compromise would be detectable. That is why the transition order is key agreement first.
It is genuinely in progress, though. OpenSSH 10.4, from July 2026, added experimental support for a composite signature scheme pairing ML-DSA-44 with Ed25519. It is not enabled by default and has to be named explicitly in HostKeyAlgorithms and PubkeyAcceptedAlgorithms. Treat it as something to read about, not to deploy on a machine you need to log into tomorrow.
The general lesson
A warning that fires on a default configuration and goes quiet on a customised one will systematically under-report on exactly the machines whose owners took security seriously enough to customise. That is not unique to OpenSSH; it is a property of any check that treats explicit configuration as consent. When you pin a cryptographic list anywhere - SSH, TLS, a web server, a VPN - you are also opting out of whatever future advice the software would have given you about that list.
The practical consequence is small and worth doing: every pinned algorithm list needs a date attached to it and a reason to be re-read. A list that was good practice in 2018 is the reason a terminal is silent in 2026.