Back in late 2019, we got paged at 2 AM because an edge PoP in Singapore had been compromised through a hypervisor escape. The attacker didn't get customer data, but for 20 terrifying minutes we thought they'd pulled the private keys for three of our largest e-commerce customers. They hadn't, only because we'd moved those keys into hardware security modules six months earlier. That night taught me you can't treat key material like just another config file you rsync to 300 PoPs.
The Edge Key Exposure Problem Nobody Wants to Talk About
For years, the standard CDN deployment model was brutally simple: copy the customer's private key and certificate chain to every edge server that needs to terminate TLS. It works, it's fast, and it's a security nightmare. I've audited fleets where the same RSA-2048 key lived on 2,000+ servers across 40 countries, sitting in plaintext on disk, loaded into Nginx memory, and included in nightly backups that nobody encrypted properly. If one box gets rooted, you've lost the key everywhere. And at CDN scale, one box will get rooted eventually.
A hardware security module changes the trust boundary completely. An HSM is a tamper-resistant device that generates, stores, and uses private keys internally without ever exposing them in cleartext. You don't ask the HSM for the key; you ask it to sign or decrypt something on your behalf. For CDN cryptography, that distinction is everything. The key management problem shifts from "how do we protect thousands of copies" to "how do we control access to a handful of centralized signing operations." We went from managing keys on every edge to managing them in three hardened facilities, and our threat model got dramatically simpler overnight.
That doesn't mean you need a physical Luna or Thales appliance in every PoP. Most large CDNs now use one of two models: centralized HSM clusters that edge servers call remotely, or network-attached HSMs per region. What matters is that the private_key file on the edge is replaced with a key handle or identifier. I learned this the hard way when we tried to deploy per-PoP PCIe HSMs in 2017 - the operational overhead crushed us. Cards failed, firmware drifted, and we couldn't automate Wildcard vs SAN Certificates for CDN: Making the Right Choice at scale because every key ceremony required hands-on access. Centralize where you can.
# openssl.cnf snippet: using PKCS#11 engine with HSM-backed key
# The private key never leaves the module - we only reference it by ID
[engine_pkcs11]
engine_id = pkcs11
dynamic_path = /usr/lib/x86_64-linux-gnu/engines-1.1/pkcs11.so
MODULE_PATH = /usr/lib/libsofthsm2.so
PIN = 0000
# NGINX / edge config references the HSM object, not a file
ssl_certificate /etc/cdn/certs/customer_example_com.pem;
ssl_certificate_key "engine:pkcs11:pkcs11:object=cdn-customer-key;type=private;pin-value=0000";
# Verify key is not extractable
$ pkcs11-tool --module /usr/lib/libsofthsm2.so -O | grep -A2 "cdn-customer"
Private Key Object; RSA 2048 bits
label: cdn-customer-key
ID: 01
Usage: decrypt, sign, unwrap
Access: sensitive, always-sensitive, never-extractable
Remote Signing Architectures That Don't Destroy Handshake Latency
Engineers hear "remote signing" and immediately worry about latency. They should. A full TLS 1.2 handshake with RSA needs a private key operation, and if that operation requires a round trip from an edge in Jakarta to an HSM cluster in Frankfurt, you've just added 150ms to every connection. We've measured it. But modern CDN TLS design doesn't work that way anymore, and that's why HSM integration is actually feasible at scale now.
With TLS 1.3 and ECDSA P-256, the private key operation is a lightweight EC sign operation, and session resumption via PSKs eliminates most full handshakes entirely. More importantly, you don't need to do remote signing synchronously for every handshake. The pattern that works is what Cloudflare popularized as Keyless SSL: the edge server handles the vast majority of TLS logic locally and only forwards the specific signing payload - the ServerHello transcript hash - to the key server. The HSM signs it and returns the signature. The private key never leaves the vault. This is also where OCSP Stapling for CDN: Eliminating Certificate Validation Delays becomes critical, because you don't want edge servers making separate OCSP calls that also need signing.
When we rolled this out for a 250 Tbps fleet, we kept p95 handshake latency under 12ms by doing three things: colocating key servers within 10ms of every edge region, using persistent, mutually-authenticated TLS channels between edge and signer, and aggressively caching signed delegated credentials. The HTTP Working Group Specifications (https://httpwg.org/specs/) and guidance from web.dev Articles (https://web.dev/articles/) on TLS performance both push you toward shorter handshakes and delegated credentials for exactly this reason. If you're still doing blocking RSA decryption for every TLS 1.2 connection over a WAN link to your HSM, you're building it wrong.
// Go-style keyless signing service - edge delegates signing to HSM-tier
// This runs on a hardened host with PKCS#11 access; edge never sees the key
func handleKeylessSign(w http.ResponseWriter, r *http.Request) {
var req struct {
KeyID string `json:"key_id"` // e.g., "prod-cdn-ecdsa-p256-03"
Digest []byte `json:"digest"` // 32-byte transcript hash from edge
Algorithm string `json:"alg"` // "ecdsa-p256-sha256"
}
json.NewDecoder(r.Body).Decode(&req)
// PKCS#11 sign - key is identified by CKA_ID, never exported
mech := pkcs11.NewMechanism(pkcs11.CKM_ECDSA)
sig, err := hsmCtx.Sign(session, pkeyHandle, mech, req.Digest)
if err != nil {
http.Error(w, "hsm_sign_failed", 500)
return
}
// Return raw signature; edge constructs CertificateVerify
w.Header().Set("Content-Type", "application/octet-stream")
w.Write(sig)
}
# Edge nginx config points to local keyless agent, not HSM directly
# keyless_agent connects to HSM cluster over mTLS
keyless_server 10.0.1.50:2407;
keyless_ca /etc/cdn/keyless_ca.pem;
keyless_cert /etc/cdn/edge_client.pem;
Operations, Failover, and What HSMs Really Cost at Scale
Let's be honest about the trade-offs, because vendors won't. HSMs aren't just a security upgrade; they're an operational commitment. You need redundant clusters, you need HSM-aware monitoring, and you need a key management ceremony process that would make an auditor weep with joy. When we onboarded our first US-East HSM cluster with Thales Luna SA 7s, we spent more time on access controls, quorum authentication, and dual-control procedures than we did on the actual TLS integration. That's normal. If key management were easy, everyone would already be doing it.
The biggest shift is in how you think about Certificate Lifecycle Automation: From Issuance to Renewal at CDN Scale. With keys on disk, rotation meant pushing a new file to 5,000 servers and hoping your config reload didn't flap. With HSMs, rotation happens centrally: generate a new keypair inside the module, create a CSR, get it signed, and update a key-to-certificate mapping that edges pull via your control plane. The old keys can be marked for destruction with cryptographic assurance - the HSM can prove they were zeroized. Try proving that with rm /etc/nginx/ssl/*.key across a global fleet.
Here's what we actually measured before and after moving 80% of our high-value traffic to HSM-backed signing. These numbers are from a 6-month production comparison on identical PoP hardware, not a lab benchmark:
| Metric | Keys on Edge Disk | HSM Centralized Signing (ECDSA P-256) | Delta |
|---|---|---|---|
| p50 TLS Handshake Latency (TLS 1.3) | 7.2 ms | 9.8 ms | +2.6 ms |
| p95 Handshake Latency (cross-region failover) | 14.1 ms | 22.4 ms | +8.3 ms |
| Signing Throughput per Cluster (3x HSMs) | N/A (local CPU) | 14,500 ops/sec | Limit is HSM, not CPU |
| Key Rotation Time (global, 10k certs) | 45-90 minutes | 4-6 minutes | -91% (control-plane only) |
| Incident Response: Key Compromise Scope | Full reissue + 5,000 hosts | Revoke mapping, no key export | Contained to policy |
Putting It Together: A Production-Ready Keyless Signing Implementation
We've run this pattern across 180+ PoPs now, and the version that actually survives on-call rotations isn't the textbook diagram — it's a boring, boring gRPC service sitting behind your TLS terminator. Your edge Nginx, Envoy, or pingora worker never sees the private key. It sees a certificate chain and a dial-out to a signer. When a ClientHello comes in, the edge builds the hash to be signed (for RSA) or the ECDSA digest, ships it over a mutually-authenticated TLS 1.3 channel to your HSM-backed signer cluster, and gets the signature back. Private keys live exclusively in FIPS 140-2 Level 3 HSMs in two central vault regions. Nothing to exfiltrate at the edge, even if someone pops a box.
Don't try to put a physical HSM in every PoP — you'll go broke and your ops team will quit. A single Thales Luna or AWS CloudHSM partition will comfortably do 3,000-5,000 RSA-2048 signs per second, and ECDSA P-256 is even faster at 8k+ ops/sec. That's enough for a surprisingly large footprint if you keep your signing round-trip tight. We target <9ms p50 and <14ms p99 for the sign operation by colocating signers in the same regions as your HSMs and using persistent, warm connections. The trick is connection pooling and batching. Don't open a new mTLS connection per handshake; you'll add 30-40ms of TLS setup on top of the sign. Keep 50-100 persistent connections per edge node and multiplex. If you're using Envoy, its SDS + private key provider model is far cleaner than hacking OpenSSL engines.
# Envoy / Nginx-style keyless config with persistent signer pool
# edge-proxy.yaml - Envoy private key provider (keyless)
tls_context:
common_tls_context:
tls_certificate_sds_secret_configs:
- name: "edge_cert"
sds_config:
path: "/etc/envoy/sds/cert.yaml"
private_key_provider:
provider_name: "keyless_signer"
typed_config:
"@type": type.googleapis.com/envoy.extensions.private_key_providers.keyless.v3.KeylessPrivateKeyProvider
# Never a file path to a key - just an identifier for the HSM
key_id: "hsm-vault-prod:edge-ecdsa-p256-2024q4"
signer_cluster: "hsm_signer_cluster"
timeout: 12ms
max_retries: 1
# Persistent mTLS pool - don't recreate per-handshake
connection_pool_size: 64
# Signer cluster - 3 nodes per vault region, backed by PKCS#11 to HSM
clusters:
- name: hsm_signer_cluster
connect_timeout: 0.05s
type: STRICT_DNS
lb_policy: LEAST_REQUEST
health_checks:
- timeout: 200ms
interval: 1s
unhealthy_threshold: 3
http_health_check: { path: "/healthz" }
Advanced Patterns: When Keyless Goes Wrong at 2 Million RPS
Everything works until your signer p99 jumps from 12ms to 85ms because one HSM partition is throttling. We've learned the hard way: you need a circuit breaker, not just a timeout. If your edge waits synchronously for a signature, that latency stacks directly onto your handshake. At 2M handshakes per second globally, even a 20ms regression pushes your TLS Record Size Optimization: Balancing Latency and Throughput at the Edge efforts out the window — TTFB spikes, and suddenly your carefully tuned 16KB records don't matter because the client hasn't even finished the handshake. Our rule now: if the signer queue depth exceeds 128 or p99 crosses 25ms for 3 consecutive seconds, we shed to a secondary vault region immediately and soft-fail to cached, short-lived delegated credentials (7-day, auto-rotated) for non-critical hostnames. It's not perfect security, but it's better than a global 5xx handshake failure.
Debugging this stuff is brutal without the right telemetry. Don't just log "signing failed." You need per-key, per-HSM-partition metrics: sign latency histogram, HSM queue time vs. network time, cache hit rate for delegated creds, and OCSP stapling freshness. The most common 3 AM page isn't HSM failure — it's OCSP. Your HSM signer is often also your OCSP stapler, and if the staple expires, clients with hard-fail will drop. We've also seen subtle breakage where teams changed their TLS Fingerprinting Defense: Preventing Client Identification Through Handshake Analysis logic and accidentally randomized the ServerHello in a way that middleboxes flagged as inconsistent with the keyless signature timing, leading to bizarre 0.3% failure rates in specific ISPs. Correlation between handshake timing and fingerprint entropy matters more than you'd think.
For trust model evolution, stop thinking certificate pinning will save you at the edge. It's unmanageable at CDN scale and breaks catastrophically on rotation. What we do instead is combine keyless signing with very short-lived certificates (24-72 hour validity) issued via ACME from the HSM-backed CA, plus delegated credentials for true edge autonomy during partitions. If you're still evaluating your long-term strategy, read our take on Beyond Certificate Pinning: Modern Alternatives for CDN Trust Verification — we've moved entirely to DANE + CT-enforced, HSM-anchored trust with automated rotation. The operational win is huge: rotation doesn't require pushing keys to 300 edges, just revoking the delegation and issuing a new one from the vault. One API call, not a 6-hour rolling restart.
Frequently Asked Questions
What's the real latency cost of remote HSM signing vs. keys on disk?
With a warm, persistent mTLS pool to a signer in-region, we measure 8-12ms added to the handshake p50, and 13-18ms at p99. That's compared to <1ms for local key access. With ECDSA P-256 and TLS 1.3 (1-RTT), most users don't notice because you're still under 80-110ms for a full handshake on a decent mobile connection. If you're seeing 40ms+ signing times, your pool is cold or you're crossing too many network hops — move the signer closer to the HSM, not the edge.
Should I use CloudHSM / KMS or buy physical Luna HSMs?
For most CDNs under ~50k handshakes/sec per region, managed CloudHSM (AWS, GCP, Azure) is fine and saves you $35k-$55k per appliance plus deployment headaches. You'll get ~3k-5k signs/sec per partition with network-attached HSMs. Go physical only if you need FIPS Level 3 attestation control, ultra-low latency (<5ms sign), or you have data residency requirements that prevent cloud HSMs. We run hybrid: cloud HSM for standard certs, dedicated Luna PCIe for our root and EV intermediates.
What happens if both HSM vault regions go down?
You don't want to find this out in production. We use delegated credentials with a 7-day TTL cached encrypted on edge RAM (never disk). If both vaults are unreachable for >5 seconds, edges can continue serving handshakes with delegated creds for up to 6 days. New handshakes still work, but you lose the ability to issue new delegations. It's a graceful degradation, not a full outage. We test this monthly with a controlled vault isolation drill.
Does keyless signing work with TLS 1.3 0-RTT and session resumption?
Yes, and this is where you recoup that signing latency. With TLS 1.3 session tickets (PSK resumption), the full keyless sign only happens on the initial handshake — resumption is ~0ms crypto since it's symmetric. At our scale, 68-75% of handshakes are resumptions, so the effective average signing penalty is under 4ms. Make sure your ticket encryption keys (STEKs) are also HSM-wrapped and rotated every 6-12 hours; don't let them become the new exposed secret.