HTTP/3 0-RTT is a handshake optimization that allows clients to send application data in the very first packet to a server, effectively skipping the round-trip time typically required for connection establishment.

Let’s see it in action. Imagine a client wanting to fetch a resource from a server that supports HTTP/3 0-RTT.

Client -> Server: ClientHello (with 0-RTT data)
Server -> Client: ServerHello, Certificate, EncryptedExtensions, (if 0-RTT data is valid) Application data

If the 0-RTT data is accepted, the client immediately receives the requested resource, and the connection is established. This is in contrast to HTTP/1.1 or HTTP/2, where the TLS handshake (requiring at least one round trip) and the HTTP handshake (another round trip) must complete before any application data can be sent.

The core problem HTTP/3 0-RTT solves is connection latency. Traditional web connections involve several steps:

  1. DNS Resolution: Finding the IP address of the server.
  2. TCP Handshake: Establishing a reliable connection (SYN, SYN-ACK, ACK). This is one round trip.
  3. TLS Handshake: Securing the connection (ClientHello, ServerHello, Certificate, etc.). This typically takes one or two round trips depending on the TLS version and cipher suite.
  4. HTTP Handshake: Negotiating HTTP/2 or HTTP/3 parameters.
  5. Application Data: Finally, sending the actual request and receiving the response.

Each round trip is a delay measured by the speed of light across the network. For users geographically distant from the server, this latency can be significant. HTTP/3, built on QUIC (which uses UDP), streamlines this process. QUIC integrates the transport and TLS handshakes.

With HTTP/3 0-RTT, the client, having previously communicated with the server, can send its initial ClientHello message along with encrypted application data. This data is encrypted using keys derived from a previous session. The server, upon receiving the ClientHello, can decrypt the 0-RTT data if the cryptographic material is still valid. If it is, the server can immediately send back the requested data within its ServerHello response.

Here’s how you might configure a server (Nginx example) to support HTTP/3 and 0-RTT:

server {
    listen 443 quic reuseport;
    listen 443 ssl http2; # Fallback for non-QUIC clients

    server_name example.com;

    ssl_certificate /etc/nginx/ssl/example.com.crt;
    ssl_certificate_key /etc/nginx/ssl/example.com.key;

    # Enable HTTP/3
    add_header Alt-Svc 'h3=":443"; ma=2592000';

    # Enable 0-RTT (requires a pre-shared key cache)
    ssl_early_data on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    # ... other configurations
}

The add_header Alt-Svc directive tells the client that HTTP/3 is available on port 443. ssl_early_data on; is the key directive for enabling 0-RTT. This requires ssl_session_cache to be configured so that the server can store and retrieve session resumption information, which includes the cryptographic material needed to encrypt and decrypt 0-RTT data.

The client’s role is also crucial. Modern browsers and HTTP clients will automatically attempt to use 0-RTT if the server supports it and if they have previously established a connection with that server (and thus have the necessary session resumption tickets or pre-shared keys).

The actual mechanism involves the server issuing a "session ticket" (or using a pre-shared key) during a full TLS handshake. This ticket contains cryptographic material that the client can use to encrypt data for future connections. When the client connects again, it sends this ticket (or the derived keys) along with its initial ClientHello. The server uses the ticket to decrypt the 0-RTT data and verify its authenticity. If the ticket is valid and the data is well-formed, the server can process it immediately.

However, 0-RTT data is not safe from replay attacks. The server must be able to distinguish between legitimate, fresh requests and replayed ones. This is typically handled by the application layer, or by mechanisms within the TLS protocol that allow the server to reject older or duplicate nonces. For instance, a server might only accept 0-RTT data for requests that are idempotent (like a GET request) and have a timestamp or nonce that is within a certain acceptable window. If the server cannot safely process the 0-RTT data (e.g., it’s a replay, or the session ticket is too old), it will simply ignore the data and proceed with a full handshake, sending the application data after the handshake is complete.

The most surprising true thing about 0-RTT is that while it eliminates connection latency, it doesn’t eliminate the possibility of the server not accepting the 0-RTT data. The client sends it optimistically, but the server has the final say based on cryptographic validity and replay protection.

The next hurdle for many developers is understanding how to ensure their application-layer protocols are safe to send in 0-RTT, particularly regarding idempotency and replay protection.

Want structured learning?

Take the full Http3 course →