How to make VaultWarden pass encrypted through Caddy, but local only?

1. The problem I’m having:

Hello,

I am very new and trying to understand how to get vaultwarden to pass through caddy via https://, but locally only(no DNS challenge).

I have caddy working and copied the certs and added them to my trusted host (outside the container.) I can curl the localhost from outside the container to get “respond” text, tested on port 2016 also, which works.

I have vaultwarden working on 127.0.0.1:8000.

I just don’t know how to make vaultwarden pass through caddy to use https, or if I’m even understanding what I’m doing properly. I just wanted to see if I could make this work locally before I decide if I wanted to bother with a full on reverse proxy.

Also, I know my Caddyfile isn’t correct, I’ve just been throwing stuff at it trying to make it work.

Sorry if I am completely misunderstanding what I can do with caddy here.

2. Error messages and/or full log output:

caddy-1  | 2026-07-13T01:33:08.432497505Z {"level":"info","ts":1783906388.432395,"msg":"maxprocs: Leaving GOMAXPROCS=16: CPU quota undefined"}
caddy-1  | 2026-07-13T01:33:08.432518946Z {"level":"info","ts":1783906388.4324062,"msg":"GOMEMLIMIT is updated","GOMEMLIMIT":30180862771,"previous":9223372036854775807}
caddy-1  | 2026-07-13T01:33:08.432523083Z {"level":"info","ts":1783906388.4324098,"msg":"using config from file","file":"/etc/caddy/Caddyfile"}
caddy-1  | 2026-07-13T01:33:08.432525718Z {"level":"info","ts":1783906388.432412,"msg":"adapted config to JSON","adapter":"caddyfile"}
caddy-1  | 2026-07-13T01:33:08.432528153Z {"level":"warn","ts":1783906388.4324133,"msg":"Caddyfile input is not formatted; run 'caddy fmt --overwrite' to fix inconsistencies","adapter":"caddyfile","file":"/etc/caddy/Caddyfile","line":1}
caddy-1  | 2026-07-13T01:33:08.432924259Z {"level":"info","ts":1783906388.4328728,"logger":"admin","msg":"admin endpoint started","address":"localhost:2019","enforce_origin":false,"origins":["//localhost:2019","//[::1]:2019","//127.0.0.1:2019"]}
caddy-1  | 2026-07-13T01:33:08.432994521Z {"level":"info","ts":1783906388.4329648,"logger":"http.auto_https","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-1  | 2026-07-13T01:33:08.432998238Z {"level":"info","ts":1783906388.432975,"logger":"http.auto_https","msg":"enabling automatic HTTP->HTTPS redirects","server_name":"srv0"}
caddy-1      | 2026-07-13T01:33:08.433052821Z {"level":"info","ts":1783906388.4330273,"logger":"tls.cache.maintenance","msg":"started background certificate maintenance","cache":"0x271cbbda0e80"}
caddy-1      | 2026-07-13T01:33:08.437980541Z {"level":"info","ts":1783906388.437946,"logger":"tls","msg":"storage cleaning happened too recently; skipping for now","storage":"FileStorage:/data/caddy","instance":"e90446bd-7450-4d43-9842-253a868b5e41","try
_again":1783992788.4379454,"try_again_in":86399.999999789}
caddy-1      | 2026-07-13T01:33:08.438009044Z {"level":"info","ts":1783906388.43799,"logger":"tls","msg":"finished cleaning storage units"}
caddy-1      | 2026-07-13T01:33:08.443186364Z {"level":"warn","ts":1783906388.4431462,"logger":"pki.ca.local","msg":"installing root certificate (you might be prompted for password)","path":"storage:pki/authorities/local/root.crt"}
caddy-1      | 2026-07-13T01:33:08.443377173Z {"level":"info","ts":1783906388.4433467,"msg":"warning: \"certutil\" is not available, install \"certutil\" with \"apt install libnss3-tools\" or \"yum install nss-tools\" and try again"}
caddy-1      | 2026-07-13T01:33:08.443384407Z {"level":"info","ts":1783906388.4433525,"msg":"define JAVA_HOME environment variable to use the Java trust"}
caddy-1      | 2026-07-13T01:33:08.466065729Z {"level":"info","ts":1783906388.4660256,"msg":"certificate installed properly in linux trusts"}
caddy-1      | 2026-07-13T01:33:08.466148635Z {"level":"info","ts":1783906388.4661164,"logger":"http","msg":"enabling HTTP/3 listener","addr":":443"}
caddy-1      | 2026-07-13T01:33:08.466236140Z {"level":"info","ts":1783906388.4662037,"logger":"http.log","msg":"server running","name":"srv0","protocols":["h1","h2","h3"]}
vaultwarden  | 2026-07-13T01:33:08.438614555Z /--------------------------------------------------------------------\
caddy-1      | 2026-07-13T01:33:08.466271637Z {"level":"warn","ts":1783906388.466244,"logger":"http","msg":"HTTP/2 skipped because it requires TLS","network":"tcp","addr":":80"}
vaultwarden  | 2026-07-13T01:33:08.438632398Z |                        Starting Vaultwarden                        |
vaultwarden  | 2026-07-13T01:33:08.438635945Z |                           Version 1.36.0                           |
vaultwarden  | 2026-07-13T01:33:08.438638199Z |--------------------------------------------------------------------|
vaultwarden  | 2026-07-13T01:33:08.438640283Z | This is an *unofficial* Bitwarden implementation, DO NOT use the   |
vaultwarden  | 2026-07-13T01:33:08.438642437Z | official channels to report bugs/features, regardless of client.   |
vaultwarden  | 2026-07-13T01:33:08.438644621Z | Send usage/configuration questions or feature requests to:         |
vaultwarden  | 2026-07-13T01:33:08.438646665Z |   https://github.com/dani-garcia/vaultwarden/discussions or        |
vaultwarden  | 2026-07-13T01:33:08.438648689Z |   https://vaultwarden.discourse.group/                             |
vaultwarden  | 2026-07-13T01:33:08.438650823Z | Report suspected bugs/issues in the software itself at:            |
vaultwarden  | 2026-07-13T01:33:08.438653077Z |   https://github.com/dani-garcia/vaultwarden/issues/new            |
vaultwarden  | 2026-07-13T01:33:08.438655161Z \--------------------------------------------------------------------/
vaultwarden  | 2026-07-13T01:33:08.438657285Z
vaultwarden  | 2026-07-13T01:33:08.468790770Z [2026-07-13 01:33:08.468][start][INFO] Rocket has launched from http://0.0.0.0:80
caddy-1      | 2026-07-13T01:33:08.466285583Z {"level":"warn","ts":1783906388.4662473,"logger":"http","msg":"HTTP/3 skipped because it requires TLS","network":"tcp","addr":":80"}
caddy-1      | 2026-07-13T01:33:08.466289500Z {"level":"info","ts":1783906388.4662495,"logger":"http.log","msg":"server running","name":"remaining_auto_https_redirects","protocols":["h1","h2","h3"]}
caddy-1      | 2026-07-13T01:33:08.466292526Z {"level":"info","ts":1783906388.466252,"logger":"http","msg":"enabling automatic TLS certificate management","domains":["localhost"]}
caddy-1      | 2026-07-13T01:33:08.466360414Z {"level":"info","ts":1783906388.4663284,"msg":"autosaved config (load with --resume flag)","file":"/config/caddy/autosave.json"}
caddy-1      | 2026-07-13T01:33:08.466367717Z {"level":"info","ts":1783906388.466335,"msg":"serving initial configuration"}
caddy-1      | 2026-07-13T01:33:45.895684468Z {"level":"error","ts":1783906425.89559,"logger":"http.log.error","msg":"dial tcp 127.0.0.1:8000: connect: connection refused","request":{"remote_ip":"172.19.0.1","remote_port":"54892","client_ip":"172.19.0.1",
"proto":"HTTP/2.0","method":"GET","host":"localhost","uri":"/","headers":{"User-Agent":["Mozilla/5.0 (X11; Linux x86_64; rv:152.0) Gecko/20100101 Firefox/152.0"],"Sec-Gpc":["1"],"Upgrade-Insecure-Requests":["1"],"Sec-Fetch-User":["?1"],"Accept":["text/htm
l,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"],"Accept-Encoding":["gzip, deflate, br, zstd"],"Dnt":["1"],"Te":["trailers"],"Accept-Language":["en-US,en;q=0.9"],"Sec-Fetch-Dest":["document"],"Sec-Fetch-Mode":["navigate"],"Sec-Fetch-Site":["none"
],"Priority":["u=0, i"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","server_name":"localhost","ech":false}},"duration":0.000230223,"status":502,"err_id":"t94uffepd","err_trace":"reverseproxy.statusError (reverseproxy.go:1626)"}
q^C

3. Caddy version:

caddy:2.11.4

4. How I installed and ran Caddy:

Docker Compose

a. System environment:

Arch Linux

b. Command:

sudo docker compose up -d

c. Service/unit/compose file:

services:
  caddy:
    image: caddy:2.11.4
    restart: unless-stopped
    ports:
      # for testing curl ports
      #- "2016:2016"
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - ./conf:/etc/caddy
      - ./site:/srv
      - caddy_data:/data
      - caddy_config:/config

  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    volumes:
      - ./vw-data/:/data/
    ports:
      # 8000 in wv is binded to 80
      - 127.0.0.1:8000:80

volumes:
  caddy_data:
  caddy_config:

d. My complete Caddy config:

localhost {
    reverse_proxy 127.0.0.1:8000
}

#localhost {
#       respond "Hello, world!"
#}
#
#localhost:2016 {
#       respond "Goodbye, world!"
#}

# https://caddyserver.com/docs/quick-starts/caddyfile

5. Links to relevant resources:

Type here

Do this:

reverse_proxy vaultwarden:80

The caddy container connects to the vaultwarden container by name using Docker’s DNS resolver, and it connects to the port inside the container, not the one you bound to the host (i.e. 80, not the 8000 from your ports config).

Thanks, this worked perfectly.
I just started messing with docker/caddy etc yesterday, so I am still very lost. I tried reading and have like 20 tabs open but wasn’t getting it. This helps me to better understand how the containers are talking. Appreciate it very much!