TL;DR — TLS 1.3 0‑RTT resumption lets a client send data on the first flight after a cached session, shaving milliseconds off latency. It requires careful key‑management, replay mitigation, and proper server‑side configuration to stay secure in production.
TLS 1.3 introduced a powerful performance optimization: zero round‑trip (0‑RTT) resumption. By reusing cryptographic material from a prior handshake, a client can start transmitting application data immediately, reducing the perceived latency of HTTPS calls—especially valuable for mobile apps and API gateways. However, the speed boost comes with trade‑offs around replay attacks, forward secrecy, and operational complexity. This post walks through the end‑to‑end architecture, examines the security implications, and provides concrete deployment patterns for engineers running services at scale.
Architecture Overview
TLS 1.3 Handshake Recap
Before diving into 0‑RTT, it helps to recall the baseline TLS 1.3 handshake:
- ClientHello – includes supported cipher suites, key share, and a random nonce.
- ServerHello – selects a cipher suite, sends its own key share, and optionally a HelloRetryRequest.
- EncryptedExtensions, Certificate, CertificateVerify, Finished – all encrypted with keys derived from the shared secret.
The handshake normally requires one full round‑trip (ClientHello → ServerHello) before any application data can be sent.
Zero Round‑Trip Resumption Flow
When a client has a valid PSK (Pre‑Shared Key) from a previous session, it can embed that PSK in the ClientHello and request early data. The server validates the PSK and, if it accepts, immediately allows the client to send encrypted application data on the same flight. The simplified flow looks like this:
Client Server
| ClientHello (PSK, early_data) --> |
| <-- | ServerHello + EncryptedExtensions
| Early Application Data (0‑RTT) --> |
| <-- | Finished
| Finished (post‑handshake) --> |
Key points:
- The PSK is derived from a prior TLS 1.3 handshake using the TLS exporter mechanism.
- Early data is encrypted with early traffic keys that are derived before the server’s Finished message.
- The server must decide whether to accept the PSK based on its own cache and policy (e.g., age, client identity).
Security Trade‑offs
Replay Attacks
Because early data is sent before the server authenticates the client, an attacker who intercepts a 0‑RTT packet can replay it to the server. This is especially dangerous for idempotent HTTP methods (GET) but can be catastrophic for state‑changing methods (POST, PUT).
Mitigation strategies:
- Stateless replay detection – use a nonce or timestamp inside the early payload and have the server reject duplicates. See the approach described in the IETF draft on replay protection for 0‑RTT.
- Application‑level idempotency – design APIs to be safe against duplicate submissions (e.g., use unique request IDs).
- Selective enabling – only enable 0‑RTT for read‑only endpoints or low‑risk services.
Forward Secrecy Impact
TLS 1.3 guarantees forward secrecy (FS) for the full handshake, but early data uses early traffic keys that are not forward‑secret because they are derived from the PSK alone. If an adversary later compromises the server’s long‑term private key, they can decrypt captured early data.
Mitigation:
- Short PSK lifetimes – configure the server to expire PSKs after a few minutes (e.g.,
max_early_data_sizein Nginx). - Hybrid approach – combine 0‑RTT with post‑handshake authentication (e.g., TLS‑auth or application‑level token exchange) to re‑authenticate after the handshake.
Compatibility Concerns
Not all clients or middleboxes support TLS 1.3 0‑RTT. Enabling it blindly can lead to handshake failures or downgrade attacks. Most modern browsers (Chrome, Edge, Firefox) support it, but older Java libraries or embedded devices may not.
Best practice:
- Feature detection – send a normal ClientHello first; if the server advertises
early_datain its EncryptedExtensions, the client can retry with a PSK. - Graceful fallback – configure your load balancer to accept both 0‑RTT and regular TLS connections on the same port.
Patterns in Production
When to Enable 0‑RTT
| Scenario | Recommendation |
|---|---|
| Public API serving mobile clients | Enable for read‑only endpoints (GET/HEAD). |
| Internal microservice RPC (gRPC) | Enable if latency budget < 5 ms and idempotent. |
| Payment processing or auth flows | Do not enable – risk of replay outweighs latency gain. |
| Content delivery (static assets) | Strong candidate – cacheable, low risk. |
Monitoring and Alerting
- Early data volume – track
ssl_early_data_bytes_sent(Nginx) orhttp_early_data_total(Envoy) to ensure the feature is actually being used. - Replay detection metrics – instrument your application to emit a
early_data_replaycounter when duplicate nonces are seen. - PSK expiration alerts – watch for spikes in
ssl_psk_reusefailures, which may indicate mis‑configuration or aggressive cache eviction.
Rolling Deployment
When rolling out 0‑RTT to a fleet:
- Canary – enable on 1 % of pods, monitor latency and replay metrics.
- Gradual ramp – increase to 10 %, then 50 %, ensuring no surge in error rates.
- Full rollout – once stability is confirmed, update the TLS config on all ingress points.
Automate the rollout with Argo Rollouts or Spinnaker, and keep a feature flag (e.g., ENABLE_TLS13_0RTT) to toggle quickly in case of incidents.
Practical Deployment Guides
Nginx Configuration
# /etc/nginx/conf.d/tls13_0rtt.conf
server {
listen 443 ssl http2;
ssl_certificate /etc/ssl/certs/example.com.pem;
ssl_certificate_key /etc/ssl/private/example.com.key;
ssl_protocols TLSv1.3;
ssl_prefer_server_ciphers off;
# Enable 0‑RTT
ssl_early_data on;
# Limit early data to 16 KB (adjust per use‑case)
ssl_early_data_max_size 16384;
# Optional: reject replayed early data at the application layer
proxy_set_header X-Early-Data $ssl_early_data;
location /api/ {
proxy_pass http://backend;
}
}
Key directives:
ssl_early_data on;– tells Nginx to accept early data.ssl_early_data_max_size– caps the amount of early data per connection.$ssl_early_data– a variable that indicates whether the request arrived via 0‑RTT; useful for logging.
Envoy Proxy Settings
# envoy.yaml (excerpt)
static_resources:
listeners:
- name: listener_0
address:
socket_address: { address: 0.0.0.0, port_value: 443 }
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
http_filters:
- name: envoy.filters.http.router
codec_type: AUTO
early_data_config:
enable: true
max_early_data: 16384
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
common_tls_context:
tls_params:
tls_maximum_protocol_version: TLSv1_3
tls_certificate_sds_secret_configs:
- name: "example_cert"
sds_config:
path: "/etc/envoy/certs/example.com.yaml"
early_data_config:
enable: true
max_early_data: 16384
Envoy’s early_data_config mirrors Nginx’s options but gives you fine‑grained control per listener.
OpenSSL Command‑Line for Testing
# Generate a self‑signed cert (for dev only)
openssl req -x509 -newkey rsa:2048 -nodes -keyout key.pem -out cert.pem -days 365 -subj "/CN=localhost"
# First full handshake to obtain a PSK
openssl s_client -connect localhost:4433 -tls1_3 -servername localhost -cert cert.pem -key key.pem -ign_eof -quiet \
-msg -debug -psk_identity "test-psk" -psk "a1b2c3d4e5f6"
# Subsequent 0‑RTT handshake using the same PSK
openssl s_client -connect localhost:4433 -tls1_3 -servername localhost -early_data "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n" \
-psk_identity "test-psk" -psk "a1b2c3d4e5f6" -quiet
The -early_data flag forces OpenSSL to send data on the first flight, letting you verify that the server accepts it.
Key Takeaways
- TLS 1.3 0‑RTT can shave 1–2 RTT (≈10‑30 ms) off latency for high‑frequency client‑server interactions.
- The primary security concern is replay attacks; mitigate with nonces, short PSK lifetimes, and selective endpoint enablement.
- Forward secrecy is weakened for early data; keep PSK windows tight and consider post‑handshake authentication.
- Production‑ready patterns include canary rollouts, early‑data monitoring, and application‑level idempotency.
- Real‑world deployments on Nginx and Envoy require just a few configuration lines, but thorough testing (OpenSSL, integration tests) is essential before full rollout.
Further Reading
- RFC 8446 – TLS 1.3 – the official specification that defines 0‑RTT.
- TLS 1.3 0‑RTT in the Wild – Cloudflare Blog – a production case study with performance numbers.
- Mozilla TLS Configuration Generator – recommended cipher suites and best‑practice settings for TLS 1.3.
- IETF draft on replay protection for early data – detailed mitigation techniques.