The Enterprise PQC Migration Checklist: Discovery to Deployment
TL;DR
A PQC migration has four non-negotiable phases in strict order: (1) cryptographic discovery — build a Cryptographic Bill of Materials (CBOM) across TLS endpoints, code dependencies, certificates, and hardware, since you cannot migrate an algorithm you haven't found; (2) risk-based prioritization — rank discovered assets by "harvest now, decrypt later" exposure, not by ease of migration; (3) dependency and library remediation — upgrade to OpenSSL 3.5+, BoringSSL, or an oqs-provider bridge, and regenerate SBOMs to confirm transitive dependencies also moved; (4) phased reverse-proxy rollout — enable hybrid key exchange (e.g., X25519MLKEM768) as an additive, backward-compatible option via canary/percentage rollout, never a flag-day cutover, because hybrid groups are opportunistic and non-supporting clients silently fall back to classical groups without any outage. The single highest-leverage rule for avoiding outages: hybrid PQC key exchange is negotiated per-connection and degrades gracefully — the actual outage risk comes from CPU capacity under-provisioning (ML-KEM keygen/encaps cost) and middlebox ClientHello-size intolerance, not from the cryptography itself.
Why Sequencing Matters
Enterprises that treat PQC migration as a single "turn on the new TLS setting" change consistently discover, mid-rollout, that they don't actually know where all their cryptography lives — vendor SaaS integrations, embedded device fleets, code-signing pipelines, and internal service-to-service mTLS are all cryptographic surface area that a reverse-proxy-only rollout never touches. The four phases below are ordered because each is a hard prerequisite for the next: you cannot prioritize risk on an incomplete inventory, you cannot upgrade a dependency you haven't mapped, and you cannot safely roll out a proxy change without knowing which clients terminate there.
Phase 1: Cryptographic Discovery
The objective of this phase is a Cryptographic Bill of Materials (CBOM) — an inventory of every algorithm, key size, and certificate in use, mapped to the system that uses it. The emerging CycloneDX CBOM extension is the closest thing to a standard machine-readable format for this:
{
"bomFormat": "CycloneDX",
"specVersion": "1.6",
"components": [
{
"type": "cryptographic-asset",
"name": "edge-lb-tls-cert",
"cryptoProperties": {
"assetType": "certificate",
"algorithmProperties": {
"primitive": "signature",
"parameterSetIdentifier": "P-256",
"executionEnvironment": "software"
}
}
}
]
}Discovery Checklist
- Scan every public and internal TLS endpoint for negotiated groups, cipher suites, and certificate signature algorithms (
nmap --script ssl-enum-ciphers,testssl.sh, or a commercial TLS observatory scan across the full IP/domain inventory). - Grep application and service source trees for direct cryptographic API usage (
RSA_,EC_KEY_,ECDSA_,DH_, hardcoded curve OIDs) rather than relying solely on dependency manifests — hand-rolled crypto calls won't show up in an SBOM. - Enumerate every internal Certificate Authority, intermediate, and root, including their signature algorithm and key size, and note which systems pin to them.
- Inventory SSH host/user keys, code-signing keys (release pipelines, firmware signing), and PGP/S-MIME keys separately from TLS — each has an independent migration timeline.
- Inventory HSMs and their currently provisioned key material and firmware/CMVP validation status (see the HSM/TEE hardware deep-dive for FIPS 140-3 specifics).
- Survey third-party SaaS and vendor integrations for their own PQC posture — a migration is incomplete if a payment processor or identity provider you depend on hasn't moved, regardless of your own readiness.
- Consolidate findings into a CBOM (CycloneDX or equivalent) checked into a version-controlled repository, not a spreadsheet that goes stale after the first quarter.
Phase 2: Risk-Based Prioritization
Not every discovered asset migrates in the same order. The dominant risk model for PQC timing is "harvest now, decrypt later" (HNDL): an adversary recording today's encrypted traffic can decrypt it retroactively once a cryptographically relevant quantum computer exists, which only threatens confidentiality, not authentication — so the correct prioritization axis is how long the data must remain confidential, not how easy the system is to migrate.
| Priority Tier | Criteria | Example Systems | Target Migration Window |
|---|---|---|---|
| Tier 0 — Critical | Data confidentiality requirement exceeds 10 years; regulated (health, financial, national security) | VPN concentrators, document-retention archives, long-lived session data | Immediate — before any HNDL-relevant timeline |
| Tier 1 — High | Externally-facing TLS with moderate-sensitivity data | Public web/API edge, customer-facing auth | Aligned to browser/client PQC support maturity |
| Tier 2 — Moderate | Internal service-to-service traffic, short data lifetime | Internal mTLS mesh, ephemeral job queues | Standard infrastructure refresh cycle |
| Tier 3 — Low | Signature-only use (integrity, not confidentiality) or already crypto-agile | Code signing (can wait for ML-DSA tooling maturity), already-abstracted KMS-backed services | Opportunistic, tracked but not urgent |
Government-adjacent organizations should anchor Tier 0/1 windows to published external mandates rather than inventing their own — NSA's CNSA 2.0 timeline and the US federal NSM-10 directives are the most commonly cited external forcing functions, and contractual/regulatory obligations frequently reference them even for non-government enterprises in the supply chain.
Prioritization Checklist
- Tag every CBOM entry with a data-sensitivity/confidentiality-lifetime rating, not just a technical migration-difficulty rating.
- Cross-reference Tier 0/1 systems against any applicable regulatory or contractual PQC deadline (CNSA 2.0, sector-specific mandates, customer contractual clauses).
- Get executive/board sign-off on the tiering — prioritization is a business-risk decision, not a purely technical one, and CISOs need documented authorization for the sequencing before committing engineering time.
- Re-run prioritization quarterly; newly discovered assets and shifting regulatory deadlines both invalidate a static one-time ranking.
Phase 3: Software Dependency Mapping and Library Remediation
Hybrid PQC support did not arrive uniformly across the TLS library ecosystem, and "update OpenSSL" is not a single well-defined action across an enterprise's actual dependency graph.
Library Landscape
| Library | PQC (ML-KEM) Status | Integration Path |
|---|---|---|
| OpenSSL 3.5+ | Native X25519MLKEM768 and ML-KEM provider support | In-tree as of 3.5; earlier 3.x needs the oqs-provider bridge |
| OpenSSL < 3.2 | No native support | Requires oqs-provider (built on liboqs) loaded via OPENSSL_CONF provider directives, or a full library upgrade |
| BoringSSL | Hybrid X25519Kyber768/X25519MLKEM768 shipped and used in Chrome by default | Google-controlled fork; consumers get it via Chromium/gRPC/Envoy updates, not a standalone package upgrade |
Go crypto/tls | Native X25519MLKEM768 support in recent stdlib releases | Requires a Go toolchain upgrade, not just a go.mod dependency bump |
| Java (JSSE + Bouncy Castle) | ML-KEM/ML-DSA available via Bouncy Castle provider registration | Requires explicit Security.addProvider(new BouncyCastleProvider()) plus JSSE algorithm-constraint updates |
Python cryptography/ssl | Tracks the linked OpenSSL version | Migration is entirely a function of the underlying libssl the interpreter was built against |
# Verify what a given OpenSSL build actually supports before assuming coverage:
openssl list -providers
openssl list -kem-algorithms | grep -i mlkem# openssl.cnf — loading oqs-provider alongside the default provider
# when the base OpenSSL build predates native 3.5+ ML-KEM support.
[provider_sect]
default = default_sect
oqsprovider = oqsprovider_sect
[default_sect]
activate = 1
[oqsprovider_sect]
activate = 1
module = /usr/lib/ossl-modules/oqsprovider.soTransitive Dependency Risk
The most commonly missed step is confirming that statically-linked or vendored copies of a TLS library inside container images, language runtimes, or third-party binaries actually picked up the upgrade — bumping a package manager's top-level entry does not guarantee every transitively vendored libssl.so was rebuilt against it. Regenerating an SBOM (via syft, cyclonedx-cli, or equivalent) after the upgrade and diffing it against the pre-upgrade SBOM is the only reliable way to confirm this, rather than trusting the top-level manifest declaration alone.
Dependency Checklist
- Identify every distinct TLS library instance in the environment (not just distinct package names — distinct build artifacts, including statically-linked and vendored copies inside containers/binaries).
- Confirm native ML-KEM/hybrid-group support version-by-version per library from the table above, rather than assuming "recent version" implies support.
- Where native support is unavailable, deploy
oqs-provider(or the platform-equivalent bridge) and validate it viaopenssl list -kem-algorithmson the actual runtime build, not a developer workstation. - Regenerate SBOMs post-upgrade and diff against pre-upgrade SBOMs to catch missed transitive/vendored copies.
- Abstract algorithm selection behind configuration (group lists, provider directives) rather than hardcoded identifiers in application code — this is the crypto-agility investment that makes the next algorithm transition cheaper than this one.
Phase 4: Phased Reverse-Proxy Rollout
This is the phase with direct outage risk, and the phase this checklist spends the most operational detail on. The critical property that makes a safe rollout possible: hybrid key exchange groups are negotiated, not mandated — listing X25519MLKEM768 in a server's group preference alongside classical X25519/secp256r1 never breaks a client that doesn't support it; the TLS 1.3 handshake simply negotiates down to a mutually supported group. Outages during PQC rollouts are therefore almost always caused by one of two operational failures, not by the protocol negotiation itself: CPU capacity exhaustion and middlebox intolerance of larger ClientHello messages.
Step 1 — Canary Configuration
# nginx.conf — hybrid group listed alongside classical groups.
# Non-supporting clients negotiate down to x25519 automatically; no client-side outage.
ssl_protocols TLSv1.3;
ssl_conf_command Groups X25519MLKEM768:x25519:secp256r1;
ssl_ecdh_curve X25519MLKEM768:x25519:secp256r1;# Canary split via weighted upstream: route a small percentage of
# terminating traffic to a pool with hybrid groups enabled first.
upstream edge_pool {
server 10.0.1.10:443 weight=95; # classical-only, unchanged baseline
server 10.0.1.20:443 weight=5; # hybrid-enabled canary
}Step 2 — Capacity Validation Before Widening the Canary
ML-KEM key generation and encapsulation are computationally cheap in absolute terms but are additional CPU work layered onto every handshake that previously did only an X25519 scalar multiplication — at high connection-establishment rates (a busy public edge doing tens of thousands of new handshakes per second), this shifts aggregate CPU headroom in a way that matters for autoscaling thresholds and capacity planning, even though no single handshake becomes perceptibly slower. Load-test the hybrid-enabled pool at realistic peak handshake rates, not just functional correctness, before increasing canary weight.
Step 3 — Watch for ClientHello Fragmentation Fallout
Hybrid ClientHello messages carrying an ML-KEM-768 public key grow past 1,200+ bytes of key-share data alone, which has historically pushed some ClientHello messages past the single-TCP-segment/single-UDP-datagram boundary that a nontrivial population of legacy middleboxes silently assumed would never happen — some older TLS-inspecting proxies and load balancers were found to drop or mishandle oversized ClientHellos during the initial industry-wide Kyber/X25519Kyber768 rollout. Monitor negotiated-group and handshake-failure metrics specifically for the canary pool, and be prepared to fall back a client-facing pool to classical-only groups quickly if failure rates spike — this is the actual outage vector, not the cryptography.
Step 4 — Staged Widening
- Deploy hybrid groups to a single low-traffic edge node or region first; confirm zero handshake-failure-rate delta against baseline over a full traffic-pattern cycle (including peak hours).
- Widen canary weight incrementally (5% → 25% → 50% → 100%), holding each step for a full observation window before advancing.
- Monitor, per pool: handshake failure rate, negotiated-group distribution, p50/p99 handshake latency, and edge-node CPU utilization — all segmented by canary vs. baseline.
- Maintain an immediate rollback path (config flag or weighted-pool reversion) that requires no code deploy — a config change, not a release, must be able to revert group preference within minutes.
- Roll out region-by-region or pool-by-pool rather than globally in one step, so a region-specific middlebox incompatibility is contained rather than fleet-wide.
- Only after full rollout and a stable observation period, evaluate deprecating classical-only listener groups — and only for Tier 0/1 systems where the regulatory/contractual deadline actually requires removing the classical fallback, since keeping hybrid-plus-classical is strictly safer than PQC-only until client-side support is universal.
Avoiding Outages: The Core Operational Rules
- Never flag-day cut over. Hybrid groups are additive and backward-compatible by design — there is no technical requirement to remove classical group support simultaneously with adding PQC support, and doing so voluntarily reintroduces exactly the outage risk hybrid negotiation was designed to avoid.
- Capacity-test before widening, not after. ML-KEM's CPU cost is a fleet-capacity question, not a per-connection latency question — the failure mode is autoscaler lag or CPU saturation under load, which only appears at realistic traffic volume.
- Treat middlebox/ClientHello-size intolerance as the primary compatibility risk, not client TLS-stack support — modern browsers and TLS libraries handle group negotiation correctly; the risk sits in the network path between client and server, not at either endpoint.
- Keep rollback a configuration change, not a deployment. Group preference lists and canary weights should live in infrastructure config (feature flags, load balancer weights) with sub-minute revert capability, decoupled from application release cycles.
- Segment rollout by traffic pattern, not just percentage. A 5% canary that happens to route only same-region, well-behaved corporate-network traffic will not surface a consumer-ISP middlebox issue — ensure canary traffic is representative, not merely small.
Summary Checklist
- Phase 1 — Discovery: CBOM built and version-controlled across TLS endpoints, code, certificates, keys, and HSMs.
- Phase 2 — Prioritization: Tiers assigned by data-confidentiality lifetime (HNDL risk), executive sign-off obtained.
- Phase 3 — Dependencies: Every TLS library instance (including vendored/static copies) confirmed on a PQC-capable version or bridged via
oqs-provider; SBOM regenerated and diffed. - Phase 4 — Rollout: Hybrid groups deployed additively via canary, capacity-tested under realistic load, monitored for middlebox-driven handshake failures, widened incrementally with sub-minute rollback available.
- Classical-only fallback retained until client-side and network-path PQC support is verified broadly sufficient — deprecation of classical groups treated as a separate, later decision from enabling hybrid support.