Skip to content
← Blog

Post-quantum SSH and TLS

Key agreement is solved and shipping; signatures are not. A working guide to auditing and fixing SSH and TLS on real infrastructure.

·16 min read
  • Post-Quantum
  • SSH
  • TLS
  • Security

Post-quantum cryptography spent a decade as a conference topic and then, quietly, became the default on your laptop. If you run OpenSSH 10 and a browser released in the last two years, most of your traffic is already protected against a quantum adversary and nobody told you. The work that remains is narrow, unglamorous and very concrete: finding the connections that were left behind. This is a guide to doing that on real infrastructure — what to check, what to change, what to verify afterwards, and the one part of the problem that you should deliberately not touch yet.

Diagram contrasting post-quantum key agreement, already the default in OpenSSH and OpenSSL, with post-quantum signatures, which are not yet deployable on the public web.
Two halves of the same problem on very different schedules: key agreement is finished and deployed, signatures are still waiting for the ecosystem.

The framing that makes all of this tractable is a split. Cryptography protects your connections in two ways: key agreement, which decides the secret that encrypts the session, and signatures, which prove who you are talking to. A quantum computer breaks both. But only one of them is urgent today, and confusing the two is why so many post-quantum programmes stall in a spreadsheet. Everything below follows from that distinction.

Where post-quantum cryptography actually stands in 2026

Key agreement is done. Not planned, not piloted — shipped, defaulted and running. OpenSSH has offered post-quantum key agreement since version 9.0 in April 2022, and since OpenSSH 10.0 in April 2025 the hybrid mlkem768x25519-sha256 is the default algorithm for key agreement. OpenSSL 3.5.0, released the day before, changed its default TLS supported-groups list to include and prefer hybrid post-quantum groups, and now offers X25519MLKEM768 as a default keyshare. If you upgraded either package in the last year, you turned this on without doing anything.[ssh100][ossl35]

LayerStatus in 2026What it means for you
SSH key agreementDefault since OpenSSH 10.0 (April 2025); available since 9.0 (2022)Upgrade the package and confirm the negotiated algorithm. No other work.
TLS key agreementHybrid groups preferred by default in OpenSSL 3.5.0 (April 2025)Check the linked OpenSSL version, then one config line per terminator.
Symmetric encryptionAES-256 and ChaCha20 are considered adequateNothing to do. Grover's algorithm halves the effective key length, not more.
HashingSHA-256 and above are considered adequateNothing to do, beyond retiring SHA-1 for reasons that predate quantum computing.
Signatures and PKIStandardised (ML-DSA, SLH-DSA) but not deployable on the public webDo not migrate. Automate issuance so you can move quickly when it is possible.

The measurement side agrees. Cloudflare has been publishing adoption figures for post-quantum key agreement since 2023, and the curve went from a rounding error to the majority of human web traffic in about two years. Check the live number rather than trusting any article, including this one — the point is not the exact percentage but the shape of the curve, which is what tells you that the laggards are now a finite, enumerable set rather than the whole internet.[cfpq][cfradar]

Signatures are the opposite story. NIST standardised ML-DSA and SLH-DSA in 2024, implementations exist, and none of it is deployable on the public web, because a certificate is only useful if the relying party already trusts the root that signed it — and no post-quantum root is in the browser trust stores. This is not a temporary packaging problem you can engineer around. It is an ecosystem sequencing problem, and it will take years.

Harvest now, decrypt later — and what it does not cover

The reason key agreement is urgent and signatures are not comes down to a single asymmetry, and OpenSSH states it more plainly than most vendor material:[pq]

The entire privacy of an SSH connection depends on cryptographic key agreement. If an attacker can break the key agreement then they are able to decrypt and view the entire session. The attacker need not perform this attack in real time; they may collect encrypted SSH sessions now and then decrypt them later once they have access to a quantum computer.[pq]

That is harvest now, decrypt later, and it is a retroactive attack. A session captured on the wire today, with a classical key exchange, is a permanent liability: it sits in an archive until a cryptographically relevant quantum computer exists, and then it opens. Nothing you do in 2032 fixes a session recorded in 2026. Signatures have no equivalent, because forging a signature after the fact does not let you go back and impersonate someone in a conversation that already happened. Which gives you a clean prioritisation rule:

  • Long-lived confidential data over a long-lived link. Database replication across a WAN, backup transfers, VPN tunnels between sites, SSH sessions where you paste credentials. Fix these first — the payload keeps its value for a decade or more.
  • Traffic crossing an untrusted path. Anything leaving your own network, and in a serious threat model, anything crossing a cloud provider's backbone. Capture is cheap for an adversary with position; storage is cheaper.
  • Machine-to-machine links nobody logs into. These are the ones the audit misses, because there is no human to see a warning. Service meshes, message queues, replication channels, monitoring agents.
  • Short-lived, low-value, internal-only traffic. A health check between two pods on the same node is not worth a migration ticket. Say so in writing, so that the exception is a decision and not an oversight.

