Delegated Credentials for CDN: Short-Lived Keys Without Sharing Private Keys

Back in 2019, a large financial services client told us flat-out they wouldn't hand over their TLS private key — not to us, not to any CDN. Their compliance team had drawn a hard line, and I couldn't blame them. We spent three weeks trying to make Keyless SSL perform acceptably for their global traffic before we realized we were patching over a deeper architectural flaw: the internet never had a clean way to let a CDN terminate TLS without owning the long-lived secret.

The Private Key Sharing Problem We Pretended Was Normal

For years, the standard CDN TLS model was simple and, frankly, uncomfortable. You wanted us to serve your domain over HTTPS from 300 edge PoPs, you uploaded your certificate and private key to our control plane, and we pushed it everywhere. It worked, but it meant your most sensitive cryptographic material was now replicated across a distributed infrastructure you didn't control. If you've ever managed Certificate Lifecycle Automation: From Issuance to Renewal at CDN Scale, you know how painful rotation becomes when that key lives in dozens of vault replicas, edge caches, and config backups.

I've seen the operational fallout firsthand. One enterprise customer rotated their SAN certificate covering 40 domains and discovered an old private key still sitting on a decommissioned edge node that hadn't been wiped correctly. It wasn't exploited, but the audit finding alone cost them months of remediation. That's the core issue with long-lived key distribution — your exposure window isn't just the lifetime of the certificate, it's the lifetime of every copy you've ever made. And when we talk about Wildcard vs SAN Certificates for CDN: Making the Right Choice, the blast radius gets even bigger. A single compromised wildcard key could impersonate any subdomain.

We tried to work around this. Keyless SSL, where the CDN never holds the private key and instead calls back to a key server you host for every handshake, was the big answer around 2017. It solved the custody problem, but it introduced a new one: latency and availability. Every TLS 1.2 handshake and every TLS 1.3 handshake that needed signing required a round-trip to your origin key server. If that server was in us-east-1 and the client was in Jakarta, you just added 250ms to the handshake. We mitigated it with persistent connections and edge key caches, but you can't cache a private key operation without defeating the purpose. It's why many teams ended up just sharing the key anyway — performance won out over principle.

# Traditional CDN TLS vs. what we wanted
# Traditional: long-lived key copied to all edges
# Validity: 90 days to 1 year
# Risk: key persists on disk across hundreds of servers

# Desired: edge holds short-lived credential, origin keeps private key
# Validity: hours to days (max 7 days per RFC 9345)
# Risk: credential automatically expires, no revocation needed

# Delegated credential is NOT a certificate
# It's a structure signed by the end-entity certificate's private key
struct DelegatedCredential {
  valid_time: 86400,          // 24 hours in this example
  cred_pubkey: X25519/P-256,  // edge's ephemeral public key
  signature: sign(EE_privkey, cred_pubkey || valid_time)
}

How Delegated Credentials Actually Work Under the Hood

Delegated credentials, standardized in RFC 9345 and discussed extensively in the HTTP Working Group Specifications, flip the model. Instead of giving the CDN your private key, you give it a short-lived delegated credential — essentially a signed statement from your end-entity certificate that says "this other public key is allowed to speak for me for the next N hours." The origin's private key stays on your HSM or secure key server. It signs the delegated credential once, and the CDN uses the credential's corresponding private key to handle handshakes at the edge until it expires.

The handshake flow is elegant if you're familiar with TLS 1.3. During the ClientHello, the client advertises support for delegated credentials via the delegated_credential extension. The edge server responds with its certificate chain plus the delegated credential and proves possession of the delegated private key via a CertificateVerify message. From the client's perspective, it validates the end-entity certificate as usual, then validates that the end-entity certificate signed the delegated credential, then validates the handshake signature came from the delegated key. No extra round trip, no call back to the origin. The web.dev Articles documentation on delegation does a good job explaining the client-side validation logic, but on the server side, the beauty is that the credential is small — a few hundred bytes — and doesn't require any changes to the CA or certificate issuance process.

What I like most about this design is how it reframes OCSP Stapling for CDN: Eliminating Certificate Validation Delays and revocation entirely. Because the credential lives for hours or a couple of days, you don't revoke it — you just stop renewing it. If an edge node is compromised, you rotate the delegated key pair on your signing service and the old credential naturally dies within its validity window. We run an automated signer that issues new credentials every 6 hours with a 24-hour validity, giving us a nice overlap for clock skew and propagation delays. The CDN edge never needs to handle a CRL or OCSP response for the delegated key itself; revocation of the parent certificate is still handled normally.

# Example: issuing a delegated credential with OpenSSL + dc-tool
# On your secure origin / signer (holds EE private key):

# 1. Generate an ephemeral keypair for the edge (or have edge generate CSR)
openssl genpkey -algorithm X25519 -out dc-priv.pem
openssl pkey -in dc-priv.pem -pubout -out dc-pub.pem

