Serve HTTP/3
HTTP/3 is enabled by default: an HTTPS site uses a QUIC listener on UDP 443 unless
the global protocol list excludes h3. Check the response protocol: a client
may use HTTP/2 after an HTTP/3 connection fails.
📌 This page describes v0.2.2.
🧾 Before you start
Section titled “🧾 Before you start”-
A name that resolves to the host, and a certificate for it (HTTPS).
-
UDP 443 open in the provider’s firewall and the host’s. If UDP is blocked, clients may use HTTP/2 instead.
-
A client with HTTP/3 support. The system
curlon most distributions does not have it and reports this error:curl: option --http3: the installed libcurl version doesn't support this
🔌 Configure HTTP/3
Section titled “🔌 Configure HTTP/3”{ email bonjour@pingclair.com servers { protocols h1 h2 h3 }}
example.com { file_server /srv/site}After starting the site, check the UDP listener on the host:
sudo ss -lunp | grep ':443 'UNCONN 0 0 *:443 *:* users:(("pingclair",pid=5425,fd=22))Removing h3 from the list removes the QUIC listener
(TLS: what you can tune).
Without a protocols line, HTTP/3 remains enabled.
The tls block also accepts a per-site switch:
example.com { tls { http3 off } file_server /srv/site}http3 off refuses this site’s QUIC handshake and removes its HTTP/3 advertisement from Alt-Svc. Other sites on the port may continue to use QUIC.
✅ Verify the client protocol
Section titled “✅ Verify the client protocol”Use an HTTP/3-capable client, such as curl built with ngtcp2 or quiche. If the host’s curl lacks HTTP/3 support, use a container:
docker run --rm --network host \ ymuski/curl-http3 curl -sI --http3 https://example.com/curl 8.2.1-DEV (x86_64-pc-linux-gnu) libcurl/8.2.1-DEV BoringSSL zlib/1.2.13 nghttp2/1.52.0 quiche/0.18.0--network host lets the container use the host’s network directly. Without
it, the request may pass through a network namespace that blocks QUIC.
HTTP/3 200content-type: text/html; charset=utf-8etag: "5e-6ab20622"accept-ranges: bytesx-served-by: pingclairserver: PingclairThe HTTP/3 status line confirms the protocol used for this response.
Requesting the same URL with --http2 and --http1.1 shows the other two
protocols, which confirms that the client is not falling back.
When a container is not available, a QUIC handshake can be checked with the system’s OpenSSL, if it is 3.5 or newer:
openssl s_client -quic -alpn h3 -connect example.com:443 -servername example.com </dev/nullProtocol: QUICv1ALPN protocol: h3 Protocol : TLSv1.3 Verify return code: 0 (ok)ALPN protocol: h3 with a verified chain proves that the QUIC listener answers
for that name with a certificate the client trusts. It does not prove that a
full HTTP/3 request works; the curl check does that.
🧭 What differs on HTTP/3
Section titled “🧭 What differs on HTTP/3”HTTP/3 shares its policy code with HTTP/1.1 and HTTP/2, so routing, matchers, headers, rate limits, FastCGI, and access logging behave the same. The following limitations apply:
| Area | On HTTP/3 |
|---|---|
| Declared request trailers | Not forwarded, as on every protocol: 501 before the response is committed; on HTTP/3 the stream is reset after that. |
| Upstream response trailers | Relayed, as on every protocol: the origin’s status and body reach the client, and the trailer fields are dropped. |
CONNECT |
Pingclair opens no tunnels. A usable host:port target receives 405 with Allow; a target without a usable port receives 400. HTTP/1.1 closes the connection after refusal. |
Trailer fields are the deliberate divergence in that table: Caddy and nginx
relay an origin’s trailer fields to the client, while this proxy forwards the
Trailer: announcement and drops the fields behind it — a client that reads
the announcement waits for fields that never arrive (#273).
A CDN in front of the origin terminates HTTP/3 itself and uses HTTP/1.1 or HTTP/2 to reach the origin. The origin listener does not identify the protocol used between the browser and the CDN; check the CDN’s HTTP/3 settings.
⚠️ Troubleshooting
Section titled “⚠️ Troubleshooting”option --http3: the installed libcurl version doesn't support this. The client has no HTTP/3; use a container as above.curl --http3does not complete or times out. UDP 443 may be blocked. Check the provider’s firewall or security group first, then the host’s.- No UDP listener on the host.
h3is missing from theserversprotocol list, or the file that is running is not the one you edited (what a reload means). - HTTP/3 works locally and not from outside. The client’s network may block UDP 443; browsers may use HTTP/2 instead.
🧭 Next steps
Section titled “🧭 Next steps”- TLS: what you can tune: the protocol list, certificates, and client certificates.
- Project status: what is supported, refused, and affected by known defects in this release.
tls: thehttp3option in context.
