Caddy with Docker: a reverse proxy with automatic HTTPS for your home server (Linux, Windows, Mac)
Before you start
- 21 min read
Boxes marked Run in terminal are what you type; plain text explains why. Check what you need first

What you get in one minute#
Caddy is a free web server that works as a reverse proxy: one container listens on ports 80 and 443, gets and renews HTTPS certificates by itself, and passes each request to the right app on your server.
Logo: the official Caddy project (caddyserver.com)
Instead of opening a port for every app, you open two and give each app a name such as notes.example.com. The config is a short text file. For a public domain Caddy uses Let's Encrypt; for a name that exists only at home it signs its own certificate with tls internal.
| What | Value (official docs, read October 2026) |
|---|---|
| Current version | Caddy v2.11.7, released October 2026 (latest stable) |
| Image to pin | caddy:2.11.7 (tag exists on Docker Hub; about 25 MB compressed for amd64) |
| Time needed | About 20 minutes for the home-only route (our estimate, not measured) |
| RAM | Not documented by the project; measure with docker stats |
| Ports | TCP 80 and 443, UDP 443 (HTTP/3). The admin API on 2019 stays inside the container |
| Licence | Apache License 2.0 (LICENSE at the release tag) |
| Official docs | caddyserver.com/docs and github.com/caddyserver/caddy |
At a glance:
- What it is: a reverse proxy with automatic HTTPS. You write one site block per app.
- Two kinds of certificate: Let's Encrypt for a public domain (needs DNS and open ports 80 and 443); Caddy's own certificate for home-only names (needs the root trusted on each device).
- No Docker socket: unlike Traefik, Caddy does not read the Docker socket. You list your apps in the Caddyfile.
- Platforms: Linux suits an always-on server. Caddy also runs on Windows and macOS.
- Pin the tag: use
2.11.7, notlatest. - Not a login screen: Caddy secures the connection. It does not decide who may see an app. Add
basic_author single sign-on for that.
How it fits together#
Browsers talk HTTPS to Caddy; Caddy talks plain HTTP to your apps over a private Docker network. Certificates live in one data folder on the host.
Drawn for this guide from the official Caddy docs. Dashed boxes: you use one of the two certificate routes, not both.
Before you start#
You need a machine that stays on, Docker with Compose, and apps that run as containers (or on the same machine).
Linux: any 64-bit distribution that runs Docker Engine and Compose v2. Check:
docker --version docker compose version
Windows: Windows 10 or 11 with Docker Desktop. Check in PowerShell:
docker --version docker compose version
Mac: Docker Desktop for Mac. Check in Terminal (same commands as Linux):
docker --version docker compose version
Checklist:
- [ ] Docker and Docker Compose work (the commands above print a version)
- [ ] Ports 80 and 443 are free on the machine (no other web server on them)
- [ ] The apps you want to put behind Caddy are known, with the port each one listens on inside its container
- [ ] Config backed up, if you already run Caddy from another guide
- [ ] For a public domain only: a domain, a DNS record pointing at your public IP, and ports 80 and 443 forwarded on your router
Tested by DIY Lab Hub: not yet. Commands come from the official docs and release notes read in October 2026; we did not run them.
Step 1: Create the folders and the Docker network#
Caddy keeps its certificates in data. If that folder is lost, every certificate is requested again, and Let's Encrypt can then refuse you for a week. The proxy network is the private street Caddy and your apps share.
Linux#
mkdir -p ~/docker_volumes/caddy/{conf,data,config,backups}
cd ~/docker_volumes/caddy
docker network create proxy
Windows (PowerShell)#
New-Item -ItemType Directory -Force "$HOME\docker_volumes\caddy\conf","$HOME\docker_volumes\caddy\data","$HOME\docker_volumes\caddy\config","$HOME\docker_volumes\caddy\backups" Set-Location "$HOME\docker_volumes\caddy" docker network create proxy
Mac (Terminal)#
Same commands as Linux:
mkdir -p ~/docker_volumes/caddy/{conf,data,config,backups}
cd ~/docker_volumes/caddy
docker network create proxy
Check it worked. The network exists (an "already exists" error from network create is harmless):
docker network ls --filter name=proxy
Tip: Already running Caddy from another DIY Lab Hub guide? Keep it, add your site blocks to its Caddyfile and skip to Step 4.
Step 2: Write the Compose file and the Caddyfile#
The Compose file starts Caddy and a tiny test backend (the same image answering "Hello"), so you can prove the proxy works before touching a real app. We mount the folder conf, not the single file: the Docker image docs warn that an editor which replaces the file breaks reloads. Some other DIY Lab Hub guides show a one-file mount; the folder is the safer form.
Save this as docker-compose.yml in ~/docker_volumes/caddy (no version: line is needed):
services:
caddy:
image: caddy:2.11.7
container_name: caddy
restart: unless-stopped
ports:
- "80:80/tcp"
- "443:443/tcp"
- "443:443/udp"
volumes:
- ./conf:/etc/caddy
- ./data:/data
- ./config:/config
networks:
- proxy
hello:
image: caddy:2.11.7
container_name: caddy-hello
restart: unless-stopped
command: ["caddy", "respond", "--listen", ":8080", "Hello from the backend"]
networks:
- proxy
networks:
proxy:
external: true
Save this as conf/Caddyfile. hello.home.arpa is a name reserved for home networks, and tls internal tells Caddy to sign the certificate itself:
hello.home.arpa {
tls internal
reverse_proxy hello:8080
}
Note hello:8080. Inside a container, localhost means that container itself, so you use the other container's name and the port the app listens on inside it.
Linux#
cd ~/docker_volumes/caddy nano docker-compose.yml nano conf/Caddyfile
Windows (PowerShell)#
Set-Location "$HOME\docker_volumes\caddy" notepad docker-compose.yml notepad conf\Caddyfile
Notepad asks to create each file; say yes. Check that the names do not end in .txt.
Mac (Terminal)#
Same commands as Linux (or open -e for TextEdit in plain text mode):
cd ~/docker_volumes/caddy nano docker-compose.yml nano conf/Caddyfile
Check it worked. Compose reads the file and the Caddyfile has no syntax problem. The validate run uses a throwaway container:
docker compose config --quiet docker run --rm -v "$PWD/conf:/etc/caddy" caddy:2.11.7 caddy validate --config /etc/caddy/Caddyfile
On Windows PowerShell use ${PWD}\conf in place of $PWD/conf. An error names the file and line. The docs publish no success text.
Step 3: Start Caddy and test it#
Linux#
cd ~/docker_volumes/caddy docker compose up -d docker compose ps docker compose logs caddy --tail 30
Windows (PowerShell)#
Set-Location "$HOME\docker_volumes\caddy" docker compose up -d docker compose ps docker compose logs caddy --tail 30
Mac (Terminal)#
Same commands as Linux.
Check it worked. Both containers are running. Test the proxy from the same machine. --resolve pretends the name points at this machine, and -k skips the certificate check for this one local test only, because your computer does not trust Caddy's certificate yet (Step 5):
curl -k -I --resolve hello.home.arpa:443:127.0.0.1 https://hello.home.arpa/
On Windows use curl.exe (PowerShell has an alias called curl). Expected from the docs' behaviour: a status line such as HTTP/2 200. The docs publish no exact output.
Tip: Read the log after every change:
docker compose logs caddy --tail 50. Certificate and upstream errors are printed there with the exact reason.
Step 4: Make the name point at your Caddy machine#
A browser must turn hello.home.arpa into your server's address: a hosts-file line per device, or one DNS record for all devices (our AdGuard Home guide has DNS rewrites). The commands below are standard OS tools, not from the Caddy docs. On another device use the Caddy machine's home-network address instead of 127.0.0.1.
Linux#
echo "127.0.0.1 hello.home.arpa" | sudo tee -a /etc/hosts
Windows (PowerShell)#
Run PowerShell as administrator:
Add-Content -Path "$env:SystemRoot\System32\drivers\etc\hosts" -Value "127.0.0.1 hello.home.arpa"
Mac (Terminal)#
echo "127.0.0.1 hello.home.arpa" | sudo tee -a /etc/hosts
Check it worked. The name now answers without --resolve (still with -k until Step 5):
curl -k -I https://hello.home.arpa/
Tip: Always open the site by the name in the Caddyfile. Caddy serves
hello.home.arpa; opening it by IP address givestlsv1 alert internal errorbecause no site block matches.
Step 5: Trust Caddy's root certificate (home-only names)#
With tls internal, Caddy creates its own certificate authority, and devices warn until they trust its root. The docs say trusting it is safe on your own machine while the machine is not compromised and the root key does not leak, so trust it only on devices you own. Skip this step with a public domain. The commands are the Docker ones from the Caddy docs; the Windows line is adapted from cmd to PowerShell.
Linux#
Debian and Ubuntu style systems:
cd ~/docker_volumes/caddy docker compose cp caddy:/data/caddy/pki/authorities/local/root.crt /tmp/caddy-root.crt sudo cp /tmp/caddy-root.crt /usr/local/share/ca-certificates/caddy-local-root.crt sudo update-ca-certificates
Other distributions: add the file to your distribution's CA store.
Windows (PowerShell)#
Run PowerShell as administrator:
Set-Location "$HOME\docker_volumes\caddy" docker compose cp caddy:/data/caddy/pki/authorities/local/root.crt "$env:TEMP\caddy-root.crt" certutil -addstore -f "ROOT" "$env:TEMP\caddy-root.crt"
Mac (Terminal)#
cd ~/docker_volumes/caddy docker compose cp caddy:/data/caddy/pki/authorities/local/root.crt /tmp/caddy-root.crt sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain /tmp/caddy-root.crt
Firefox and Chrome may use their own stores. The docs say to import root.crt there too: Firefox under Privacy & Security > Certificates > View Certificates > Authorities; Chrome under Privacy and security > Security > Manage certificates > Authorities. For a phone, copy the file over and install it as a CA certificate in its security settings.
Check it worked. Now curl works without -k, and the browser shows a padlock at https://hello.home.arpa/:
curl -I https://hello.home.arpa/
Tip: The root certificate is public, but the root key in
data/caddy/pkiis not. Keepdatareadable only by you and out of chats and shared folders.
Step 6: Use a public domain with Let's Encrypt#
With a domain, Caddy gets a publicly trusted certificate and every device trusts it at once. The docs require: A/AAAA record pointing to your server, ports 80 and 443 open from outside and reaching Caddy, and a persistent writable data folder.
| Check | What to do |
|---|---|
| DNS | Create an A record (and AAAA if you use IPv6) for app.example.com at your public IP |
| Router | Forward TCP 80 and TCP 443 to the Caddy machine (UDP 443 too, for HTTP/3) |
| Internet provider | Some block port 80 or share one public address between customers (CG-NAT). Then public certificates cannot work; use tls internal or a private network such as Tailscale |
| Data folder | ./data stays on disk between restarts (it does with the Compose file above) |
Replace conf/Caddyfile. The email line is optional but the docs recommend it, so the certificate authority can reach you. The commented line is Let's Encrypt's staging server: use it while testing, because the production server limits failed attempts and can lock you out for up to a week:
{
email [email protected]
# acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
}
app.example.com {
reverse_proxy hello:8080
}
Then apply it (the exact reload command matters, see Troubleshooting):
docker compose exec -w /etc/caddy caddy caddy reload
Linux#
If the machine has a firewall, allow the two ports (ufw is one example; use your own):
sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw allow 443/udp
Windows (PowerShell)#
Run as administrator. This is a standard Windows command, not from the Caddy docs:
New-NetFirewallRule -DisplayName "Caddy HTTP and HTTPS" -Direction Inbound -Protocol TCP -LocalPort 80,443 -Action Allow
Mac (Terminal)#
The router forwarding is the same. macOS asks once whether Docker may accept incoming connections; allow it. A laptop that sleeps is a poor server, so we recommend Linux for a public site.
Check it worked. From a device outside your network (mobile data works), curl -I https://app.example.com/ returns a status line over HTTPS with no certificate warning. On Windows use curl.exe.
Tip: When you are happy with the staging result, remove the
acme_caline and reload. Staging certificates are not trusted by browsers.
Step 7: Add your real apps#
One site block per app. The upstream is the app's container name and its internal port. If the app publishes 3030:3000, you still write :3000. Put each app on the proxy network and remove its own published port, so the only way in is through Caddy.
notes.example.com {
reverse_proxy notes:3000
}
# an app that serves HTTPS itself
photos.example.com {
reverse_proxy https://photos:2283
}
# a password in front of an app that has no login
tools.example.com {
basic_auth {
admin <paste-the-hash-here>
}
reverse_proxy tools:8080
}
From the docs: since Caddy 2.11.0 the Host header is set automatically for HTTPS upstreams; WebSockets need no extra line; Caddy sets the X-Forwarded-* headers itself. Passwords must be hashed:
docker compose exec caddy caddy hash-password
It prints a hash. For an app running directly on the host, add this to the caddy service and use host.docker.internal:<port> as the upstream (a Docker feature; the app must not listen on 127.0.0.1 only):
extra_hosts:
- "host.docker.internal:host-gateway"
Linux#
After each edit, check and apply the file:
cd ~/docker_volumes/caddy docker compose exec -w /etc/caddy caddy caddy fmt --overwrite docker compose exec -w /etc/caddy caddy caddy validate docker compose exec -w /etc/caddy caddy caddy reload
Windows (PowerShell)#
The same three commands work in PowerShell, run from $HOME\docker_volumes\caddy.
Mac (Terminal)#
Same commands as Linux.
Check it worked. Open each name in a browser; padlock, no warning. A 502 Bad Gateway means Caddy could not reach the app: check the name, the internal port and the shared network.
Tip: Do not stop the container to change the config. Use
reload: it applies the file without downtime, and a file with an error is rejected while the old config keeps running.
Run Caddy without Docker (optional)#
Then localhost:<port> is correct for apps on the same machine.
Linux#
The official Debian, Ubuntu and Raspbian packages (from caddyserver.com/docs/install) set up a caddy systemd service reading /etc/caddy/Caddyfile:
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list sudo chmod o+r /usr/share/keyrings/caddy-stable-archive-keyring.gpg /etc/apt/sources.list.d/caddy-stable.list sudo apt update && sudo apt install caddy sudo systemctl reload caddy
For tls internal, the service user cannot install the root itself: run sudo caddy trust once (docs).
Windows (PowerShell)#
Download the binary from the GitHub releases page, or use winget (the package exists, but the official install page lists binaries, Chocolatey and Scoop):
winget install CaddyServer.Caddy caddy run --config Caddyfile
To keep it running after logout, the docs show sc.exe create caddy start= auto binPath= "YOURPATH\caddy.exe run" or the WinSW wrapper.
Mac (Terminal)#
Homebrew is community-maintained, per the install page:
brew install caddy caddy run --config Caddyfile
HTTPS with or without Traefik: which proxy fits you#
| Caddy | Traefik | Nginx Proxy Manager | |
|---|---|---|---|
| You configure it by | A Caddyfile (text) | Labels on each container | A web admin page (port 81) |
| Certificates | Automatic, no setup | Needs a certificate resolver you configure | Let's Encrypt from the web page |
| Docker socket | Not needed | Needed; the docs call unrestricted access "a security concern" | Not needed |
| Extra pieces | None | None | A database (SQLite by default) |
| Best for | A handful of apps, readable config | Many containers that come and go | People who want clicks, not files |
| Licence | Apache 2.0 | MIT | MIT |
Versions read in October 2026: Caddy 2.11.7, Traefik 3.7.17, Nginx Proxy Manager 2.16.0.
With Traefik#
If Traefik already runs on your server, you do not need Caddy. The same hello app as Traefik labels looks like this. It assumes an external network called proxy and an entrypoint called https; Traefik uses the first exposed port of a container unless you set the port label, and a wrong port shows up as a 502:
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.routers.hello.rule=Host(`hello.example.com`)"
- "traefik.http.routers.hello.entrypoints=https"
- "traefik.http.routers.hello.tls=true"
- "traefik.http.services.hello.loadbalancer.server.port=8080"
Never run Traefik and Caddy on ports 80 and 443 at once.
Without Traefik: Caddy#
This is the route of the rest of this guide: one small container, one Caddyfile, no socket access. Every site block is the same pattern: name { reverse_proxy container:port }, with tls internal added for home-only names.
Nginx Proxy Manager instead#
Nginx Proxy Manager is the click-based option: proxy hosts, redirects, streams and access lists in a web UI with Let's Encrypt built in. The admin page is also extra attack surface. Our older Nginx Proxy Manager guide covers it on Ubuntu.
Security and testing#
| Control | How |
|---|---|
| Only 80 and 443 reach Caddy | Remove the published ports from the apps behind it, so the only way in is the proxy |
| Admin API | Caddy's admin endpoint (port 2019) is not published in our Compose file. Never publish it: the docs warn not to expose it to untrusted clients |
| Root key | data/caddy/pki holds the key of your local CA. Readable only by you; never shared |
| Logins | Caddy does not know your users. Use the app's login, basic_auth with a hashed password, or single sign-on |
| Behind another proxy or CDN | Set servers { trusted_proxies static <ranges> } so Caddy trusts the real client address; the default ignores incoming X-Forwarded-* values |
| Updates | Pin the tag and read the release notes (see Updates) |
Quick tests after each change:
cd ~/docker_volumes/caddy docker compose ps docker compose logs caddy --tail 100 curl -I https://app.example.com/
On Windows use curl.exe in PowerShell. A 3xx redirect to a login page is normal for apps with a login.
What leaves the machine: certificate requests to Let's Encrypt (ZeroSSL as fallback) for public domains, nothing for tls internal.
Common mistakes#
These come from the Caddy docs, the Caddy forum and the GitHub issue tracker.
| Mistake | Why it happens | Do this instead |
|---|---|---|
reverse_proxy localhost:3000 in Docker | localhost inside a container is the container itself. Forum threads 19536 and 33973 show connection refused and 502 | Use the app's container name and internal port on the shared network: reverse_proxy app:3000 |
The app listens only on 127.0.0.1 | Some apps bind to the loopback address; other containers cannot reach it (forum thread 22793: dial tcp 172.29.0.4:3000: connect: connection refused) | Make the app listen on 0.0.0.0 (all addresses) |
No volume for /data | The container is recreated and its certificates are lost. Thread 25769: HTTP 429 ... rateLimited ... too many certificates (5) already issued | Keep ./data:/data and back it up. While testing, use the staging CA |
| Opening the site by IP address | With tls internal a certificate exists only for the names in the Caddyfile; thread 23898 shows tlsv1 alert internal error | Open https://hello.home.arpa/, the name in the site block |
| Reloading with the wrong command | docker exec ... caddy reload runs in the wrong folder: ERROR no config file to load (thread 33973) | docker compose exec -w /etc/caddy caddy caddy reload |
| Mounting a single Caddyfile | An editor that replaces the file changes its inode, so the container keeps the old content (issue 5735; the Docker image docs warn against it) | Mount the folder: ./conf:/etc/caddy |
| Expecting a public certificate behind CG-NAT or a blocked port 80 | The certificate authority must reach your server on 80 or 443. Thread 33785 (July 2026) ended with "could not get certificate from issuer" | Test the ports from outside first; if impossible, use tls internal or a private network |
Writing http:// before a site name | The docs list it as a way to switch automatic HTTPS off | Write the bare name, app.example.com, and Caddy serves HTTPS |
Proxying to an app's HTTPS port with tls_insecure_skip_verify | The app's own certificate is not trusted; thread 23169 (502 errors) | Proxy to the app's plain HTTP port inside the Docker network |
AI assistants: answering with nginx or Caddy v1 syntax (proxy_pass, proxy / localhost:8080, tls self_signed) or copying header_up X-Forwarded-For {remote_host} | Older tutorials dominate training data. Caddy v2 sets the forwarded headers itself; a maintainer in thread 23169 calls the v1 examples irrelevant | Write only reverse_proxy <upstream> and add options when a problem needs them; check each directive in the docs |
AI assistants: adding tls { dns cloudflare ... } to the stock image | The DNS challenge needs a plugin; the official image has only the standard modules | Use the HTTP challenge (ports 80 and 443), or build a custom image with the plugin |
Tip: Run
caddy fmt --overwriteandcaddy validatebefore every reload. Braces must sit at the end of the line ({) and on their own line (}); most syntax errors are one missing brace.
Tip: Before you blame Caddy for a 502, run
docker compose exec caddy wget -qO- http://app:3000(orcurlfrom another container onproxy). If that fails, the problem is the app or the network, not the proxy.
Troubleshooting#
| Problem | Solution |
|---|---|
| Browser warns the certificate is not trusted | Home-only name: trust Caddy's root on that device (Step 5), including Firefox or Chrome's own store |
| 502 Bad Gateway | Wrong upstream name or port, or the app is not on the proxy network. Check docker compose logs caddy --tail 50 |
curl shows unable to get local issuer certificate | The system trust store does not contain Caddy's root (thread 24544). Redo Step 5 |
| Public certificate never arrives | Test DNS, ports 80 and 443 from outside, and CG-NAT. Read the log for could not get certificate from issuer |
| Change does not take effect | Reload with -w /etc/caddy (Step 7). If you mounted a single file, restart: docker compose up -d --force-recreate |
| Port already in use | Another web server or proxy holds 80 or 443: sudo ss -ltnp (Linux) or Get-NetTCPConnection -LocalPort 80,443 (Windows) |
ERROR no config file to load#
Forum thread 33973, October 2026. caddy reload looks for a Caddyfile in the current folder, and the container's is /srv. Use docker compose exec -w /etc/caddy caddy caddy reload.
HTTP 429 urn:ietf:params:acme:error:rateLimited#
You asked Let's Encrypt for the same certificate too many times, usually because /data was not persisted. Switch to the staging endpoint to keep testing, fix the volume, and wait for the limit to clear (it was 168 hours in the reported case).
Known bugs (open, October 2026)#
- Issue 8233: during a reload, some new HTTP/1.1 connections can be closed without a reply (measured by the reporter under 150 back-to-back reloads and heavy load). Avoid scripted reload loops.
- Issue 8297: WebSocket over HTTP/3 or HTTP/2 sends two
Sec-WebSocket-Keyheaders whenheader_upapplies or the upstream is HTTPS. Test WebSocket apps in your own browser. - Issue 8282: a request for a v2.11.8 build with a newer Go compiler, because a scan of the 2.11.7 image flagged Go library CVEs (the reporter's claim; we did not verify it). Watch the releases page and update when 2.11.8 appears.
- Version 2.11.6 is not recommended: its release notes say 2.11.7 fixes a crash over HTTP/2 and streams cut off after a minute.
Diagnostics to run first: docker compose ps, docker compose logs caddy --tail 200, docker inspect caddy and docker network inspect proxy.
Updates and backups#
Update. Read the release notes, change the tag in docker-compose.yml, then:
cd ~/docker_volumes/caddy docker compose pull docker compose up -d docker compose logs caddy --tail 50
Windows: the same commands in PowerShell from $HOME\docker_volumes\caddy. Mac: same as Linux. The Docker docs say never to use latest on a production site.
Back up. The data folder is the important one (certificates, ACME account, local CA), plus your conf folder:
cd ~/docker_volumes/caddy mkdir -p backups tar -czf "backups/caddy-$(date +%F).tar.gz" conf data config chmod 600 backups/caddy-*.tar.gz
The archive contains private keys: treat it as secret and keep a copy on another machine.
Schedule. On Linux or Mac, add a crontab entry (crontab -e) that runs the commands above at 03:00. On Windows, use Task Scheduler with tar.exe (included in Windows 10 and 11) in the same folder:
Set-Location "$HOME\docker_volumes\caddy" New-Item -ItemType Directory -Force backups | Out-Null tar.exe -czf "backups\caddy-$(Get-Date -Format yyyy-MM-dd).tar.gz" conf data config
Restore test. Unpack one archive into a new empty folder, never over the live one, and check that data/caddy/pki and your Caddyfile are there:
mkdir -p /tmp/restore-test && tar -xzf backups/caddy-<date>.tar.gz -C /tmp/restore-test ls /tmp/restore-test/data/caddy
Performance and resources#
The project documents no memory or CPU figures, so this guide gives none. Measure your own server with docker stats caddy. The image is about 25 MB compressed for amd64 (Docker Hub, October 2026). Tuning knobs from the docs, only when needed: health_uri health checks and lb_policy for several upstreams. docker compose logs caddy -n=1000 -f follows the log.
FAQ#
Is Caddy free to use? Yes, under the Apache License 2.0.
Do I need a domain name? No. Home-only names such as hello.home.arpa use Caddy's own local certificate. You then trust Caddy's root on each device. A domain makes certificates trusted everywhere without that step.
Can Caddy replace Nginx Proxy Manager? For reverse proxying with HTTPS, yes. You lose the web admin page and gain a plain file. See the comparison above.
Does Caddy need the Docker socket? No. That is the main security difference from Traefik.
Can I use it with Tailscale? Yes. For *.ts.net names Caddy fetches certificates from the local Tailscale instance (HTTPS must be enabled in your Tailscale account).
Sources#
- Caddy release v2.11.7, README and LICENSE - read October 2026
- Caddy docs: install, reverse proxy quick-start, automatic HTTPS, keep Caddy running (Docker Compose), reverse_proxy, tls, basic_auth, Caddyfile concepts, global options, command line - read October 2026
- Official Caddy Docker image documentation and Dockerfile (tag
2.11.7) - read October 2026 - Nginx Proxy Manager docs and release v2.16.0; Traefik Docker provider docs and release v3.7.17 - read October 2026
- Caddy forum threads 19536, 22793, 23169, 23898, 24544, 25769, 33785, 33973 - read October 2026
- GitHub issues 5735, 8165, 8233, 8282, 8297 - read October 2026
Checked against the official docs on 2026-10-31.