The estimates for when a cryptographically relevant quantum computer arrives range from five to twenty years, with many observers expecting the mid-2030s. You do not need to hold an opinion on that number. You need to know whether the data you are transmitting today is still sensitive when the number arrives — and for most infrastructure, the honest answer is yes.[pq]

Fixing SSH

SSH is the easy win and the right place to start, because OpenSSH has done the hard part for you. There is no plugin, no provider to install, no experimental branch. There is a version number and a configuration line, and the project publishes both.[ssh100][pq][ssh105][sshrel]

OpenSSH versionPost-quantum key agreementWhat to do
10.0 and newer (April 2025+)mlkem768x25519-sha256 by defaultNothing, but verify — a local KexAlgorithms override can still disable it.
9.9mlkem768x25519-sha256 present, not yet preferredPut it first in KexAlgorithms, or upgrade. Both algorithm names resolve here.
9.0 – 9.8sntrup761x25519-sha512 by defaultQuantum-resistant today, but only under the name sntrup761x25519-sha512@openssh.com; the short name and ML-KEM both arrived in 9.9.
8.5 – 8.9sntrup761x25519-sha512@openssh.com, but never preferredThe subtle case: on 8.9 it sits in the default list below the classical curves, so it is offered and never chosen. Put it first explicitly.
Older than 8.5NoneThis is the real exposure. Upgrade, or document the accepted risk with a date.

Two traps live in that table. The first is naming: the short name sntrup761x25519-sha512 only exists from 9.9, so on anything older you must write the @openssh.com form, and sshd refuses to start on a name it does not recognise. The second is more insidious. Being in the default list is not the same as being chosen from it — on OpenSSH 8.9 the post-quantum algorithm ships in the default KexAlgorithms but ranks sixth, below curve25519-sha256, so every modern client negotiates a classical exchange against it while the server truthfully reports post-quantum support. That is the whole reason this article keeps insisting on reading the negotiated result rather than the capability list.[ssh85][ssh99][ntru]

Step 1: find out what you are actually negotiating

Before changing anything, measure. The distinction that trips people up is between supported and negotiated: a server can support ML-KEM and still complete a classical exchange, because a stale client asked for one and the server's preference list allowed it. Only the negotiated algorithm tells you whether a given connection was actually protected.

# 1. Your client. Anything older than 8.5 has no post-quantum key agreement
#    at all; 8.5 through 9.8 have it only under the @openssh.com vendor name.
ssh -V
# OpenSSH_10.5p1, OpenSSL 3.5.7 9 Jun 2026

# 2. The remote server's software version, straight from the protocol banner.
ssh -v server.example.com exit 2>&1 | grep 'remote software version'
# debug1: Remote protocol version 2.0, remote software version OpenSSH_10.3

# 3. Which key agreement algorithms your local build even supports.
ssh -Q kex | grep -E 'mlkem|sntrup'
# mlkem768x25519-sha256
# sntrup761x25519-sha512

# 4. Supported is not preferred. On OpenSSH 8.9, for example, the PQ algorithm
#    is in the default list but sixth in it, so it is offered and never chosen:
ssh -G server.example.com | grep -i '^kexalgorithms'
# kexalgorithms curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,
# ecdh-sha2-nistp384,ecdh-sha2-nistp521,sntrup761x25519-sha512@openssh.com,...
#                                       ^ present, but nothing will ever pick it

# 5. The only answer that matters: what did THIS connection actually agree on?
#    Everything above is capability; this line is the negotiated result.
ssh -v server.example.com exit 2>&1 | sed -n 's/.*kex: algorithm: /negotiated: /p'
# negotiated: mlkem768x25519-sha256

Once you can read that line for one host, you can read it for all of them. OpenSSH 10.1 added a client-side warning when a connection negotiates a non-post-quantum exchange, which is excellent for humans and useless for the automated links that make up most of a real fleet. So sweep them yourself:[ssh101]

