Skip to content

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.

  • 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 curl on most distributions does not have it and reports this error:

    curl: option --http3: the installed libcurl version doesn't support this

{
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:

Terminal window
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.

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:

Terminal window
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 200
content-type: text/html; charset=utf-8
etag: "5e-6ab20622"
accept-ranges: bytes
x-served-by: pingclair
server: Pingclair

The 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:

Terminal window
openssl s_client -quic -alpn h3 -connect example.com:443 -servername example.com </dev/null
Protocol: QUICv1
ALPN 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.

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.

  • option --http3: the installed libcurl version doesn't support this. The client has no HTTP/3; use a container as above.
  • curl --http3 does 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. h3 is missing from the servers protocol 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.
  • 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: the http3 option in context.