The Bleeding Edge Problem
I'm writing QUIC documentation and thought it would be great to have the site available as HTTP/3 (which runs on top of QUIC). This is a relatively new protocol, so I ran into some familiar problems:
- Tooling: The webserver that I'm using (apache) doesn't offer HTTP/3 support, and has no current plans to
- Uptime: The page in question is relatively popular, and I don't want to risk it going down while I set this up
The QUIC Possibility
Luckily the details of the QUIC transport protocol give us some flexibility here:
- QUIC uses a different underlying protocol (UDP vs TCP), so I can leave apache on TCP port 443 and put up a reverse proxy on the UDP port
- All HTTP/3-capable browsers will try QUIC and, if they get a connection error, will fall back to TCP (and the unmodified apache setup)
The Solution
The fix is a simple Caddy reverse proxy. It's running in a docker container to let me "split the bindings": Caddy wants to bind both TCP and UDP ports but I can only give it the UDP port. Containerizing gives me the flexibility of letting it bind both in its container, but only exposing the UDP port to the world.
Details
The Caddy installation looks like this. You'll want to tweak a few lines, indicated with "youruser
" or "yoursite
" placeholders:
setup:
### change as appropriate for your OS
sudo snap install docker
mkdir ~/caddy/
mkdir ~/caddy/caddy_data
mkdir ~/caddy/caddy_config
~/caddy/docker-compose.yaml
:
version: "3.7"
services:
caddy:
container_name: caddy
hostname: caddy
image: caddy:2.4.6
restart: unless-stopped
ports:
- "443:443/udp"
volumes:
- /home/youruser/caddy/Caddyfile:/etc/caddy/Caddyfile
- /path/to/yoursite/fullchain.pem:/caddy.crt
- /path/to/yoursite/privkey.pem:/caddy.key
- /home/youruser/caddy/caddy_data:/data
- /home/youruser/caddy/caddy_config:/config
extra_hosts:
- "host-gateway:172.17.0.1"
volumes:
caddy_data:
external: true
caddy_config:
~/caddy/Caddyfile
:
{
auto_https off
servers {
protocol {
experimental_http3
}
}
}
yoursite.com {
tls /caddy.crt /caddy.key
reverse_proxy * https://host-gateway {
transport http {
tls_insecure_skip_verify
}
}
}
startup:
cd ~/caddy
sudo docker-compose up -d
### tail the logs with `sudo docker logs -f caddy`
Advertising HTTP/3
The above sets up a reverse proxy that serves HTTP/3 on UDP port 443, but nothing will try it until you advertise it on your "real" HTTP server. Fortunately this minor config was the only change needed on the production server:
In my VirtualHost
apache config for the site:
Header set alt-svc "h3=\":443\"; ma=3600, h3-29=\":443\"; ma=3600
This advertises an HTTP/3 service (both "standard" and "draft 29" versions of the protocol) with the "Alt-Svc:" header. You'll need to bounce apache for this to take effect.
Cert Rotation
One last thing is needed to handle cert rotation. The above solution copies your site certificate and key into the Caddy container, but if you're using LetsEncrypt that cert is only good for three months and is likely being rotated monthly. I run the following in cron to ensure the container is always capturing a relatively fresh certificate:
### bounce the reverse proxy every month
39 0 25 * * cd /home/youruser/caddy && /path/to/docker-compose down && /path/to/docker-compose up -d
cover image by Stephen Cleary CC 2.0
Top comments (1)
As of version 2.6 of caddy, http3 is enabled by default. So, you no longer have to provide the
experimental_http3
in config