#!/usr/bin/env bash
# pq-ssh-sweep.sh - classify every host in hosts.txt by negotiated key agreement.
# Read-only: it opens a connection, runs `exit`, and reads the debug output.
set -uo pipefail

while read -r host; do
  [ -z "$host" ] && continue
  # </dev/null matters: without it ssh swallows the rest of hosts.txt from the
  # loop's stdin and you silently audit only the first host.
  alg=$(ssh -o BatchMode=yes -o ConnectTimeout=5 -o StrictHostKeyChecking=accept-new \
        -v "$host" exit </dev/null 2>&1 | sed -n 's/.*kex: algorithm: //p' | head -1)
  case "$alg" in
    mlkem*)   printf '%-38s PQ-ML-KEM  %s\n' "$host" "$alg" ;;
    sntrup*)  printf '%-38s PQ-LEGACY  %s\n' "$host" "$alg" ;;
    "")       printf '%-38s UNREACHED  (auth, firewall or timeout)\n' "$host" ;;
    *)        printf '%-38s CLASSICAL  %s\n' "$host" "$alg" ;;
  esac
done < hosts.txt

# PQ-ML-KEM  -> done, nothing to do.
# PQ-LEGACY  -> safe today (sntrup761 is quantum-resistant) but not the NIST
#               standard; schedule the upgrade to OpenSSH 9.9+ anyway.
# CLASSICAL  -> this is your harvest-now-decrypt-later exposure. Fix first.

Step 2: change the configuration, carefully

With the survey in hand, the change itself is small. Put it in a drop-in file rather than editing the main config, so the change is greppable, revertible and obviously yours:[sshdcfg][ssh99]

# /etc/ssh/sshd_config.d/50-post-quantum.conf
# Requires `Include /etc/ssh/sshd_config.d/*.conf` in the main sshd_config,
# which Debian, Ubuntu, RHEL and Fedora already ship. Check with:
#   grep -r '^Include' /etc/ssh/sshd_config

# --- OpenSSH 9.9 and newer ---
# Post-quantum first, classical last. The list is ordered by preference, and
# the two hybrids are the only entries here that resist a quantum adversary.
KexAlgorithms mlkem768x25519-sha256,sntrup761x25519-sha512,curve25519-sha256,curve25519-sha256@libssh.org

# --- OpenSSH 9.0 to 9.8: NEITHER name above exists on those builds. ---
# ML-KEM only arrived in 9.9, and until 9.9 the NTRU Prime hybrid existed
# solely under its vendor extension name. sshd refuses to start on an
# unknown algorithm name, so on those versions use exactly this line and
# nothing from the one above:
#   KexAlgorithms sntrup761x25519-sha512@openssh.com,curve25519-sha256
# Confirm which names your build accepts before editing:  ssh -Q kex

# Signatures are NOT the urgent problem (see the article), but this is a good
# moment to drop the algorithms that are weak for classical reasons too.
HostKeyAlgorithms ssh-ed25519,ssh-ed25519-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-256
PubkeyAcceptedAlgorithms ssh-ed25519,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,rsa-sha2-512,rsa-sha2-256

Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com

# --- Validate BEFORE reloading, and keep your current session open. ---
#   sudo sshd -t && sudo systemctl reload ssh   # or sshd, depending on the distro
# Then open a SECOND session and confirm it works before closing the first.

Do not skip the validation ritual. Run sshd -t before reloading, keep your existing session open, and open a second session to confirm before you close the first. A KexAlgorithms typo locks you out of the host, and on a machine with no console access that is a rebuild, not an incident.

The client side is where you get to be stricter, because you can afford to fail loudly on your own laptop in a way you cannot on a shared bastion. The pattern that survives contact with reality is a hard default plus explicit, dated exceptions:[sshcfg]

# ~/.ssh/config - client side
#
# ssh_config is FIRST-OBTAINED-VALUE-WINS, not last. The specific block must
# come BEFORE `Host *`, otherwise the general block wins and the exception
# below is silently dead. This is the single most common mistake in hardened
# client configs, and it fails in the direction you will not notice.

# Exceptions first: the boxes you have not fixed yet. Make each one explicit
# and dated, so it shows up in review instead of quietly becoming permanent.
Host legacy-nas.internal jump-2019.example.com
    # TODO(2026-11-30): appliance firmware pending, ticket OPS-4471
    # Note this REPLACES the list rather than using `+`, which would append to
    # the full compiled-in default and quietly re-enable every modp group.
    KexAlgorithms curve25519-sha256,diffie-hellman-group-exchange-sha256
    WarnWeakCrypto no-pq-kex

