HTTP/3’s UDP-based QUIC transport layer is so fundamentally different from TCP that it feels less like an upgrade and more like a complete protocol rewrite.
Let’s get HAProxy serving HTTP/3. We’ll use a common setup: HAProxy listening on UDP/443 for QUIC, with TLS termination, and then forwarding to an HTTP/1.1 backend.
First, ensure your HAProxy version supports HTTP/3. This means HAProxy 2.4 or later, built with the USE_QUIC option enabled. You can check this with haproxy -vv. Look for USE_QUIC in the output. If it’s not there, you’ll need to recompile or find a pre-built binary with QUIC support.
Here’s a minimal haproxy.cfg snippet for HTTP/3:
frontend http_frontend
bind *:443 ssl crt /etc/ssl/private/mycert.pem udp
mode http
http-request redirect scheme https code 301 if !{ ssl_fc } # Redirect HTTP to HTTPS
http-strict-transport-security max-age=31536000; # HSTS header
# QUIC specific settings
http-method set-if QUIC
http-request set-header X-Forwarded-Proto https
http-request set-header X-Forwarded-Port 443
http-request add-header Alt-Svc "h3=\":443\"; ma=86400" # Announce HTTP/3 support
default_backend http_backend
backend http_backend
mode http
server http11_server 127.0.0.1:8080 check
Let’s break this down.
The bind *:443 ssl crt /etc/ssl/private/mycert.pem udp line is key. We’re binding to port 443, enabling SSL/TLS with our certificate, and crucially, adding udp. This tells HAProxy to listen for QUIC connections on this port.
The http-method set-if QUIC directive is a HAProxy-specific way to identify QUIC requests. When HAProxy receives a connection that it identifies as QUIC, this condition becomes true.
The http-request add-header Alt-Svc "h3=\":443\"; ma=86400" line is how we advertise HTTP/3. The Alt-Svc (Alternative Service) header tells the client that this service is also available via HTTP/3 on port 443 for the next 86400 seconds (1 day). Browsers that support HTTP/3 will see this and attempt to establish a QUIC connection on their next visit.
The http-method set-if QUIC is a HAProxy specific way to detect QUIC. When HAProxy receives a QUIC connection on the UDP socket, this condition is true. This is used to set specific headers or perform actions for QUIC requests.
The http-request set-header X-Forwarded-Proto https and http-request set-header X-Forwarded-Port 443 are standard practice for indicating to the backend application the original protocol and port.
The backend http_backend section remains largely the same as for HTTP/1.1 or HTTP/2. HAProxy terminates the QUIC connection (and TLS) and then forwards the HTTP request over TCP to your backend.
The server http11_server 127.0.0.1:8080 check is your backend server, listening on TCP port 8080. It doesn’t need to understand HTTP/3 itself; HAProxy handles the translation.
When a client supports HTTP/3 and sees the Alt-Svc header, it will attempt to establish a QUIC connection. HAProxy will accept this UDP connection, perform the TLS handshake over QUIC, and then process the HTTP request. It then establishes a TCP connection to your backend server and sends the request as HTTP/1.1.
This setup allows you to gradually roll out HTTP/3 without requiring your backend applications to be QUIC-aware immediately. The Alt-Svc header is what enables the discovery. Without it, clients would continue to use HTTP/1.1 or HTTP/2 over TCP.
The Alt-Svc header is more than just a suggestion; it’s a crucial part of the HTTP/3 discovery mechanism. Clients cache these Alt-Svc entries. For a client to switch to HTTP/3, it first needs to see this header. The ma (max-age) parameter dictates how long the client should remember this alternative service. A longer ma means clients are more likely to try HTTP/3 on subsequent visits, but if you need to disable HTTP/3, you’ll need to lower this value or remove the header to clear the cache.
The next thing you’ll likely want to tackle is handling mixed-mode setups where you might have both HTTP/1.1 and HTTP/3 traffic on the same IP and port, and ensuring your logging correctly identifies the protocol used.