How to set up http3 on a localhost

1. Output of caddy version:

v2.6.2 h1:wKoFIxpmOJLGl3QXoo6PNbYvGW4xLEgo32GPBEjWL8o=

2. How I run Caddy:

a. System environment:

I run it with docker.

b. Command:

docker-compose up

c. Service/unit/compose file:

docker-compose file:

version: '3.8'
    image: caddy:latest
    container_name: caddy
      - ./Caddyfile:/etc/caddy/Caddyfile
      - ./caddy_data:/data
      - ./caddy_config:/config
      - ./site:/usr/share/caddy/docs
      - 80:80
      - 443:443
    restart: unless-stopped

d. My complete Caddy config:

localhost:443 {
  respond "Hello, world! You're using {http.request.proto}"

3. The problem I’m having:

I am trying to set up a local caddy server that runs http3. I am interested in testing out the performance of http3 compared to http2, but I am completely stuck. I can’t seem to figure out how to do that exactly, as I only get http2 response when I try to curl. Here is the output:

curl -v https://localhost:443
*   Trying
* Connected to localhost ( port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /Users/kristijonasmedelis/opt/anaconda3/ssl/cacert.pem
*  CApath: none
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: [NONE]
*  start date: Dec 21 19:25:13 2022 GMT
*  expire date: Dec 22 07:25:13 2022 GMT
*  issuer: CN=Caddy Local Authority - ECC Intermediate
*  SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
* Using HTTP2, server supports multiplexing
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7fb326813600)
> GET / HTTP/2
> Host: localhost
> user-agent: curl/7.78.0
> accept: */*
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* Connection state changed (MAX_CONCURRENT_STREAMS == 250)!
< HTTP/2 200 
< alt-svc: h3=":443"; ma=2592000
< content-type: text/plain; charset=utf-8
< server: Caddy
< content-length: 35
< date: Thu, 22 Dec 2022 00:45:37 GMT
* Connection #0 to host localhost left intact
Hello, world! You're using HTTP/2.0%                                                                 

4. Error messages and/or full log output:

caddy | {"level":"info","ts":1671669448.9732368,"msg":"using provided configuration","config_file":"/etc/caddy/Caddyfile","config_adapter":"caddyfile"}
caddy | {"level":"warn","ts":1671669448.9742868,"msg":"Caddyfile input is not formatted; run the 'caddy fmt' command to fix inconsistencies","adapter":"caddyfile","file":"/etc/caddy/Caddyfile","line":2}
caddy | {"level":"info","ts":1671669448.9750993,"logger":"admin","msg":"admin endpoint started","address":"localhost:2019","enforce_origin":false,"origins":["//localhost:2019","//[::1]:2019","//"]}
caddy | {"level":"info","ts":1671669448.9752524,"logger":"http","msg":"server is listening only on the HTTPS port but has no TLS connection policies; adding one to enable TLS","server_name":"srv0","https_port":443}
caddy | {"level":"info","ts":1671669448.9752896,"logger":"http","msg":"enabling automatic HTTP->HTTPS redirects","server_name":"srv0"}
caddy | {"level":"info","ts":1671669448.9848115,"logger":"tls","msg":"cleaning storage unit","description":"FileStorage:/data/caddy"}
caddy | {"level":"info","ts":1671669448.994749,"logger":"tls.cache.maintenance","msg":"started background certificate maintenance","cache":"0x400012f1f0"}
caddy | {"level":"info","ts":1671669449.008281,"logger":"tls","msg":"finished cleaning storage units"}
caddy | {"level":"warn","ts":1671669449.0372903,"logger":"","msg":"installing root certificate (you might be prompted for password)","path":"storage:pki/authorities/local/root.crt"}
caddy | {"level":"info","ts":1671669449.0391757,"msg":"Warning: \"certutil\" is not available, install \"certutil\" with \"apt install libnss3-tools\" or \"yum install nss-tools\" and try again"}
caddy | {"level":"info","ts":1671669449.0394347,"msg":"define JAVA_HOME environment variable to use the Java trust"}
caddy | {"level":"info","ts":1671669449.0875618,"msg":"certificate installed properly in linux trusts"}
caddy | {"level":"info","ts":1671669449.0880172,"logger":"http","msg":"enabling HTTP/3 listener","addr":":443"}
caddy | {"level":"info","ts":1671669449.0887034,"msg":"failed to sufficiently increase receive buffer size (was: 208 kiB, wanted: 2048 kiB, got: 416 kiB). See for details."}
caddy | {"level":"info","ts":1671669449.0894935,"logger":"http.log","msg":"server running","name":"srv0","protocols":["h1","h2","h3"]}
caddy | {"level":"info","ts":1671669449.0897727,"logger":"http.log","msg":"server running","name":"remaining_auto_https_redirects","protocols":["h1","h2","h3"]}
caddy | {"level":"info","ts":1671669449.0898588,"logger":"http","msg":"enabling automatic TLS certificate management","domains":["localhost"]}
caddy | {"level":"warn","ts":1671669449.0946858,"logger":"tls","msg":"stapling OCSP","error":"no OCSP stapling for [localhost]: no OCSP server specified in certificate","identifiers":["localhost"]}
caddy | {"level":"info","ts":1671669449.0977216,"msg":"autosaved config (load with --resume flag)","file":"/config/caddy/autosave.json"}
caddy | {"level":"info","ts":1671669449.0979636,"msg":"serving initial configuration"}

5. What I already tried:

I am very sorry since I am quite new to this kind of stuff, but I haven’t tried much and I am completely stuck. I tried changing up the caddyfile to something like this:

localhost:443 {
  respond "Hello, world! You're using {http.request.proto}"

You need to bind UDP as well. By default, only TCP is bound with docker-compose.

See the docs:

Make sure to follow these instructions so that you get proper HTTP/3 performance. You need to do those things on the host machine.

I am sorry if I missed something else, but after adding - “443:443/udp” to my docker-compose, so now it looks like this:

version: '3.8'
    image: caddy:latest
    container_name: caddy
      - ./Caddyfile:/etc/caddy/Caddyfile
      - ./caddy_data:/data
      - ./caddy_config:/config
      - ./site:/usr/share/caddy/docs
      - 80:80
      - 443:443
      - "443:443/udp"
    restart: unless-stopped

I still get the same resposne that I am using http2. Again, sorry if I am missing something, but I can’t figure it out…

1 Like

Are you using a build of curl that actually supports http3? You might need to use the --http3 option.

Many browsers try HTTP/2 first, and only try HTTP/3 afterwards if the initial response had the Alt-Svc header saying that HTTP/3 is available. So if the browser has no reason to make more requests to the same host, it might just choose not to use HTTP/3.

Oh yes, I forgot to mention that I am using this curl command:

docker run --rm ymuski/curl-http3 curl --http3 https://localhost

and the response i get is:

WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested
curl: (7) quiche: recv() unexpectedly returned -1 (errno: 111, socket 5)

I know that there is the warning, but if I try some other website like:

docker run -it --rm ymuski/curl-http3 curl -IL --http3

I do get a response like this:

WARNING: The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested
HTTP/3 403
date: Thu, 22 Dec 2022 01:29:29 GMT
content-type: text/html; charset=UTF-8
cf-chl-bypass: 1
permissions-policy: accelerometer=(),autoplay=(),camera=(),clipboard-read=(),clipboard-write=(),fullscreen=(),geolocation=(),gyroscope=(),hid=(),interest-cohort=(),magnetometer=(),microphone=(),payment=(),publickey-credentials-get=(),screen-wake-lock=(),serial=(),sync-xhr=(),usb=()
referrer-policy: same-origin
x-frame-options: SAMEORIGIN
cache-control: private, max-age=0, no-store, no-cache, must-revalidate, post-check=0, pre-check=0
expires: Thu, 01 Jan 1970 00:00:01 GMT
set-cookie: __cf_bm=pOXvvRTJI9j1Qp5hh6CRSpSAu9qpYLZEf3mnzqghYKI-1671672569-0-AeRjCRBh+d6zTPTAjYDtQwGVDpeZSWKGCOgjfRDQ9yymPx4lTFuCy1UsrDrAR4PV/nCTa21OekT+lb5BbpuTVqw=; path=/; expires=Thu, 22-Dec-22 01:59:29 GMT;; HttpOnly; Secure; SameSite=None
server-timing: cf-q-config;dur=6.0000002122251e-06
server: cloudflare
cf-ray: 77d516351f05bc10-VNO
alt-svc: h3=":443"; ma=86400, h3-29=":443"; ma=86400
1 Like

When you try to connect to localhost from inside a container, you’re trying to connect to something inside the container itself. But there’s no server running in that container.

You’ll need to do --net=host I think so that localhost is “this machine” instead of “this container”.


This topic was automatically closed after 30 days. New replies are no longer allowed.