# ...and the hard default for everything else.
Host *
    KexAlgorithms mlkem768x25519-sha256,sntrup761x25519-sha512
    WarnWeakCrypto yes

# Verify the result rather than trusting the file. `ssh -G` prints the
# effective configuration for a given destination, after all matching:
#   ssh -G legacy-nas.internal | grep -i '^kexalgorithms'
#   ssh -G anything-else.example.com | grep -i '^kexalgorithms'

Two details worth internalising. First, every post-quantum algorithm OpenSSH implements is a hybrid: mlkem768x25519-sha256 runs ML-KEM alongside X25519 and combines both, so if ML-KEM is broken by future cryptanalysis the result is no weaker than the classical exchange you were using before. There is no downside case. Second, if you are on OpenSSH 9.x and cannot upgrade, the NTRU Prime hybrid is quantum-resistant and has been available since 9.0 — but note the name changed: before 9.9 it exists only as sntrup761x25519-sha512@openssh.com, and the short form will be rejected. It is not the NIST algorithm, but it stops the harvesting attack today, which is the whole point.[pq]

Fixing TLS

TLS is the same shape with a different lever. The primitives live in OpenSSL, not in your web server: with OpenSSL 3.5 or newer you need no patches, no oqs-provider, no forked nginx. Confirm the library first, because this is where most of the surprises live — a distribution can ship a current nginx linked against an OpenSSL that cannot do ML-KEM at all.[ossl35][tlsdraft][mlkemkex]

# 1. ML-KEM needs OpenSSL 3.5.0 or newer. No 3.x before 3.5 can do it at all,
#    however recent the rest of the stack looks.
openssl version
# OpenSSL 3.5.7 9 Jun 2026

# 2. Confirm the provider actually exposes the primitives.
openssl list -kem-algorithms | grep -i mlkem
openssl list -signature-algorithms | grep -iE 'ML-DSA|SLH-DSA'

# 3. What does a live endpoint negotiate when the client offers the hybrid?
openssl s_client -connect www.example.com:443 -servername www.example.com \
  -groups X25519MLKEM768 -tls1_3 </dev/null 2>/dev/null \
  | grep -E 'Negotiated TLS1.3 group|Protocol  :|Cipher    :'
# Negotiated TLS1.3 group: X25519MLKEM768

# 4. Control test: the same endpoint must still serve a classical-only client,
#    otherwise you have not hardened it, you have broken it.
openssl s_client -connect www.example.com:443 -servername www.example.com \
  -groups X25519 -tls1_3 </dev/null 2>/dev/null \
  | grep 'Negotiated TLS1.3 group'
# Negotiated TLS1.3 group: X25519

Note the control test in step four. Enabling a hybrid group is not supposed to break classical clients, and if it does you have misconfigured the list rather than hardened it. Order matters, exclusivity does not: put the hybrid first and leave the classical curves behind it.[osslgroups]

# Pick the ONE stanza that matches your terminator - this is four different
# config syntaxes in one block, not a file you can paste as-is.

### nginx (built against OpenSSL 3.5+; no patches, no oqs-provider needed)
# ssl_ecdh_curve feeds the TLS supported-groups list. Hybrid first, then the
# classical curves that keep older clients working.
ssl_protocols       TLSv1.3 TLSv1.2;
ssl_ecdh_curve      X25519MLKEM768:X25519:secp256r1;
ssl_prefer_server_ciphers off;

### HAProxy 3.x
ssl-default-bind-curves X25519MLKEM768:X25519:secp256r1

### Apache httpd (mod_ssl passes the list straight to OpenSSL)
SSLOpenSSLConfCmd Groups X25519MLKEM768:X25519:P-256

### OpenSSL system-wide default, for everything that reads openssl.cnf
# [openssl_init] -> ssl_conf -> system_default
Groups = X25519MLKEM768:X25519:secp256r1

The configuration is one line per server, and in every case it feeds the same OpenSSL supported-groups list:[nginxssl][haproxy][osslconf]

