Managing TLS 1.3 Session Resumption in Distributed OpenSSL Environments
Learn how to implement TLS 1.3 session resumption in distributed environments using OpenSSL's PSK tickets and key callbacks to eliminate sticky session requirements.
28 Apr 2026, 09:17 UTC

The Problem: The "Sticky Session" Bottleneck
In TLS 1.2, session resumption often relied on Session IDs. The server stored the session state in its own memory, meaning a client returning to a load-balanced cluster had to hit the exact same server that handled the initial handshake, or the cluster had to synchronize a massive shared cache. This created a rigid dependency on "sticky sessions" at the load balancer level.
TLS 1.3 solves this by shifting the state from the server to the client using Pre-Shared Keys (PSK) delivered via session tickets. However, this introduces a new engineering challenge: if Server A issues a ticket, Server B cannot decrypt it unless both servers share the same Ticket Encryption Key (STEK). Without a strategy for key synchronization, your users will suffer the latency of full handshakes despite having valid tickets.
From Session IDs to PSK Tickets
In TLS 1.3, the server sends a NewSessionTicket message after the main handshake. This ticket contains the session state encrypted with a symmetric key known only to the server. When the client reconnects, it presents this ticket as a PSK (Pre-Shared Key) identity.
The primary advantage here is the reduction of round-trips. In a full handshake, the client and server must negotiate keys and verify certificates. In a resumed session, they derive new keys from the PSK contained in the ticket, potentially enabling 0-RTT (Zero Round Trip Time) where application data is sent in the first packet.
Implementing Distributed Key Management
To allow any server in a cluster to resume a session, you must implement a mechanism to rotate and share the ticket keys. OpenSSL provides a callback mechanism for this purpose: SSL_CTX_set_session_ticket_key_cb.
Instead of letting OpenSSL generate a random key at startup, you provide a function that retrieves the current key from a secure shared store (like Redis or a vault). This ensures that regardless of which backend receives the request, the STEK is consistent across the fleet.
Example: Configuring the Ticket Key Callback
While the full implementation requires a C wrapper, the logic follows this flow. You must define a callback that matches the SSL_CTX_set_session_ticket_key_cb signature. The callback is triggered when OpenSSL needs to encrypt a new ticket or decrypt a presented one.
// Conceptual logic for a ticket key callback
int ticket_key_callback(SSL *s, unsigned char *key_name, unsigned char *iv,
SSL_SESSION *session, int len, int write_key) {
if (write_key) {
// Server is issuing a ticket: Provide the CURRENT active key
copy_key_from_shared_store(key_name, iv, CURRENT_KEY_ID);
} else {
// Client presented a ticket: Look up the key by key_name
copy_key_from_shared_store(key_name, iv, LOOKUP_BY_NAME);
}
return 1; // Success
}
Required Permissions: This code runs within the application process managing the SSL context. It requires read/write access to your shared key store.
Risk: If the key_name is not tracked correctly, the server will fail to decrypt the ticket, forcing a fallback to a full handshake. This isn't a security failure, but it is a performance hit.
Trade-offs: 0-RTT and Forward Secrecy
Two critical engineering trade-offs emerge when implementing PSK resumption:
- The Replay Attack Risk: 0-RTT allows a client to send data before the handshake completes. Because this first flight is static, an attacker can capture the packet and replay it to the server. Mitigation: Only use 0-RTT for idempotent requests (e.g., HTTP GETs that don't change state) and implement a server-side replay cache.
- Forward Secrecy: If a Ticket Encryption Key is stolen, any session resumed with that key can be decrypted. Unlike the main TLS 1.3 handshake, which uses ephemeral Diffie-Hellman for every session, the ticket is a symmetric secret. Mitigation: Rotate your STEKs frequently (e.g., every 24 hours) and delete old keys immediately.
Verifying Resumption Performance
You can verify if your resumption strategy is working using the OpenSSL command-line tool. Run the following command from a client machine:
openssl s_client -connect yourserver.com:443 -reconnect
What to look for:
- The first connection should show a full handshake.
- The second connection (triggered by
-reconnect) should indicate a "Resumed" session or a PSK-based handshake. - If you are using a load balancer, run this across multiple requests to ensure different backend servers are successfully decrypting the tickets.
To confirm the actual network behavior, use Wireshark to filter for tls.record.content_type == 23. Look for the NewSessionTicket message following the Server Hello; if this is missing, resumption is disabled.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.