# 2. Issue a delegated credential valid for 24 hours (86400 seconds)
#    This is signed with your end-entity certificate's private key
dc-tool issue \
  --ee-cert ee.crt \
  --ee-key ee.key \
  --dc-pubkey dc-pub.pem \
  --valid-for 86400 \
  --out dc-credential.bin

# 3. Deploy to CDN edge: dc-credential.bin + dc-priv.pem
# Edge presents these during TLS handshake without ever seeing ee.key
# Automate renewal via cron/systemd timer every 6 hours

# TLS 1.3 handshake extension (client must advertise support)
# ClientHello: delegated_credential(34) extension
# ServerHello: presents [ee.crt, dc-credential, proof with dc-priv]

Deploying Short-Lived Keys at CDN Scale Without Breaking Handshakes

Running this in production is less about cryptography and more about key management plumbing. You need a signer service that is highly available but tightly scoped — it only needs your end-entity private key and a small API to issue credentials to authenticated edge nodes. We keep ours in a VPC with no inbound internet access, fronted by mutual-TLS authenticated workers that request fresh credentials. The edge fetches a new credential well before expiry, so there's never a hard cutover. When we first rolled this out for a media customer doing 8 Tbps of TLS traffic, we measured the impact on p95 handshake time and were relieved to see it was identical to traditional private key termination, because there's no added network hop.

There's a catch you should plan for: client support. Delegated credentials require TLS 1.3 and explicit client support for the extension. All modern browsers have it — Chrome, Firefox, Safari, and Edge shipped support after the RFC was finalized — but you'll still see a long tail of older clients, bots, and enterprise TLS stacks that don't advertise it. Your edge has to gracefully fall back. Our implementation checks the ClientHello; if the extension is missing, we fall back to Keyless or to a traditionally provisioned certificate if the customer allows it as a fallback tier. Don't assume 100% coverage on day one. Log the extension presence rate for your traffic mix before you make delegated credentials your only path.

The operational wins, though, are real and measurable. Short-lived keys change your risk model from "protect this secret forever across 300 locations" to "protect this signing service and let the edge secrets expire daily." Audit teams understand that difference immediately. And for teams worried about key management complexity, the automation is actually simpler than traditional certificate renewal — you're signing a lightweight credential every few hours, not reissuing a full certificate chain through a CA.

Deployment Model Key Exposure Window p50 Handshake Latency (Edge) Origin Dependency Revocation Strategy
Shared Private Key (Traditional) 90 - 365 days (cert lifetime) 2 - 4 ms None after deploy OCSP / CRL for cert
Keyless SSL Zero (key stays at origin) 35 - 210 ms (depends on RTT to key server) Per-handshake RPC Disable key server
Delegated Credentials (24h validity) 24 hours (auto-expires) 2 - 5 ms Every 6 hours for renewal Expiry; no OCSP needed for DC
Delegated Credentials (7-day max) 7 days (RFC 9345 maximum) 2 - 5 ms Daily renewal Expiry

Practical Implementation: Automating Delegated Credential Rotation at the Edge

We learned the hard way that you don't want humans in the loop for short-lived credentials. If your delegated credential (DC) is valid for 7 days — and most deployments we run are 2-7 days, with Cloudflare famously using 7 days and a few high-security origins pushing 24-48 hours — you need fully automated issuance, distribution, and rotation. Our setup now treats DCs like OCSP staples: ephemeral, constantly refreshed, and never manually touched.

The flow's pretty straightforward once you've got it wired up. Your origin CA private key stays locked in an HSM or offline vault and signs a Delegated Credential that's bound to your end-entity certificate. That DC contains a fresh public key, a validity window (we enforce a max of 7 days, but 96 hours is our default), and a signature from the end-entity private key. You then push the DC private key — not the cert private key — to your edge nodes via your secrets distribution pipeline. We use a 6-hour rotation cron with a 50% overlap window, so edges always have two valid DCs in memory. That overlap saved us during a 14-minute HashiCorp Vault outage last March where no new DCs could be issued; clients just kept handshaking with the previous credential.

Here's the stripped-down issuance we run with a custom Go signer (BoringSSL's dcgen tool works similarly) on our intermediate signer box:

# Generate DC keypair on signer (P-256 for speed, ~0.3ms sign vs 1.2ms for P-384)
openssl ecparam -name prime256v1 -genkey -noout -out dc-private.pem
openssl ec -in dc-private.pem -pubout -out dc-public.pem

# Issue DelegatedCredential valid for 4 days (345600 seconds)
# --cert is your end-entity cert, --key is its PRIVATE key (HSM-backed)
dcgen --cert ee-cert.pem --key ee-private-hsm.pem \
  --dc-pub dc-public.pem \
  --validity 345600 \
  --out dc-credential.bin

# Distribute to edges - never write ee-private-hsm.pem to edge disk
vault kv put cdn/edges/dc-2025-08-13 [email protected]
vault kv put cdn/edges/dc-key [email protected]

# Edge Nginx/BoringSSL config snippet
ssl_delegated_credential /run/secrets/dc-credential.bin;
ssl_delegated_credential_key /run/secrets/dc-private.pem;

