# ⚡ Serve HTTP/3 HTTP/3 is on by default in the sense that nothing has to be installed for it: the server binds a QUIC listener on UDP 443 as soon as the protocol set allows it. What needs care is verifying it, because a client that silently falls back to HTTP/2 looks exactly like success. ## 🧾 Before you start - A name that resolves to the host, and a certificate for it ([HTTPS](/start/https/)). - **UDP 443 open** in the provider's firewall and the host's. QUIC has no fallback: if UDP is blocked, clients use HTTP/2 and never mention it. - A client with HTTP/3 support. The system `curl` on most distributions does not have it — asking anyway is explicit about it: ```text curl: option --http3: the installed libcurl version doesn't support this ``` ## 🔌 Turn it on ```caddyfile { email pingclair@pingclair.com servers { protocols h1 h2 h3 } } example.com { file_server /srv/site } ``` Measured on the host, with the site running: ```bash sudo ss -lunp | grep ':443 ' ``` ```text UNCONN 0 0 *:443 *:* users:(("pingclair",pid=5425,fd=22)) ``` Removing `h3` from the list takes that listener away; the list is the switch ([TLS: what you can tune](/guides/tls-tuning/#-which-protocols-are-served)). A single site can be taken out of HTTP/3 without stopping the listener: ```caddyfile example.com { tls { http3 off } file_server /srv/site } ``` ## ✅ Prove a client used it The server's access log does not name the protocol, so the proof comes from the client. Any curl built with ngtcp2 or quiche works; a container is the quickest way to get one on a host whose curl cannot do HTTP/3: ```bash docker run --rm --network host \ ymuski/curl-http3 curl -sI --http3 https://example.com/ ``` ```text 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` is what lets the container use the host's UDP path; without it the request may travel through a network namespace that blocks QUIC. ```text HTTP/3 200 content-type: text/html; charset=utf-8 etag: "5e-6ab20622" accept-ranges: bytes x-served-by: pingclair server: Pingclair ``` The first line is the whole answer: the status line says `HTTP/3`, not `HTTP/2`. Requesting the same URL with `--http2` and `--http1.1` shows the other two, which proves the client is not simply 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: ```bash openssl s_client -quic -alpn h3 -connect example.com:443 -servername example.com