ComponentRequirementCommon trap
nginxLinked against OpenSSL 3.5.0+; ssl_ecdh_curveA current nginx built against OpenSSL 3.0 — check nginx -V, not the nginx version.
HAProxyOpenSSL 3.5.0+; ssl-default-bind-curvesPer-bind overrides silently outrank the default line.
Apache httpdOpenSSL 3.5.0+; SSLOpenSSLConfCmd GroupsDirective is per-vhost; a vhost without it inherits the compiled-in default.
CDN / managed load balancerProvider-side setting, often already onThe origin leg behind it is yours, and it is usually the one still classical.
Application runtimes (Go, Java, Node)Runtime's own TLS stack, not the system OpenSSLAssuming the system library governs. It frequently does not.

The one architectural thing to get right: this only applies where TLS is terminated. If a CDN or a load balancer terminates for you, that is the hop that needs the hybrid group, and your origin configuration is a separate question — as is the link between the two, which is exactly the sort of internal leg that gets forgotten. If you terminate in Kubernetes, this belongs in the gateway, and the migration off ingress-nginx is a natural moment to do it, since you are rewriting those objects anyway.[rfc8446]

Building an inventory you can defend

Everything above is per-host. What auditors, and your future self, will ask for is coverage: not "is this endpoint fixed" but "how many are there and which ones are not". Build the list from the source of truth you already trust rather than from a scan, so that absence means something:

#!/usr/bin/env bash
# pq-inventory.sh - a starting point, not a CBOM. One line per listener, with
# the negotiated group, so the output diffs cleanly between runs.
set -uo pipefail
printf '%-34s %-9s %-22s %s\n' HOST PORT GROUP NOTE

while IFS=: read -r host port; do
  out=$(openssl s_client -connect "$host:$port" -servername "$host" \
        -groups X25519MLKEM768:X25519 -tls1_3 </dev/null 2>/dev/null)
  grp=$(printf '%s' "$out" | sed -n 's/^Negotiated TLS1.3 group: //p')
  sig=$(printf '%s' "$out" | sed -n 's/^Peer signature type: //p')
  case "$grp" in
    *MLKEM*) note="pq-ok" ;;
    "")      note="no-tls1.3-or-unreachable" ;;
    *)       note="classical-only" ;;
  esac
  printf '%-34s %-9s %-22s %s (sig %s)\n' "$host" "$port" "${grp:--}" "$note" "${sig:--}"
done < endpoints.txt

# endpoints.txt is one host:port per line. Feed it from whatever you already
# trust as the source of truth - the load balancer config, Consul, the CMDB -
# rather than from a network scan, so that absence is meaningful.

Resist the urge to buy a cryptographic bill of materials product before you have run something like this. The tooling is genuinely improving, but the first pass is worth doing by hand because it tells you the shape of your own estate, which is the part no vendor can give you. Order of work:

  1. External TLS termination. Every public hostname. Smallest list, highest exposure, usually fixed by one change at the edge.
  2. SSH, everywhere. Bastions first, then everything reachable from them. This is where the long tail of appliances, network gear and forgotten VMs surfaces.
  3. Internal service-to-service TLS. Service mesh, database connections, message brokers. Larger list, harder to enumerate, and the place where a mesh's own defaults may already have solved it for you.
  4. VPN and site-to-site tunnels. Long-lived, high-value, and frequently running vendor firmware with its own upgrade calendar. Start the vendor conversation early, because this is a procurement problem more than a technical one.
  5. Everything embedded. Devices, agents, printers, cameras, industrial controllers. Document them, accept the risk explicitly, and put a date on the acceptance.

Keep the output diffable and re-run it on a schedule. A number that moves in the wrong direction is the earliest signal that a rebuilt image or a rolled-back package quietly undid the work — and that is a far more common failure than a botched migration.

Signatures and certificates: why you should wait

Now the part where the useful advice is to do less. NIST standardised two post-quantum signature schemes in 2024, ML-DSA and SLH-DSA, and both are implemented in OpenSSL 3.5. You can generate the keys today. You should not put them in your certificate chain.[fips204][fips205][fips203]

The reason is the one OpenSSH gives when explaining why it prioritised key agreement: there is no store-now-forge-later attack. A signature only has to be unforgeable at the moment it is verified. The urgency for signatures is not about protecting today's traffic; it is about making sure classical signature keys are retired before a cryptographically relevant quantum computer exists — a deadline measured in years, not in captured packets.[pq]

