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.

Want structured learning?

Take the full Http3 course →