One gotcha we hit early: don't forget to monitor client support. Unlike standard TLS handshakes, DCs require the client to advertise delegated_credential in ClientHello. As of mid-2025 you're looking at ~92% coverage — Chrome 115+, Firefox 118+, Safari 17+ — but Java 8 clients and openssl s_client without BoringSSL will fall back to a full handshake to origin or fail if you enforce DC-only. We keep TLS Record Size Optimization: Balancing Latency and Throughput at the Edge in mind here too, since DC handshakes add ~300-500 bytes to the ServerHello; you don't want to blow your initial congestion window.

Advanced Patterns and Troubleshooting When Delegation Breaks

Once you've got basic rotation stable, the real CDN-scale problems start. Clock skew is the silent killer. A DC with a 24-hour validity will fail validation if your edge node's clock is 5 minutes fast or the client's is 5 minutes slow — tls implementations check valid_time against the client's wall clock with zero tolerance. We NTP-sync all edges to within 50ms and add a 300-second backdated not_before to absorb minor client drift. If you're seeing intermittent ERR_SSL_DELEGATED_CREDENTIAL_EXPIRED in Chrome, 90% of the time it's not your signer — it's a stale credential stuck in edge cache after a failed reload.

Another pattern we now run everywhere is key-type agility. Don't issue a single P-256 DC and call it done. We issue both P-256 and X25519 DCs and let the edge pick based on the ClientHello's key_share. It cuts handshake CPU by ~18% for mobile clients negotiating X25519 and gives you a fallback if you need to quickly deprecate a curve. This also plays nicely with TLS Fingerprinting Defense: Preventing Client Identification Through Handshake Analysis — rotating DC keys frequently naturally varies your handshake fingerprint, but if you're doing deterministic key selection per PoP, you can actually make fingerprinting easier. Randomize DC selection per handshake instead.

For troubleshooting, log aggressively on the edge. We emit a counter for dc_handshake_success, dc_client_unsupported_fallback, and dc_signature_invalid. If that last one spikes above 0.1% of handshakes, you've probably pushed a DC that wasn't signed by the current end-entity cert — classic mistake after a cert renewal where the old EE key signed a DC that's now presented alongside the new EE cert. The RFC requires the DC to be verifiable with the EE public key in the handshake; mismatched pairs fail hard. And if you're migrating away from old pinning setups, review Beyond Certificate Pinning: Modern Alternatives for CDN Trust Verification before you enforce DCs — some legacy mobile apps pinned the EE public key and will reject the DC key even though the browser trusts it.

Frequently Asked Questions

What happens if a CDN edge is compromised and the delegated private key is stolen?

That's the whole point — the blast radius is tiny. The attacker only gets the short-lived DC private key, not your long-term end-entity private key. They can impersonate you until the DC expires — at most 7 days per the spec, but we keep ours to 4 days, so practical max exposure is 96 hours. Once it expires, it's cryptographically useless. Revoke the DC by simply not re-issuing it, rotate the edge, and you're clean without revoking or reissuing your actual certificate. Compare that to a stolen shared EE key, which requires emergency revocation, CT log delays, and a global cert replacement.

Do delegated credentials work with all browsers and clients?

Not yet universally. Delegated Credentials are supported in all major browsers as of late 2024 — Chrome/Edge 115+, Firefox 118+, Safari 17+ — which covers about 92-94% of real user traffic for us. They are NOT supported in OpenSSL 1.1.1 or 3.x (only BoringSSL and NSS), Java's default JSSE, or older Python requests stacks. Your edge must gracefully fall back to a standard handshake or proxy to origin for unsupported clients. Always test with curl --tls-delegated-credentials via BoringSSL's curl fork and don't enforce DC-only unless you've measured your unsupported tail.

How is this different from just using short-lived certificates from Let's Encrypt?

Short-lived certs (e.g., 6-hour Let's Encrypt certs) still require the CA to sign each renewal and the private key is the same one on the edge. DCs are self-issued by you using your existing EE private key — no CA round-trip needed — and the key on the edge is a *different*, throwaway key. That means you can rotate edge keys every few hours without hitting ACME rate limits (50 certs/week/domain for Let's Encrypt) and without exposing your long-term key to the edge at all. We rotate DCs 42 times per week per PoP; doing that with short-lived certs would be operationally impossible.

Can I use delegated credentials with RSA certificates?

Technically yes, but you shouldn't. The spec allows any signature algorithm, but RSA DCs are slower (a 2048-bit RSA DC sign takes ~4.5ms vs 0.3ms for P-256 on our Xeon edges) and add ~600+ bytes to the handshake. All large-scale CDN deployments — including ours — use ECDSA (P-256) or Ed25519 EE certs to issue ECDSA/X25519 DCs. If your origin cert is still RSA, consider migrating to ECDSA first; you'll get DC support plus a 30-40% handshake latency win as a bonus. RSA DCs also trigger weird middlebox bugs we saw with old F5s stripping unknown extensions.

Related Articles

References & Standards: HTTP Working Group Specifications · web.dev Articles · IETF Datatracker