The practical blockers are ecosystem-shaped, and none of them are yours to solve:

  • No post-quantum roots are trusted by browsers. A certificate chain is only as useful as the root the relying party already trusts, and those trust stores move on a multi-year cadence with good reason.
  • The size problem is real. ML-DSA signatures and public keys are substantially larger than ECDSA's. In a handshake that carries a full chain, this is measured in extra round trips on lossy links, not in abstract bytes.
  • OpenSSH has only an experimental post-quantum signature scheme. The project removed its experimental XMSS support in 10.1, then added a composite mldsa44-ed25519 scheme in 10.4 (July 2026) pairing ML-DSA-44 with Ed25519. It is explicitly experimental and not enabled by default: you add it to HostKeyAlgorithms and PubkeyAcceptedAlgorithms yourself and generate keys with ssh-keygen -t mldsa44-ed25519. Worth knowing about; not worth putting under production host keys yet.[ssh104][mldsased][ssh101]
  • Every intermediate must agree. Certificate validation involves your server, the client, every middlebox that inspects, and whatever pinning someone added in 2019. Signatures fail closed, and they fail for everyone at once.

So what do you do with signatures in 2026? Two things, both cheap. Retire the algorithms that are already weak for classical reasons — DSA is gone from OpenSSH 10.0 entirely, and SHA-1 signatures should have left years ago. And make your certificate issuance automated and short-lived, so that when post-quantum certificates do become issuable, swapping them is a configuration change and not a project. Crypto-agility is not a product you buy; it is the property of already being able to rotate quickly, which you should want for entirely unrelated reasons. The same instinct behind boring architecture applies here: the exotic option is not the safe one.[ssh103]

The regulatory calendar, stated accurately

Deadlines are where technical articles quietly become wrong, so here is the careful version. NIST IR 8547, the document everyone cites for "RSA is deprecated in 2030", is an Initial Public Draft published in November 2024. Its comment period closed in January 2025 and the comments received are public. It signals NIST's expected approach and it is the right planning input — but citing it as a final, binding standard is inaccurate, and someone in your review will know that.[ir8547]

InstrumentStatusWhat it actually says
NIST IR 8547Initial Public Draft, November 2024Signals RSA, ECDSA, ECDH and finite-field DH deprecated after 2030 and disallowed after 2035. A draft, not a final standard.
FIPS 203 / 204 / 205Final, August 2024ML-KEM, ML-DSA and SLH-DSA. These are the algorithms everything else refers to.
EU coordinated roadmapAdopted by Member States, June 2025Transition started and pilots running by end 2026; high-risk and critical infrastructure by 2030; as complete as practical by 2035.
Your sector regulatorVariesUsually derived from one of the above. Read the derivation, not the summary.

The distinction matters in practice: a draft tells you where the floor is heading, which is enough to plan against. It is not enough to write "required by NIST" in a design document. Cite it as the draft it is, and your programme survives its first serious review.[ir8547]

Europe is on a parallel track with different mechanics. Member States adopted a coordinated implementation roadmap through the NIS Cooperation Group, structured around beginning the transition and running pilots by the end of 2026, completing high-risk use cases and critical infrastructure by 2030, and finishing as far as practical by 2035. It is a coordination instrument rather than a regulation, but it is what national roadmaps are being written against, so if you operate in the EU it is the document your sector regulator has read.[euroadmap]

One consequence worth naming for anyone operating across jurisdictions: these calendars are converging on the same end dates but not on the same obligations, and vendor firmware is the binding constraint in every one of them. If a device is on a five-year replacement cycle, the purchase you make this year is a post-quantum decision whether or not anyone in the procurement thread says so.

A 90-day plan

If you want the whole thing on one page, here it is. The premise is that key agreement is a solved problem you are simply rolling out, and everything else is preparation.

PhaseWorkDone when
Weeks 1–2
Survey
Sweep SSH and TLS for the negotiated algorithm, not the supported list. Build the endpoint inventory from your own source of truth.You have a number: how many endpoints are classical, and which.
Weeks 3–6
External edge
Every public TLS terminator and every bastion. Confirm the OpenSSL the binary is actually linked against.Hybrid negotiated externally, and classical clients still work.
Weeks 7–10
Internal
Service-to-service TLS, replication, backups, the origin leg behind the CDN. The links with no human to see a warning.The internal count matches the external one.
Weeks 11–12
Residual
Appliances and firmware that cannot be fixed. Vendor tickets opened, risk accepted in writing, review date set.Every remaining exception has an owner and a date.
Ongoing
Signatures
Automate certificate issuance and shorten lifetimes. Do not deploy post-quantum certificates.You can rotate any certificate without a change window.

