OpenSSL TLS 1.3 0‑RTT: Cutting Repeat‑Connection Latency Without Losing Track of Replay Risk
Learn how to enable OpenSSL 1.1.1+ TLS 1.3 0‑RTT early data to skip a handshake round trip, why replay attacks demand idempotent operations, and how to verify acceptance with s_server, s_client, and SSL_get_early_data_status.
31 Oct 2025, 17:48 UTC

The repeat-connection tax
Every time a TLS 1.3 client reconnects to a server it has seen before, it still pays a round trip. The client sends its Finished message and waits for the server's Finished before application data flows. On a WAN link with 100 ms RTT, that is a 100 ms tax on every resumed session. OpenSSL 1.1.1 and later let you avoid that tax for a narrow class of traffic by sending early data—application bytes inside the very first flight of the handshake.
What 0‑RTT actually does
TLS 1.3 0‑RTT (zero round-trip time) data is not a new handshake; it is a shortcut for session resumption. When a client holds a valid pre-shared key (PSK) from a previous connection, it can encrypt application data and attach it to the ClientHello. The server can decrypt and process that data immediately, before it has even sent its own Finished message. The full handshake still completes, but the application does not wait for it.
The catch is that the server cannot cryptographically prove the ClientHello is fresh. An attacker who captures the first flight can replay it. OpenSSL gives you the tools to bound that risk, but it does not remove it.
Enabling early data in OpenSSL
In code, the path is straightforward:
- Enable early data on the SSL object with
SSL_set_early_data_enabled(ssl, 1). - Write the initial payload with
SSL_write_early_data()rather thanSSL_write(). - On the server side, set an anti-replay window with
SSL_CTX_set_anti_replay_window()so duplicate early-data records are rejected. - After the handshake, inspect
SSL_get_early_data_status()to see whether the server accepted, rejected, or never saw the data.
These APIs require OpenSSL 1.1.1 or later built with TLS 1.3 support, which is the default in current distributions. The SSL_CONF_cmd interface can also toggle the same behavior for applications that use the configuration string approach.
A worked example with s_server and s_client
The OpenSSL command-line tools can demonstrate the flow without writing C. The commands below assume you are in a shell with a server certificate and key available, and that you have permission to bind to the chosen port.
Server (run in a terminal with access to server.crt and server.key):
openssl s_server -accept 4433 -cert server.crt -key server.key -early_data
Client (run in a second terminal):
openssl s_client -connect localhost:4433 -early_data
Because 0‑RTT only applies to resumption, the client must already hold a session ticket from a prior connection to the same server. If you are testing manually, connect once first to obtain a session, then reconnect with -early_data.
Expected checks:
- The client output should contain the string
Early data was sent. - The server should report
SSL_get_early_data_status == SSL_EARLY_DATA_ACCEPTEDfor the resumed session. - If the server rejects the data—because the anti-replay window already saw the record—the status becomes
SSL_EARLY_DATA_REJECTEDand the application must retry over the normal encrypted stream.
The replay boundary
The anti-replay window is a bounded cache of recently seen early-data records. It prevents trivial replay within that window, but it cannot prove freshness outside it. That leaves the application responsible for safety.
Treat early data as idempotent: an operation that produces the same result whether it runs once or twice. A GET request, a cache lookup, or a telemetry ping is safe. A POST that increments a counter, submits an order, or transfers funds is not. If an attacker replays a "debit $10" early-data record, the server may process it twice.
A practical rule: if the operation would be dangerous to repeat, do not send it with SSL_write_early_data(). Send it after the handshake completes and the server has confirmed freshness.
Trade-offs and limitations
0‑RTT trades cryptographic freshness for latency. The gain is real—one full round trip is removed—but it is only available on resumption, only for the first application write, and only when the server explicitly enables it. Session resumption is therefore a hard prerequisite: a cold connection cannot use early data.
Verification is the last step. After enabling the feature, confirm that your client actually sends early data and that your server accepts it. Then attempt a replay: capture the early-data packet and resend it. A correctly configured server should reject the duplicate and return SSL_EARLY_DATA_REJECTED or an alert. If it accepts the replay, your anti-replay window is misconfigured or your application is ignoring the status code.
Bottom line
OpenSSL's TLS 1.3 0‑RTT support can cut roughly one round trip from repeat connections, which matters for WAN clients and chatty protocols. Enable it with SSL_set_early_data_enabled(), guard the server with SSL_CTX_set_anti_replay_window(), and restrict early data to idempotent operations. Then verify with the command-line tools and a deliberate replay test before you ship it.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.