DEV Community

Anton Kopylov
Anton Kopylov

Posted on • Originally published at kopylov.net

HTTP/3 on Kamal, and a PR to Basecamp

A few days ago I read a good writeup on how much HTTP/3 can shave off page loads — QUIC drops a round trip on connection setup and survives network changes without renegotiating. I wanted it on my own sites.

All of them deploy with Kamal, which fronts each app with kamal-proxy. The good news: kamal-proxy already speaks HTTP/3 — it has an --http3 flag. The bad news: Kamal gave me no way to turn it on.

Two things were missing, and the second is the subtle one:

  1. Kamal never passes --http3 to the proxy.
  2. The proxy container only publishes TCP 80 and 443. HTTP/3 runs over QUIC, which is UDP — so even with the flag set, the UDP 443 listener would sit there unreachable.

Someone had already filed a feature request, so I wasn't alone in wanting this. I forked Kamal and added a proxy.run.http3 option that does both halves of the job: it passes --http3 to kamal-proxy run, and it publishes the HTTPS port over UDP as well (443:443/udp). There's a guard, too — you can't enable HTTP/3 when ports aren't published, since there'd be nothing to listen on.

Turning it on is one line:

# config/deploy.yml
proxy:
  run:
    http3: true
Enter fullscreen mode Exit fullscreen mode

Until the PR lands, the Gemfile points at the fork:

gem "kamal", github: "tonic20/kamal", branch: "proxy-http3", require: false
Enter fullscreen mode Exit fullscreen mode

I shipped it on watchbrandindex.com and confirmed the negotiation — browser and proxy now agree on h3.

Now to see if DHH merges it upstream. 🤞

P.S. — While running other sites through that checker, it flagged this one too. This blog isn't on Kamal; it's a static site on S3 + CloudFront, which already serves HTTP/3. But browsers were discovering it the slow way: connect over h2, read the Alt-Svc header, then upgrade to h3 on a later connection. The fix here lives in DNS, not the server — an HTTPS resource record (RFC 9460) at the apex:

kopylov.net.  HTTPS  1 . alpn="h3,h2"
Enter fullscreen mode Exit fullscreen mode

Now browsers learn h3 is available during the DNS lookup and try QUIC on the very first connection. One more round trip saved, no server involved.

Top comments (0)