Ninety days is realistic for the first three phases in most estates, because the changes are small and the risk is concentrated in the survey rather than the edit. What takes longer is always the same two things: appliances you cannot upgrade, and links nobody knew existed. Both are found in week one if you do the inventory properly, which is the argument for doing it first.

Five mistakes worth avoiding

Patterns that show up repeatedly in real migrations, in rough order of how much time they cost:

  1. Auditing capability instead of negotiation. "Our servers support ML-KEM" is not a finding. A server can support it and still complete a classical handshake all day for one stale client. Read the negotiated algorithm on the connection, not the supported list.
  2. Removing the classical algorithms. Hybrids exist precisely so you do not have to choose. Put the post-quantum group first, keep X25519 behind it, and you gain protection without an availability incident.
  3. Chasing certificates. Time spent trying to deploy ML-DSA certificates in 2026 is time not spent on the exchange that is actually being harvested. Automate issuance instead; that is the work that pays off later.
  4. Stopping at the edge. The CDN handshake is post-quantum and the origin link is not. This is the single most common gap, because the external test passes and the internal leg has no user to complain.
  5. Treating it as a one-off. A rebuilt base image, a pinned package, a rolled-back config, and a host that was compliant in March is negotiating classically in September. Put the sweep on a timer — a systemd timer is more than enough — and alert on the count going up.

The short version

Post-quantum key agreement is not a future project. It is a default you have probably already inherited, and the entire job is finding the connections that missed it. Upgrade to OpenSSH 10.x and OpenSSL 3.5+, put the hybrid group first in both, sweep the fleet for what is actually negotiated, and write down the exceptions with dates on them. That is a week of work for most teams and it closes the only attack in this space that is running today.

Then stop. Do not migrate certificates, do not buy a crypto-agility platform, and do not write "quantum" in a strategy document. Make your certificate issuance automated and short-lived, keep the inventory current, and wait for the ecosystem — because when post-quantum signatures do become deployable, the organisations that handle it well will not be the ones that started earliest. They will be the ones that can already rotate a certificate without a change window.

Frequently asked questions

Do I need to do anything if I am already on OpenSSH 10?

Verify, then probably not. The default is mlkem768x25519-sha256, but a hardening baseline, a CIS-derived template or a config-management module may have written its own KexAlgorithms line that predates ML-KEM and silently excludes it. Run ssh -v host exit 2>&1 | grep 'kex: algorithm' against a real host and read the negotiated result. The server side is what matters for inbound sessions.

Does enabling post-quantum key exchange break old clients?

Not if you configure it as a preference rather than a restriction. Both KexAlgorithms in SSH and the supported-groups list in TLS are ordered preferences: put the hybrid first and keep curve25519-sha256 or X25519 behind it, and modern peers get the post-quantum exchange while older ones fall back. Breakage happens when you remove the classical entries entirely — which is a defensible choice for a closed fleet you fully control, and a bad one for anything public.

Is sntrup761x25519-sha512 good enough, or must I move to ML-KEM?

It is genuinely quantum-resistant and it stops the harvest-now-decrypt-later attack today, which is the property you actually need. Its weakness is standardisation rather than cryptography: NTRU Prime was not selected by NIST, so it will not satisfy a compliance regime written around FIPS 203, and long-term implementation support is less certain. If you are on OpenSSH 9.0–9.8 and cannot upgrade this quarter, you are protected. Schedule the move to 9.9+ anyway.

Why can't I just deploy post-quantum certificates now?

Because a certificate is only useful if the relying party already trusts the root that signed it, and no post-quantum root is in the browser trust stores. You can generate ML-DSA keys with OpenSSL 3.5 and stand up an internal CA with them, which is a reasonable experiment for a closed system. For anything a browser touches, the chain will not validate. This is an ecosystem sequencing problem, not a configuration problem, and it resolves in years.

How do I check what my TLS endpoint actually negotiates?

openssl s_client -connect host:443 -servername host -groups X25519MLKEM768 -tls1_3 </dev/null 2>/dev/null | grep 'Negotiated TLS1.3 group'. You need an OpenSSL 3.5+ client for the test itself — testing with an older client produces a false negative, because your client never offered the group. Always run the control test with -groups X25519 as well, to confirm you have hardened the endpoint rather than broken it.

Do I need to worry about AES and SHA-256?

No. Grover's algorithm gives a quadratic speed-up against symmetric primitives, which effectively halves the key length: AES-256 retains a 128-bit security margin, and SHA-256 remains adequate. AES-128 is more debated but not the practical concern. The asymmetric algorithms — RSA, ECDH, ECDSA — are the ones broken outright by Shor's algorithm, and they are the entirety of this article's subject.

