How to use HTTP/3 with cURL

Using HTTP/3 with cURL is a direct way to confirm that an HTTPS endpoint can complete a request over QUIC before monitoring, edge routing, or client policy starts depending on that transport.

The --http3 option tells cURL to start with HTTP/3 and then fall back to HTTP/2 or HTTP/1.1 if the QUIC path does not come up quickly enough, while --http3-only removes that fallback and makes the request fail when HTTP/3 cannot be established. Both options require a cURL build whose Features: line includes HTTP3.

Packaged cURL builds do not all include QUIC support, and network devices can still block or delay UDP even when ordinary HTTPS works over TCP. Check the local build first, use --http3 to prove negotiated transport, and reserve --http3-only for strict checks where a timeout or connection failure is acceptable.

Steps to use HTTP/3 with cURL:

  1. Check the local cURL build for the HTTP3 feature before using protocol-specific flags.
    $ curl --version
    curl 8.19.0 (aarch64-apple-darwin25.3.0) libcurl/8.19.0 OpenSSL/3.6.2 zlib/1.2.12 brotli/1.2.0 zstd/1.5.7 nghttp2/1.69.0 ngtcp2/1.22.1 nghttp3/1.15.0
    Release-Date: 2026-03-11
    Protocols: dict file ftp ftps gopher gophers http https imap imaps ipfs ipns ldap ldaps mqtt mqtts pop3 pop3s rtsp scp sftp smb smbs smtp smtps telnet tftp ws wss
    Features: alt-svc AsynchDNS brotli HSTS HTTP2 HTTP3 HTTPS-proxy IPv6 Largefile libz SSL threadsafe UnixSockets zstd

    Look for HTTP3 in the Features: line. Without it, --http3 and --http3-only fail immediately with an unsupported-option error.

  2. Send a header request with --http3 to let cURL try HTTP/3 first while still allowing an older protocol when necessary.
    $ curl --http3 --silent --show-error --head https://portal-edge.example.test/
    HTTP/3 200
    content-length: 18426
    server: edge-proxy
    content-type: text/html; charset=utf-8
    cache-control: max-age=60
    alt-svc: h3=":443"; ma=86400

    A status line that starts with HTTP/3 confirms that the transfer completed over QUIC instead of a TCP fallback.

  3. Print only the negotiated protocol number when a script or health check needs a single clean result.
    $ curl --http3 --silent --show-error --output /dev/null --write-out '%{http_version}\n' https://portal-edge.example.test/
    3

    Output 3 confirms successful HTTP/3 negotiation, while 2 or 1.1 shows that the request finished over an earlier HTTP version instead.

  4. Inspect verbose output when the transport decision needs stronger proof than the response status line.
    $ curl --http3 --verbose --silent --show-error --output /dev/null https://portal-edge.example.test/
    * Host portal-edge.example.test:443 was resolved.
    * IPv4: 192.0.2.24, 192.0.2.25
    *   Trying 192.0.2.24:443...
    ##### snipped #####
    * using HTTP/3
    * [HTTP/3] [0] OPENED stream for https://portal-edge.example.test/
    * [HTTP/3] [0] [:method: GET]
    * [HTTP/3] [0] [:authority: portal-edge.example.test]
    > GET / HTTP/3
    < HTTP/3 200

    The using HTTP/3 line and the HTTP/3 stream metadata provide stronger transport proof than the response status line alone.

  5. Test a host that does not currently complete HTTP/3 to see how --http3 falls back instead of failing.
    $ curl --http3 --silent --show-error --output /dev/null --write-out '%{http_version}\n' https://legacy-www.example.test/
    2

    An output such as 2 or 1.1 means the request still succeeded, but the transfer did not stay on HTTP/3.

  6. Force strict transport with --http3-only after a normal --http3 request has already shown that the endpoint speaks HTTP/3.
    $ curl --http3-only --silent --show-error --head https://portal-edge.example.test/
    HTTP/3 200
    content-length: 18426
    server: edge-proxy
    content-type: text/html; charset=utf-8
    cache-control: max-age=60
    alt-svc: h3=":443"; ma=86400

    Strict mode is appropriate when fallback would hide a broken QUIC path that should fail a check instead.

  7. Cap a strict-mode negative test with --max-time when the target or network path might never complete HTTP/3.
    $ curl --http3-only --max-time 5 --silent --show-error --head https://legacy-www.example.test/
    curl: (28) Connection timed out after 5001 milliseconds

    --http3-only does not downgrade to HTTP/2 or HTTP/1.1, so unsupported targets and UDP-blocked paths fail the transfer instead of silently succeeding over TCP.