ALPN vs Alt Svc: Why HTTP/2 and HTTP/3 Detection Tools Disagree

Two sites that both serve HTTP/3 can look totally different to checker tools. One shows "HTTP/3 supported" in green. Another shows "no HTTP/3" in the same row, for a site you know runs on Cloudflare. The reason is that HTTP/2 and HTTP/3 are negotiated differently at the protocol level, and most tools only probe one of those paths. Here is the short version of why, and how to read our results correctly.

Key takeaways

  • HTTP/2 is negotiated inside the TLS handshake via ALPN. Any TLS client can check it.
  • HTTP/3 runs on QUIC (UDP). Most tools cannot open a real QUIC connection, so they parse the Alt-Svc HTTP header instead. That is an advertisement, not a guarantee.
  • If a site uses Cloudflare but blocks the very first UDP probe, tools that actually speak QUIC may report no HTTP/3 even though browsers use it fine.
  • If a site is listed in browser "happy eyeballs" caches, the first response may come over HTTP/2, then later ones over HTTP/3. Timing matters.
  • Our HTTP/2 checker uses ALPN. Our HTTP/3 checker parses Alt Svc.

HTTP/2 is simple to detect

HTTP/2 uses the same TCP plus TLS path as HTTP/1.1. The version is picked during the TLS handshake using an extension called ALPN (Application Layer Protocol Negotiation). The client sends a list of protocols it supports (h2, http/1.1), and the server picks one. That pick comes back in the TLS ServerHello, so any TLS client library (including Node's tls.connect) can see it without even sending an HTTP request.

This is what our HTTP/2 checker does. We open a TLS connection to port 443, offer h2 in ALPN, and read what the server selected. If it selects h2, the site supports HTTP/2. No ambiguity, no network hoop jumping. It works the same way for any public site.

HTTP/3 is different

HTTP/3 does not run on TCP. It runs on QUIC, which is a protocol on top of UDP. QUIC integrates encryption, stream multiplexing, and congestion control in one. Detecting it is two hard problems in one:

  • You need a client that actually speaks QUIC. Node does not have one in the standard library. Browsers have one, but you cannot drive it from a server side check. Tools like curl can, if built with HTTP/3 support, which is not the default in most distros.
  • Many hosting environments block outgoing UDP on port 443 for regular app containers. Railway, Render, Heroku style platforms typically route TCP and only forward UDP when explicitly configured. That means a backend running in such an environment will often fail QUIC probes even when the target supports HTTP/3.

Because of this, almost every public HTTP/3 checker uses a softer signal: the Alt-Svc response header. When a server speaks HTTP/3, it advertises this on an HTTP/1.1 or HTTP/2 response with a header like:

Alt-Svc: h3=":443"; ma=86400
Alt-Svc: h3-29=":443"; ma=86400, h3=":443"; ma=86400

That is an advertisement: "I can also serve this on HTTP/3 at this port." It is not proof of a working QUIC path. If the advertisement is there, in practice it almost always works, because the server would not advertise it otherwise, but a strict auditor should treat Alt Svc as "claims HTTP/3" and not "confirmed HTTP/3."

Why two tools disagree

  • One uses curl with HTTP/3, the other parses Alt Svc. If curl HTTP/3 cannot establish a UDP session (blocked by a firewall between the checker and the target), it returns "no HTTP/3." Alt Svc parsing still shows "yes."
  • One uses a browser, the other a server side probe. Browsers cache Alt Svc discoveries and race TCP against QUIC ("happy eyeballs"). Server side probes see whatever the response header says on that single request.
  • Geography and edge. HTTP/3 rollouts at CDNs are uneven across regions. A checker in Europe can see Alt Svc that a US checker does not see, and vice versa.
  • User agent matters. Some origins only advertise Alt-Svc to real browser user agents. If a checker uses a generic UA, the response may omit the header entirely.

How our checker decides

We use the approach that works reliably from a server side backend:

  • HTTP/2: we open a TLS connection, offer ALPN, and read the selected protocol. If the server picked h2, HTTP/2 is confirmed.
  • HTTP/3: we send a HEAD request with a real browser user agent and parse Alt-Svc for h3, h3-29, or older quic tokens.
  • We label HTTP/3 results as advertised in the Alt Svc header rather than guaranteed, because the underlying UDP path may be restricted from where we run.

If you want to verify end to end on your own network, the quickest tool is curl --http3 -I https://example.com on a build of curl that includes HTTP/3 support. Chrome DevTools also shows the protocol used for each request in the Network tab, which is as close to ground truth as you will get.

What to do with this

If you run a serious site, enable HTTP/2 everywhere (it is basically free on HTTPS), and confirm it with an ALPN check. HTTP/3 is harder to run on the origin, so most teams get it via a CDN (Cloudflare, Fastly) at the edge. Once enabled, the CDN will advertise Alt Svc automatically, and tools like ours will pick it up.

If a checker says "no HTTP/3" and you are sure you enabled it, compare results: run the HTTP/3 checker (Alt Svc), an actual browser request with DevTools, and a curl with HTTP/3. If DevTools says h3 and Alt Svc is present, you are fine. If only one of the two reports HTTP/3, investigate UDP reachability between your CDN and the client rather than your origin config.

Check your site

Our HTTP/2 checker uses ALPN, so it is a confirmed result. The HTTP/3 checker reads Alt Svc. Run them side by side, or use the full HTTPS report for everything in one pass.