A full TLS 1.3 handshake still costs one round-trip before any application data flows. At CDN scale, that round-trip is repeated billions of times a day. Session resumption — via session tickets and Pre-Shared Keys (PSKs) — cuts that cost to zero RTT on repeat visits, but only if your ticket distribution, rotation, and replay protection are engineered correctly across a distributed edge.
For a global CDN, TLS performance is not just about cipher suites. It is about how often you can avoid the handshake entirely. This guide breaks down how modern resumption works, when 0-RTT helps (and when it hurts), and the operational patterns high-performing CDNs use to keep resumption rates above 85% without compromising security.
Key Takeaway: TLS 1.3 replaced both Session IDs and TLS 1.2 tickets with a single PSK mechanism. Your CDN's job is to issue, encrypt, distribute, and rotate those PSKs securely across hundreds of PoPs while enforcing strict anti-replay policies for 0-RTT.
1. Why Handshake Latency Matters More at the Edge
A cold TLS 1.3 handshake requires 1-RTT: ClientHello → ServerHello + Finished → Client Finished. On a 70ms transatlantic link, that is 70ms of dead time before the first HTTP request byte. Add TCP and DNS resolution and you are easily at 180-250ms to first byte for a new connection.
Resumption changes the economics. Instead of negotiating fresh key material, the client presents proof of a prior session. The server validates it and derives new traffic keys. For returning visitors — which are 60-75% of traffic for most sites — this saves 30-50% of connection setup time and significant CPU on both sides. Handshake crypto (especially ECDHE) is expensive; resumption avoids a full asymmetric exchange.
At CDN scale, two additional factors amplify the impact. First, connection coalescing and HTTP/2/3 reuse mean fewer handshakes, but mobile networks and aggressive browser connection limits still churn connections constantly. Second, understanding your CDN architecture is critical: Anycast, load balancing, and PoP affinity determine whether a resumption attempt actually lands on a server that can decrypt the ticket.
2. Resumption Fundamentals: From Session IDs to TLS 1.3 PSKs
Three generations of resumption exist. You will still see all three in the wild, but TLS 1.3 PSK is the target state.
| Metric | Edge Processing | Origin Processing | Improvement |
|---|---|---|---|
| Latency (p50) | 8–15 ms | 120–300 ms | 15–20x faster |
| Cache Hit Ratio | 85–95% | N/A | Reduces origin load |
| TLS Handshake | 1–5 ms (nearby PoP) | 50–150 ms | 10–30x faster |
| Bandwidth Cost | $0.01–0.04/GB | $0.05–0.12/GB | 3–5x cheaper |
| Availability | 99.99% (multi-PoP) | 99.9% (single DC) | 10x fewer outages |
Session IDs (TLS 1.2 and earlier, Stateful)
The server stores session state (master secret, cipher suite) in a cache indexed by a 32-byte Session ID. The client echoes the ID in its ClientHello. This is simple but does not scale for CDNs: every edge server needs access to the same cache, requiring either sticky routing or a globally replicated session cache. Most CDNs disabled this years ago.
Session Tickets (TLS 1.2 RFC 5077, Stateless)
The server encrypts the session state into a ticket using a Ticket Encryption Key (TEK), typically AES-128-CBC + HMAC-SHA256 or AES-GCM. It sends the ticket to the client in a NewSessionTicket message. On resumption, the client sends the ticket back; the server decrypts it and recovers the state. No server-side storage is needed, which is ideal for stateless edge nodes. The downside: if the TEK leaks, all tickets encrypted with it can be decrypted, and TLS 1.2 tickets do not provide forward secrecy for resumed sessions.
PSK Resumption (TLS 1.3 RFC 8446)
TLS 1.3 unifies everything into PSKs. After a successful handshake, the server sends one or more PSK identities (also called tickets) derived from the resumption master secret. The client stores them. On the next connection, it sends a pre_shared_key extension with the PSK identity, a binder (HMAC proving it owns the PSK), and still performs an ECDHE key share. The server validates the binder, derives new handshake and application traffic secrets combining the PSK and the fresh ECDHE secret, and can optionally accept early data.
Crucially, TLS 1.3 PSKs are always combined with ECDHE, restoring forward secrecy even for resumed sessions. Tickets are also single-use and bound to the original SNI and ALPN, reducing cross-protocol attacks.
Figure 1: TLS 1.3 handshake comparison. 0-RTT sends application data with the first flight, but requires anti-replay handling at the edge.
3. TLS 1.3 PSK in Detail: What the CDN Actually Validates
When a client attempts PSK resumption, the edge must do four checks in microseconds:
- PSK identity lookup and decryption: The ticket is typically an encrypted blob containing the resumption secret, creation timestamp, cipher suite, and max early data size. The edge tries each active Ticket Encryption Key until decryption succeeds.
- Binder verification: The client computes an HMAC over the ClientHello (truncated) using a binder key derived from the PSK. This proves possession without revealing the PSK. If the binder fails, the server falls back to a full handshake.
- Freshness and lifetime: TLS 1.3 tickets have a lifetime hint (usually 0-7 days, CDN default 2-4 hours). The server enforces its own max lifetime, often 24 hours, regardless of the hint. Expired tickets are rejected.
- Policy binding: The PSK is bound to SNI, ALPN, and sometimes client IP. A PSK issued for cdn.example.com cannot be used for api.example.com.
If all checks pass and the client offered early data, the server decides whether to accept it. Acceptance is signaled via the early_data extension in EncryptedExtensions. From then on, new traffic keys are derived as HKDF(PSK, ECDHE_shared_secret), so even if the PSK is later compromised, past sessions remain secure due to the ephemeral ECDHE contribution.
| Method | TLS Version | Server State | CDN Scalability | Security Properties |
|---|---|---|---|---|
| Session IDs | 1.2 and below | Stateful cache | Poor — requires shared cache or stickiness | No ticket key to leak; but cache theft risk |
| Session Tickets (RFC 5077) | 1.2 | Stateless (encrypted ticket) | Good if TEKs synced globally | No forward secrecy on resumption; TEK rotation critical |
| PSK without 0-RTT | 1.3 | Stateless (encrypted PSK) | Excellent — stateless + forward secrecy via ECDHE | Forward secrecy preserved; single-use tickets |
| PSK with 0-RTT | 1.3 | Stateless + anti-replay window | Good but needs replay cache | Replayable early data; requires idempotency checks |
4. 0-RTT: Where the Performance Win Meets Replay Reality
0-RTT is the headline feature of TLS 1.3 resumption. By sending early data (typically an HTTP GET) alongside the ClientHello, the client eliminates the round-trip entirely. For a CDN serving static assets, images, or cached HTML, this can shave 80-120ms off Largest Contentful Paint on high-latency networks — a direct boost to Core Web Vitals.
The catch is cryptographic: 0-RTT data is not forward-secure in the same way and is replayable. An attacker who captures a ClientHello + early data flight can replay it to another edge node. Since the PSK is still valid, the server will accept it again unless it tracks what it has already seen.
This is not theoretical. Replaying a GET /api/add-to-cart or POST /transfer could have real side effects. RFC 8446 is explicit: 0-RTT data must be considered replayable and should only be used for idempotent operations.
CDN Anti-Replay Strategies
- Single-use tickets: Issue each PSK with a unique ticket age and store its hash in a distributed cache (Redis, DynamoDB Global) for the replay window (typically 5-15 minutes). Reject duplicates. This is the strongest defense but adds a cache lookup to the hot path.
- ClientHello freshness: Enforce strict ticket age checks (e.g., allow ±10 seconds of clock skew) and reject tickets older than 2 hours even if the lifetime hint is longer.
- Method and path allowlisting: Only accept early data for GET and HEAD to safe, cacheable origins. Immediately return 425 Too Early for anything else and force a retry without early data.
- Strike register at the edge: Use a bloom filter or sharded in-memory window per PoP for sub-millisecond replay detection, then asynchronously replicate to other PoPs. Cloudflare and Fastly publish variants of this approach.
Most CDNs default to 0-RTT for static asset hosts and disable it for dynamic APIs. If you enable it broadly, you must ensure your origin is replay-safe — exactly the same discipline required for robust DDoS protection and WAF rule design.
5. CDN PSK Strategies: Keys, Rotation, and Global Distribution
Getting resumption rates high is an operational problem, not just a protocol one. A client that received a ticket from PoP-A in Ashburn will likely resume via PoP-B in Chicago after anycast re-routing. If PoP-B cannot decrypt the ticket, resumption fails and you fall back to a full handshake.
Ticket Encryption Key Management
Modern CDNs use a key ring, not a single key:
- Active key: Used for encrypting new tickets (1 key).
- Standby keys: Used only for decrypting tickets issued recently (2-3 keys).
- Rotation: Generate a new 256-bit key every 12-24 hours, promote it to active, demote the oldest standby to retired. Distribute via a control plane (e.g., HashiCorp Vault + config distribution) to all edge nodes within 60 seconds.
- Encryption: Use AEAD — AES-256-GCM or ChaCha20-Poly1305 — with a key ID in the ticket header so the edge can try the correct key first.
Key distribution must be strongly consistent. A 5-minute propagation delay can drop global resumption success by 8-12% during rotation. Some providers use a hierarchical push: control plane → regional aggregators → PoPs, with mTLS and signed key bundles.
Ticket Lifetime Tuning
Longer lifetimes improve resumption rates but increase the window for replay and key compromise. The sweet spot for most CDNs is 2-4 hours for browser clients and up to 24 hours for mobile apps with stable connections. TLS 1.3 also supports ticket_age_add obfuscation to prevent tracking — ensure your edge randomizes it per ticket.
Resumption and Session Affinity
Do not rely on session affinity to make resumption work. Anycast and consistent hashing already provide PoP affinity, but failover, PoP maintenance, and client mobility break it. Design for stateless resumption everywhere; use affinity only as a performance optimization for the replay cache.
6. Implementation: Configuring the Edge
Below are production-tested patterns for enabling PSK resumption and safe 0-RTT. Adapt paths and key management to your CDN or origin stack.
# /etc/nginx/conf.d/tls_resumption.conf
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:50m; # fallback for TLS 1.2 IDs, 50MB ~ 200k sessions
ssl_session_timeout 4h;
ssl_session_tickets on;
ssl_session_ticket_key /etc/nginx/tickets/ticket.key; # current active key
# For key rotation, provide multiple keys: first is for encryption
ssl_session_ticket_key /etc/nginx/tickets/ticket_prev1.key;
ssl_session_ticket_key /etc/nginx/tickets/ticket_prev2.key;
# TLS 1.3 0-RTT - enable only for safe origins
ssl_early_data on;
# Enforce replay safety at application layer
if ($request_method !~ ^(GET|HEAD)$) {
return 425; # Too Early - client will retry without early data
}
# Pass early-data flag to origin for logging
proxy_set_header Early-Data $ssl_early_data;
#!/bin/bash
# rotate_ticket_keys.sh - runs hourly on control plane
set -euo pipefail
NEW_KEY=$(openssl rand 80 | base64) # 48 bytes key_name + 32 bytes AES key
KEY_ID=$(date +%s)
vault kv put cdn/ticket-keys/$KEY_ID value="$NEW_KEY" ttl="72h"
# Distribute to all PoPs via config service
/usr/local/bin/cdn-config-push --type ticket_keys --id $KEY_ID
# Keep only last 3 keys active, revoke older
vault kv list cdn/ticket-keys/ | tail -n +4 | xargs -r vault kv delete
echo "Rotated ticket key $KEY_ID at $(date -u +%FT%TZ)"
For managed CDNs (Cloudflare, Akamai, Fastly), these knobs are exposed as dashboard toggles: “TLS 1.3”, “0-RTT”, and “Session Ticket Rotation”. Verify that your provider synchronizes keys globally — ask for their rotation interval and anti-replay method before enabling 0-RTT on dynamic traffic.
7. Measuring and Tuning Resumption Performance
Track these metrics per PoP and globally:
- Resumption rate: resumed_handshakes / total_handshakes. Target >80% for TLS 1.3 PSK, >90% for returning users within the ticket lifetime.
- 0-RTT acceptance rate: accepted_early_data / offered_early_data. If this is low (<50%), clients are offering early data that you reject — check method filtering and ticket age.
- Fallback rate: Full handshakes after a PSK was offered. High fallback often means key desync or binder failure due to SNI mismatch.
- Time to First Byte (TTFB) delta: Compare TTFB for resumed vs full handshakes. Expect 25-60ms improvement for PSK, 70-110ms for 0-RTT on intercontinental paths.
Export these via Prometheus/OpenTelemetry and correlate with image optimization and asset caching — 0-RTT matters most for small, cacheable assets where handshake overhead dominates transfer time. Also watch CPU: successful resumption reduces handshake CPU by ~40%, freeing edge cores for WAF and compression.
Operational Tip: Start with PSK without 0-RTT, measure resumption rate for a week, then enable 0-RTT only for a canary host serving static assets (e.g., assets.example.com). Monitor replay cache hit rate and origin 425 responses before rolling out broadly.
Frequently Asked Questions
Is 0-RTT safe to enable on a CDN for all traffic?
No. Only enable 0-RTT for idempotent, replay-safe requests such as GET for static assets, images, and cached HTML. Disable it for POST, PUT, DELETE, and any authenticated or state-changing API. Most CDNs let you scope early data to specific hosts or paths — use that to limit risk.
How often should CDN TLS session ticket keys be rotated?
Every 12 to 24 hours is standard. Keep the current key for encryption and the previous 2–3 keys for decryption only. This bounds exposure if a key leaks and improves forward secrecy while keeping resumption success high. Automate distribution so all PoPs receive new keys within a minute.
Why does TLS session resumption fail when users hit different CDN PoPs?
Because the new PoP cannot decrypt the ticket. This happens when ticket encryption keys are not synchronized globally or when you use stateful Session IDs without a shared cache. Anycast routing can send a resumption attempt to any PoP, so you need globally distributed TEKs or a shared ticket store for reliable resumption.
Does TLS 1.3 PSK resumption provide forward secrecy?
Yes. TLS 1.3 always performs an ECDHE exchange even when resuming with a PSK. The PSK authenticates the prior session, but fresh ephemeral keys are mixed into the final traffic secrets, so compromising the ticket key does not decrypt past resumed sessions. This fixes the main weakness of TLS 1.2 tickets.
Next steps: Review your edge TLS configuration against the rotation and anti-replay checklist above, then validate with a real-user measurement of resumption rate and TTFB by PoP. Pair session resumption with persistent connection tuning and edge caching for the largest end-to-end gain.