What about VPNs, databases and message queues?

Same rule, harder execution. WireGuard has no post-quantum key agreement in its base protocol and relies on its pre-shared-key mechanism for a hybrid arrangement. IPsec support depends entirely on your vendor's firmware. Database and broker TLS usually follows the runtime's own TLS stack rather than the system OpenSSL, so verify each one individually rather than assuming a system-wide setting reached it. These links are also the highest-value targets, because the payload is exactly the long-lived confidential data the harvesting attack is designed to collect.

Is this compliance theatre, or a real threat?

The threat is real but time-shifted, which is what makes it easy to misjudge in both directions. Nobody is decrypting your traffic today. The question is whether traffic captured today is still sensitive in the mid-2030s, and for medical records, financial data, legal material, government communications and long-lived credentials, the answer is clearly yes. The unusual feature of this particular risk is that the mitigation is nearly free — a version bump and one configuration line — so the cost-benefit calculation does not require you to believe any specific arrival date.

All of this assumes the base system stays still, which it does not: upgrading Ubuntu 24.04 to 26.04 on servers covers the release upgrade that changes six of these defaults underneath you.

Sources

Every source below was read while writing this article. Project release notes and standards documents are cited in preference to secondary coverage, and where a document is a draft rather than a final standard, it is described as one.

  1. OpenSSH — Post-Quantum Cryptography (project page, FAQ and warning text)
  2. OpenSSH 9.9 release notes (19 September 2024) — ML-KEM hybrid added; sntrup761x25519-sha512 gains its IANA name
  3. OpenSSH 10.0 release notes (9 April 2025) — mlkem768x25519-sha256 becomes the default key agreement
  4. OpenSSH 10.1 release notes (6 October 2025) — warning on non-post-quantum key agreement, WarnWeakCrypto
  5. OpenSSH 8.5 release notes (3 March 2021) — sntrup761x25519-sha512@openssh.com replaces the older NTRU Prime hybrid, disabled by default
  6. OpenSSH 10.3 release notes (2 April 2026)
  7. OpenSSH 10.4 release notes (6 July 2026) — experimental mldsa44-ed25519 composite post-quantum signature scheme, not enabled by default
  8. OpenSSH 10.5 release notes (11 August 2026) — current release at the time of writing
  9. OpenSSH — full release notes index
  10. ssh_config(5) — KexAlgorithms, WarnWeakCrypto, Match
  11. sshd_config(5) — KexAlgorithms, HostKeyAlgorithms, Include
  12. IETF draft-ietf-sshm-mlkem-hybrid-kex — hybrid ML-KEM key exchange for SSH
  13. IETF draft-ietf-sshm-ntruprime-ssh — sntrup761x25519-sha512 for SSH (successor to the expired draft-josefsson document)
  14. IETF draft-miller-sshm-mldsa44-ed25519-composite-sigs — the composite signature scheme OpenSSH 10.4 implements
  15. OpenSSL 3.5 series release notes — PQC support and hybrid groups preferred by default (3.5.0, 8 April 2025)
  16. OpenSSL documentation — SSL_CTX_set1_groups_list(3), the TLS supported-groups list
  17. OpenSSL documentation — SSL_CONF_cmd(3), the Groups command Apache passes through
  18. nginx documentation — ssl_ecdh_curve in ngx_http_ssl_module
  19. HAProxy configuration manual — ssl-default-bind-curves
  20. IETF draft-ietf-tls-ecdhe-mlkem — hybrid ECDHE + ML-KEM groups for TLS 1.3
  21. RFC 8446 — The Transport Layer Security (TLS) Protocol Version 1.3
  22. NIST FIPS 203 — Module-Lattice-Based Key-Encapsulation Mechanism Standard (ML-KEM)
  23. NIST FIPS 204 — Module-Lattice-Based Digital Signature Standard (ML-DSA)
  24. NIST FIPS 205 — Stateless Hash-Based Digital Signature Standard (SLH-DSA)
  25. NIST IR 8547 (Initial Public Draft, 12 November 2024) — Transition to Post-Quantum Cryptography Standards
  26. European Commission / NIS Cooperation Group — Coordinated Implementation Roadmap for the Transition to PQC
  27. Cloudflare — State of the post-quantum Internet in 2025
  28. Cloudflare Radar — post-quantum encryption adoption (live figures)

Was this useful?