HTTP/3 isn’t just a faster HTTP/2; it’s a fundamentally different protocol built on UDP instead of TCP.
Let’s get an API Gateway serving HTTP/3 and QUIC. We’ll use Envoy Proxy as our gateway, which is a popular choice and has excellent HTTP/3 support.
First, you need a recent version of Envoy. HTTP/3 support is still relatively new and requires specific build flags. You’ll likely need to compile it yourself.
# Example build command (details will vary based on your OS and dependencies)
git clone --recursive https://github.com/envoyproxy/envoy
cd envoy
git checkout v1.28.0 # Or a later version with stable HTTP/3 support
bazel build -c opt --cxxopt=-D_GLIBCXX_USE_CXX11_ABI=0 //source/exe:envoy_static
The key here is bazel build -c opt --cxxopt=-D_GLIBCXX_USE_CXX11_ABI=0 //source/exe:envoy_static. The opt flag ensures optimization, and the _GLIBCXX_USE_CXX11_ABI=0 is often required for compatibility with BoringSSL, which Envoy uses for TLS. The envoy_static target builds a single statically linked binary, which simplifies deployment.
Next, we need to configure Envoy. This involves setting up listener for QUIC/HTTP/3 and a cluster to forward traffic to.
Here’s a snippet of an Envoy configuration (envoy.yaml) for serving HTTP/3:
admin:
access_log_path: /tmp/admin_access.log
address:
socket_address:
address_value: 0.0.0.0
port_value: 9901
static_resources:
listeners:
- name: listener_quic
address:
socket_address:
address_value: 0.0.0.0
port_value: 443 # Standard HTTPS port
listener_filters:
- name: envoy.filters.listener.tls_inspector
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector
filter_chains:
- filter_chain_match:
transport_protocol: QUIC
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: http3_listener
codec_type: HTTP3
http_filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
route_config:
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: http_service_cluster
- filter_chain_match:
transport_protocol: RAW_UPGRADE
transport_socket:
name: envoy.transport_sockets.quic
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.quic.v3.QuicDownstream
downstream_tls_context:
require_client_certificate: false
session_ticket_keys:
- inline_bytes: "LS0tLS1CRUdJTiBQUk1BVEUgS0VZLS0tLS0K..." # Replace with your actual key
tls_certs:
- certificate_chain:
filename: "/etc/ssl/certs/your_domain.crt" # Replace with your cert
private_key:
filename: "/etc/ssl/private/your_domain.key" # Replace with your key
- filters: # Fallback for HTTP/1.1 and HTTP/2
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: http_listener
codec_type: AUTO
http_filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
route_config:
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: http_service_cluster
clusters:
- name: http_service_cluster
connect_timeout: 0.25s
type: LOGICAL_DNS
dns_lookup_family: V4_ONLY
lb_policy: ROUND_ROBIN
# For HTTP/1.1 and HTTP/2
# http2_protocol_options: {} # Uncomment if your backend supports HTTP/2
# For HTTP/3 (QUIC) - this is usually handled by the listener, but can be specified
# If your backend *also* supports HTTP/3, you'd configure it here.
# For simplicity, we'll assume the backend speaks HTTP/1.1 or HTTP/2.
# If your backend speaks HTTP/3, you'd need a QUIC transport socket here.
# Example:
# transport_socket:
# name: envoy.transport_sockets.quic
# typed_config:
# "@type": type.googleapis.com/envoy.extensions.transport_sockets.quic.v3.QuicUpperDownstream
# upstream_tls_context:
# sni: your_domain.com
# # ... other TLS context options
hosts:
- socket_address:
address: 127.0.0.1
port_value: 8080 # Your backend service port
Let’s break down the crucial parts of this configuration.
The listener_quic is where the magic happens. It’s configured to listen on port 443. The envoy.filters.listener.tls_inspector is vital because it needs to determine if the incoming connection is TLS (for HTTP/2 or HTTP/1.1) or QUIC (which uses UDP and has its own TLS handshake).
The filter_chain_match with transport_protocol: QUIC is specifically for QUIC connections. Inside this, codec_type: HTTP3 tells Envoy to use its HTTP/3 codec. The envoy.filters.network.http_connection_manager is configured to handle HTTP/3 traffic.
The transport_socket with envoy.transport_sockets.quic.v3.QuicDownstream handles the QUIC-specific TLS handshake. This is where you provide your TLS certificates (your_domain.crt, your_domain.key) and session ticket keys. The session_ticket_keys are important for enabling 0-RTT or 1-RTT resumption, which is a key performance benefit of QUIC. You’ll need to generate these keys.
The filter_chain_match with transport_protocol: RAW_UPGRADE and the subsequent filters block is for handling standard TCP-based protocols like HTTP/1.1 and HTTP/2. The codec_type: AUTO allows Envoy to automatically detect and handle HTTP/1.1 or HTTP/2. This ensures that clients that don’t support HTTP/3 can still connect.
The cluster named http_service_cluster defines how Envoy forwards requests. It points to your backend service, which in this example is running on 127.0.0.1:8080. For simplicity, we’ve assumed the backend speaks HTTP/1.1. If your backend also supports HTTP/3, you would configure a transport_socket with QuicUpperDownstream in the cluster definition, similar to how the listener is configured for downstream QUIC.
To actually run this, you’d start Envoy with your configuration:
./envoy -c envoy.yaml
Testing this requires a client that supports HTTP/3. Modern browsers like Chrome and Firefox do. You can also use tools like curl (with a recent version compiled with HTTP/3 support) or nghttp.
# Example using curl (ensure it's compiled with nghttp2 and cares about HTTP/3)
curl --http3 https://your_domain.com/
One of the more subtle aspects of HTTP/3 is how it handles head-of-line blocking. Unlike HTTP/2, where a lost packet on a TCP connection could stall all streams multiplexed over that connection, HTTP/3’s QUIC transport protocol provides stream-level multiplexing and loss recovery. This means if a packet for one HTTP/3 stream is lost, only that specific stream is affected, while other streams on the same QUIC connection can continue to make progress. This is a significant performance improvement, especially on lossy networks.
The next hurdle you’ll likely encounter is optimizing QUIC connection establishment and tuning its congestion control algorithms for your specific network environment.