I work on OnePassApps, the team behind app.onepassapps.com. Everything below was measured against our own site from outside the browser, signed out, on one machine, on one day.
When I wrote about the TLS layer under this site I said our host advertises HTTP/3 with alt-svc: h3=":443"; ma=86400, but that I could not tell you whether it actually works, because I had no HTTP/3 client. A later post repeated the same limit. This one closes it: I installed a QUIC client and asked the server directly.
What I used
The curl on this box is 8.18.0 and was built without HTTP/3 support - asking it for HTTP/3 answers "the installed libcurl version does not support this". So I installed aioquic 1.7.2 and drove the QUIC connection and the HTTP/3 framing myself.
Nine URLs: six on the app host (/, /guides, /studios/image, /apps/paperkit, /apps/movie-tracker, /terms) and three on the API host (/api/health, /api/products, /api/plans).
Both sides of the comparison used the same request shape - the same User-Agent, the same Accept header, and accept-encoding: identity. That matters, because an earlier post of ours measured that the answer changes shape with the request headers, so a comparison across two protocols with two different header sets would be worthless.
The short version: it works
All nine URLs answered 200 over QUIC on port 443, and the body length was the same over both protocols for every one of them.
| URL | HTTP/3 body | HTTP/2 body |
|---|---|---|
| / | 25,011 bytes | 25,011 bytes |
| /guides | 61,760 bytes | 61,760 bytes |
| /studios/image | 17,232 bytes | 17,232 bytes |
| /apps/paperkit | 19,054 bytes | 19,054 bytes |
| /apps/movie-tracker | 19,311 bytes | 19,311 bytes |
| /terms | 74,862 bytes | 74,862 bytes |
| /api/health | 410 bytes | 410 bytes |
| /api/products | 15,650 bytes | 15,650 bytes |
| /api/plans | 4,020 bytes | 4,020 bytes |
The bytes themselves are not identical between two fetches of the same URL - that is the per-request key we published separately - so this post compares lengths, not bytes. The lengths were stable: three fetches over each protocol gave the same length every time.
One more Server-Timing line
Over HTTP/2 our host sends exactly two server-timing lines: cfCacheStatus;desc="DYNAMIC" and cfEdge;dur=N,cfOrigin;dur=M. That is our own published response-header layer.
Over HTTP/3 it sends three. The same two, plus a third line reading cfExtPri.
A priority header HTTP/2 dropped
The HTTP/3 answer also carries priority: u=3,i=?0 - the HTTP/3 priority field - and the HTTP/2 answer for the same URL carries no priority field at all.
Counting header lines as each client sees them: HTTP/3 gave 24 lines on the app-host pages and HTTP/2 gave 21. Three of that difference is accounted for: one is the :status pseudo-header that only the HTTP/3 framing carries, one is the priority field, one is the extra server-timing line. The API host shows the same three-line gap: 35 against 32 on /api/health, and 34 against 31 on /api/products and /api/plans.
Six URLs on one handshake
HTTP/3 multiplexes requests over a single connection, so the six app-host URLs were fetched over one QUIC connection and one handshake: the handshake took 0.0791 s and all six answers were complete 0.0837 s later. On the API host, three URLs on one connection: 0.0366 s for the handshake and all three answers 0.1659 s later.
The advertisement is still sent to the client already speaking HTTP/3
The HTTP/3 answers still carry alt-svc: h3=":443"; ma=86400, and cf-cache-status reads DYNAMIC on both protocols.
What I did not do
One build, one day, one machine, one network path, one region. Signed out, no cookies, no JavaScript, no browser - these are plain HTTPS and QUIC requests to the live host. Nine URLs, not the 56 in the sitemap. One QUIC client, so a different client could see different timings. I did not measure a rendered page, and I did not compare the browser's own HTTP/3 support.
The write-ups on the layers underneath this one are the TLS layer, the protocol layer and the two Server-Timing lines - the first two of them flagged that HTTP/3 was advertised here but not exercised. The per-request key that changes the bytes between two fetches is its own post.
If you want to see the apps themselves, the OnePassApps home page and the guides are the place to start.
Top comments (0)