cURLParse cURL Converter

HTTP/3 and QUIC in curl 8.x — What Actually Works Today

cURLParse — Convert cURL Commands to Python, Node.js, Go & Rust Guides · Updated 2026-10-01 · All guides

HTTP/3 support in curl is real, shipped, and still quieter than the blog posts suggest. Before you chase h3 latency numbers, check your binary: curl --version and look for HTTP3 in the Features: line. Plenty of stock distro builds — including the curl 8.5 build on the Ubuntu box this page was written next to — have HTTP/2 but no HTTP/3 at all, and on those --http3 dies before connecting: option --http3: the installed libcurl version doesn't support this, exit code 2. The converter on the home page will happily parse an --http3 flag from a DevTools copy; it can't tell you whether your binary can execute it.

Backends: curl 8.x cut this list in half

curl implements HTTP/3 through third-party QUIC stacks, and the project has been aggressively pruning the options:

  • ngtcp2 + nghttp3 — the primary, non-experimental backend. Against OpenSSL, it needs OpenSSL 3.5+ (the first OpenSSL whose QUIC API third-party stacks can actually use) or a fork: AWS-LC, BoringSSL, LibreSSL, or quictls.
  • quiche — Cloudflare's Rust stack; works, but still labeled EXPERIMENTAL in curl's own docs.
  • msh3 (MS quic) and OpenSSL-QUIC (OpenSSL's own API-first QUIC layer) — both removed. The OpenSSL-QUIC backend shipped experimental in 8.8-era curl and was dropped starting with 8.19 (early 2026) because the API lagged, and its throughput and memory numbers trailed ngtcp2 by factors of 3 and ~20 in the project's own benchmarks.

So if you're rebuilding curl for h3 in 2026: ngtcp2+nghttp3 against OpenSSL 3.5+ (pairing OpenSSL 3.5+ with ngtcp2 ≥ 1.12), or quiche. Anything telling you to build --with-ngtcp2 against OpenSSL 3.0 is following a stale guide — OpenSSL only got a usable third-party-facing QUIC API in 3.5.

HTTP/3 in curl remains flagged experimental overall: expect behavior changes between minor releases, and don't build a health check that alerts on curl: unknown --http3.

--http3 vs --http3-only, and the "silent" downgrade

  • --http3 (since 7.66.0): try QUIC first, fall back to HTTP/2 or HTTP/1 on the same request. It's happy-eyeballing for transports: curl races the QUIC handshake against a delayed TCP+TLS attempt (~100 ms soft / 200 ms hard by default). Failure only happens when both die.
  • --http3-only (since 7.88.0): QUIC or error. The diagnostic tool, not the daily driver.

The fallback is what makes --http3 "silently" use HTTP/2: nothing failed, UDP 443 was just dropped or the server never answered the Initial packet, so you got an h2 transfer and never saw a word about it. Three checks when you suspect a silent downgrade:

  1. curl --http3 -w '%{http_version}\n' -o /dev/null -s URL — prints 3 or 2, no vibes involved (the variable exists since 7.50).
  2. -v: an h3 connection logs Connected to ... (quic), no TCP socket line; you'll see two connect attempts when fallback engaged.
  3. Corporate network, VPN, or a proxy in the path? curl cannot do HTTP/3 through any proxy — with -x set, h3 is off the table entirely, and HTTPS-only plus no-proxy is a hard design constraint, not a bug.

Note --http3 is mutexed with --http2/--http1.1 in the parser — you can't demand "h3 preferred but negotiate h2 on the same socket"; the h2 attempt is a separate fallback connection, which is why you see duplicated Trying lines in verbose output.

Alt-Svc: the bootstrap path people skip

The standards-compliant route to h3 isn't a flag; it's the server telling you about it. --alt-svc cache.txt enables the Alt-Svc parser and persists the "this origin also speaks h3 on :443" advertisement across invocations (added 7.64.1). Pass --alt-svc "" for an in-memory cache only. Two consequences people misread:

  • The first request still goes over h2/h1 — the upgrade applies to subsequent requests once the cache is populated. Benchmarking "HTTP/3" with a cold cache measures HTTP/2.
  • The cache is per-file, not per-user-agent: point every script at one shared file or your h3 hit-rate stays near zero.

Debugging QUIC when -v stops helping

-v shows handshake order and ALPN for TCP, but for h3 it mostly proves that QUIC was used, not why it failed. Layer up:

  • --trace-ascii out.log for the full request/response stream; since 8.3 --trace-config lets you narrow components (ids,http/2 style) instead of drowning in the whole trace.
  • --http3-only converts silent fallback into a hard error with an exit code — the fastest way to learn whether QUIC is broken or merely slow.
  • Frame-level h3 tracing is still immature in curl: there's no verbose equivalent of --trace-config for QUIC frames. When -v isn't enough, capture UDP 443 (tshark -f 'udp port 443') and look for whether your Initial packet gets any reply at all — no reply is a middlebox dropping QUIC, an Initial and then silence is a server-side h3 problem. Same shape of diagnosis as when curl works but your code fails: isolate the transport before blaming the app.

What's still maturing (be honest in your rollout)

Working today on a proper ngtcp2 build: transfers, redirects, parallel -Z multiplexing over one connection, the eyeballing fallback. Still rough: proxy use (none), HTTP/3 over anything but HTTPS (impossible by design), frame-level observability, and the quiche backend's experimental label — the curl team's stated bar for dropping it is the underlying QUIC library calling itself non-beta first.

FAQ

curl --http3 "works" — how do I know it's not just h2? curl --http3 -s -o /dev/null -w '%{http_version}\n' URL. Anything other than 3 is a fallback. Then re-run with --http3-only to see the real failure.

My Docker image has curl 8.x but --http3 fails. Two different failures. option --http3: the installed libcurl version doesn't support this (exit 2) means the flag compiled but the libcurl lacks an HTTP/3 backend. Unknown option means the tool predates the flag entirely (pre-7.66). Diagnose with curl --version | grep -o HTTP3: no output, rebuild with ngtcp2+nghttp3+OpenSSL 3.5+ or switch to an http3-enabled image.

Does --http3 change what the server's application layer sees? No — same request/response semantics; it changes transport, not HTTP verbs. Your body/-G habits from URL and query hacks are unaffected.

Developer Sponsor / Partner
Copied to clipboard!