1. The problem I’m having:
I’m trying to migrate from my system installation of Caddy into one containerized with Docker/Compose to make building custom binaries and integrating with everything else easier.
However, when stopping my system Caddy (systemctl stop caddy) and bring up my containerized Caddy, it doesn’t appear to function and it being inaccessible on the outside even either http or https. I cannot access anything on it, Caddy itself doesn’t appear to do much in logs (it doesn’t renew/fetch certificates, etc). Kind of at a loss of what I could be poking at to see where the problem could be. I’ve even tried debug which doesn’t seem to make it spit out anything extra.
2. Error messages and/or full log output:
With my system’s Caddy
Relevant, functioning Caddy logs:
Apr 16 17:52:11 synth.download caddy[245118]: {"level":"info","ts":1744825931.5050786,"msg":"using config from file","file":"/etc/caddy/Caddyfile"}
Apr 16 17:52:11 synth.download caddy[245118]: {"level":"warn","ts":1744825931.514972,"logger":"caddyfile","msg":"Unnecessary header_up X-Forwarded-For: the reverse proxy's default behavior is to pass headers to the upstream"}
Apr 16 17:52:11 synth.download caddy[245118]: {"level":"warn","ts":1744825931.5151222,"logger":"caddyfile","msg":"Unnecessary header_up X-Forwarded-For: the reverse proxy's default behavior is to pass headers to the upstream"}
Apr 16 17:52:11 synth.download caddy[245118]: {"level":"info","ts":1744825931.5250201,"msg":"adapted config to JSON","adapter":"caddyfile"}
Apr 16 17:52:11 synth.download caddy[245118]: {"level":"warn","ts":1744825931.5250835,"msg":"Caddyfile input is not formatted; run 'caddy fmt --overwrite' to fix inconsistencies","adapter":"caddyfile","file":"/etc/caddy/Caddyfile","line":5}
Apr 16 17:52:11 synth.download caddy[245118]: {"level":"info","ts":1744825931.5326803,"msg":"redirected default logger","from":"stderr","to":"/var/log/caddy/caddy"}
Apr 16 17:52:11 synth.download caddy[245118]: {"level":"info","ts":1744825931.5987442,"msg":"[INFO][FileStorage:/var/lib/caddy/.local/share/caddy] Lock for 'issue_cert_*.pds.synth.download' is stale (created: 2025-04-16 17:41:43.662212691 +0000 UTC, last update: 2025-04-16 17:51:54.331108266 +0000 UTC); removing then retrying: /var/lib/caddy/.local/share/caddy/locks/issue_cert_wildcard_.pds.synth.download.lock"}
Apr 16 17:52:11 synth.download systemd[1]: Started caddy.service - Caddy.
Using curl -vL:
$ curl -vL synth.download
* Host synth.download:80 was resolved.
* IPv6: 2a0a:4cc0:80:108b::
* IPv4: 152.53.49.149
* Trying [2a0a:4cc0:80:108b::]:80...
* Connected to synth.download (2a0a:4cc0:80:108b::) port 80
> GET / HTTP/1.1
> Host: synth.download
> User-Agent: curl/8.5.0
> Accept: */*
>
< HTTP/1.1 308 Permanent Redirect
< Connection: close
< Location: https://synth.download/
< Server: Caddy
< Date: Wed, 16 Apr 2025 18:20:05 GMT
< Content-Length: 0
<
* Closing connection
* Clear auth, redirects to port from 80 to 443
* Issue another request to this URL: 'https://synth.download/'
* Host synth.download:443 was resolved.
* IPv6: 2a0a:4cc0:80:108b::
* IPv4: 152.53.49.149
* Trying [2a0a:4cc0:80:108b::]:443...
* Connected to synth.download (2a0a:4cc0:80:108b::) port 443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* CAfile: /etc/ssl/certs/ca-certificates.crt
* CApath: /etc/ssl/certs
* 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 / X25519 / id-ecPublicKey
* ALPN: server accepted h2
* Server certificate:
* subject: CN=synth.download
* start date: Feb 26 16:15:44 2025 GMT
* expire date: May 27 16:15:43 2025 GMT
* subjectAltName: host "synth.download" matched cert's "synth.download"
* issuer: C=US; O=Let's Encrypt; CN=E6
* SSL certificate verify ok.
* Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA384
* Certificate level 1: Public key type EC/secp384r1 (384/192 Bits/secBits), signed using sha256WithRSAEncryption
* Certificate level 2: Public key type RSA (4096/152 Bits/secBits), signed using sha256WithRSAEncryption
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://synth.download/
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: synth.download]
* [HTTP/2] [1] [:path: /]
* [HTTP/2] [1] [user-agent: curl/8.5.0]
* [HTTP/2] [1] [accept: */*]
> GET / HTTP/2
> Host: synth.download
> User-Agent: curl/8.5.0
> Accept: */*
>
< HTTP/2 200
< accept-ranges: bytes
< alt-svc: h3=":443"; ma=2592000
< content-type: text/html; charset=utf-8
< etag: "d97datvsaxsyka3"
< last-modified: Tue, 15 Apr 2025 16:53:38 GMT
< server: Caddy
< vary: Accept-Encoding
< content-length: 26283
< date: Wed, 16 Apr 2025 18:20:05 GMT
<
< [ in between here would be the entire html of my website ] >
* Connection #1 to host synth.download left intact
In Docker
Logs, appearing fine:
caddy-1 | {"level":"info","ts":1744827972.1217363,"msg":"using config from file","file":"/etc/caddy/Caddyfile"}
caddy-1 | {"level":"warn","ts":1744827972.1300132,"logger":"caddyfile","msg":"Unnecessary header_up X-Forwarded-For: the reverse proxy's default behavior is to pass headers to the upstream"}
caddy-1 | {"level":"warn","ts":1744827972.1301796,"logger":"caddyfile","msg":"Unnecessary header_up X-Forwarded-For: the reverse proxy's default behavior is to pass headers to the upstream"}
caddy-1 | {"level":"info","ts":1744827972.143104,"msg":"adapted config to JSON","adapter":"caddyfile"}
caddy-1 | {"level":"warn","ts":1744827972.143157,"msg":"Caddyfile input is not formatted; run 'caddy fmt --overwrite' to fix inconsistencies","adapter":"caddyfile","file":"/etc/caddy/Caddyfile","line":2}
caddy-1 | {"level":"info","ts":1744827972.150389,"msg":"redirected default logger","from":"stderr","to":"/var/log/caddy/caddy"}
Using curl -vL:
$ curl -vL synth.download
* Host synth.download:80 was resolved.
* IPv6: 2a0a:4cc0:80:108b::
* IPv4: 152.53.49.149
* Trying [2a0a:4cc0:80:108b::]:80...
* Connected to synth.download (2a0a:4cc0:80:108b::) port 80
> GET / HTTP/1.1
> Host: synth.download
> User-Agent: curl/8.5.0
> Accept: */*
>
< HTTP/1.1 308 Permanent Redirect
< Connection: close
< Location: https://synth.download/
< Server: Caddy
< Date: Wed, 16 Apr 2025 18:26:16 GMT
< Content-Length: 0
<
* Closing connection
* Clear auth, redirects to port from 80 to 443
* Issue another request to this URL: 'https://synth.download/'
* Host synth.download:443 was resolved.
* IPv6: 2a0a:4cc0:80:108b::
* IPv4: 152.53.49.149
* Trying [2a0a:4cc0:80:108b::]:443...
* Connected to synth.download (2a0a:4cc0:80:108b::) port 443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* CAfile: /etc/ssl/certs/ca-certificates.crt
* CApath: /etc/ssl/certs
* TLSv1.3 (IN), TLS alert, internal error (592):
* OpenSSL/3.0.13: error:0A000438:SSL routines::tlsv1 alert internal error
* Closing connection
curl: (35) OpenSSL/3.0.13: error:0A000438:SSL routines::tlsv1 alert internal error
3. Caddy version:
System Caddy is v2.9.1, Docker Caddy is v2.9.1
4. How I installed and ran Caddy:
a. System environment:
Ubuntu Server 24.04.2 LTS on ARM64, Caddy installed via apt from Caddy’s repositories
Docker 28.0.4, installed via apt from Docker’s repositories, Caddy built with caddyx.
b. Command:
System:
sudo systemctl start caddy # if already not started on boot
Docker:
cd /srv/docker/caddy && docker compose up -d
c. Service/unit/compose file:
System Caddy managed/started by the systemd unit included with the package.
Dockerfile:
# Builder
FROM caddy:builder AS builder
# latest version of go
#COPY --from=golang:alpine /usr/local/go/ /usr/local/go/
RUN xcaddy build \
--with github.com/caddy-dns/porkbun \
--with github.com/mholt/caddy-events-exec@a5d053d
# Container
FROM caddy:latest
# latest version of go
#COPY --from=golang:alpine /usr/local/go/ /usr/local/go/
# install additional packages
RUN apk add --no-cache tzdata curl
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
Compose:
services:
caddy:
build: .
restart: always
#ports:
# - "80:80"
# - "443:443"
# - "443:443/udp"
network_mode: host
cap_add:
- NET_ADMIN
volumes:
- ./conf:/etc/caddy
- ./caddy_data:/data
- ./external_certs:/mnt/external_certs
- /var/www:/var/www
- /var/log/caddy:/var/log/caddy
d. My complete Caddy config:
{
debug
email <email>
acme_dns porkbun {
api_key <key>
api_secret_key <key>
}
events {
on cert_obtained exec /etc/caddy/scripts/cert-obtained.sh "{event.data.identifier}" "{event.data.certificate_path}" "{event.data.private_key_path}"
}
log {
output file /var/log/caddy/caddy {
roll_size 10MB
roll_keep 10
}
}
}
synth.download {
root * /var/www/website
encode zstd gzip
file_server
# iceshrimp.net split domain
redir /.well-known/webfinger https://beeping.synth.download{uri} 301
redir /.well-known/host-meta https://beeping.synth.download{uri} 301
redir /.well-known/nodeinfo https://beeping.synth.download{uri} 301
# redirect pub to public directory
redir /pub /pub/
handle_path /pub/* {
root * /var/www/pub
file_server browse
encode zstd gzip
}
# xmpp xep-0156
handle /.well-known/host-meta {
@options method OPTIONS
handle @options {
header Access-Control-Allow-Origin "*"
header Content-Type "application/xrd+xml"
}
}
handle /.well-known/host-meta.json {
@options method OPTIONS
handle @options {
header Access-Control-Allow-Origin "*"
header Content-Type "application/xrd+xml"
}
}
# 🐱
handle_errors {
rewrite * /{err.status_code}
reverse_proxy https://http.cat {
header_up Host {upstream_hostport}
replace_status {err.status_code}
}
}
log {
output file /var/log/caddy/website {
roll_size 10MB
roll_keep 10
}
}
handle_path /robots.txt {
root * /var/www/robots.txt
file_server
encode zstd gzip
}
}
# pocket id authentication
auth.synth.download {
encode zstd gzip
reverse_proxy /api/* 127.0.0.1:30522
reverse_proxy /.well-known/* 127.0.0.1:30522
reverse_proxy /* 127.0.0.1:19241
log {
output file /var/log/caddy/zitadel {
roll_size 10MB
roll_keep 10
}
}
handle_path /robots.txt {
root * /var/www/robots.txt
file_server
encode zstd gzip
}
}
# notebook subdomain
# ideally this would have been a subfolder(??) (synth.download/notebook) but it Really Hated that so whatever, we're doing this now
notebook.synth.download {
root * /var/www/quartz
try_files {path} {path}.html {path}/ =404
file_server
encode zstd gzip
log {
output file /var/log/caddy/notebook {
roll_size 10MB
roll_keep 10
}
}
handle_path /robots.txt {
root * /var/www/robots.txt
file_server
encode zstd gzip
}
}
# disappearing subdomain
disappearing.synth.download {
root * /var/www/website/subdomain/disappearing
file_server
encode zstd gzip
@redir {
path /subdomain/disappearing/style.css
path /font.css
}
handle @redir {
root * /var/www/website
encode zstd gzip
file_server
}
log {
output file /var/log/caddy/disappearing {
roll_size 10MB
roll_keep 10
}
}
handle_path /robots.txt {
root * /var/www/robots.txt
file_server
encode zstd gzip
}
}
# vaultwarden
vault.synth.download {
encode zstd gzip
reverse_proxy 127.0.0.1:60838 {
header_up X-Real-IP {remote_host}
}
log {
output file /var/log/caddy/vaultwarden {
roll_size 10MB
roll_keep 10
}
}
handle_path /robots.txt {
root * /var/www/robots.txt
file_server
encode zstd gzip
}
}
# redlib
reddit.synth.download {
encode zstd gzip
reverse_proxy 127.0.0.1:51617
log {
output file /var/log/caddy/redlib {
roll_size 10MB
roll_keep 10
}
}
handle_path /robots.txt {
root * /var/www/robots.txt
file_server
encode zstd gzip
}
}
# freshrss
rss.synth.download {
encode zstd gzip
reverse_proxy 127.0.0.1:27819
log {
output file /var/log/caddy/freshrss {
roll_size 10MB
roll_keep 10
}
}
handle_path /robots.txt {
root * /var/www/robots.txt
file_server
encode zstd gzip
}
}
# forgejo
forged.synth.download {
encode zstd gzip
reverse_proxy 127.0.0.1:41807
log {
output file /var/log/caddy/forgejo {
roll_size 10MB
roll_keep 10
}
}
handle_path /robots.txt {
root * /var/www/robots.txt
file_server
encode zstd gzip
}
}
# mailserver
# we're just using caddy to generate certificates for us
mx1.synth.download {
# Optional, can be useful for troubleshooting
# connection to Caddy with correct certificate:
respond "Hello DMS"
}
# xmpp
xmpp.synth.download {
handle /ws* {
reverse_proxy 127.0.0.1:5080 {
header_up X-Real-IP {remote_host}
header_up Host {host}
header_up X-Forwarded-For {remote_host}
header_up Upgrade {http.upgrade}
header_up Connection {http.connection}
}
}
handle /* {
reverse_proxy 127.0.0.1:5080 {
header_up X-Real-IP {remote_host}
header_up Host synth.download
header_up X-Forwarded-For {remote_host}
}
}
}
upload.xmpp.synth.download {
request_body {
max_size 100m
}
handle /upload/* {
@options method OPTIONS
handle @options {
header Access-Control-Allow-Origin "*"
header Access-Control-Allow-Methods "PUT, GET, OPTIONS, HEAD"
header Access-Control-Allow-Headers "Authorization, Content-Type"
header Access-Control-Allow-Credentials "true"
header Content-Length "0"
header Content-Type "text/plain"
respond 200
}
reverse_proxy 127.0.0.1:5050
}
}
muc.xmpp.synth.download, proxy.xmpp.synth.download, pubsub.xmpp.synth.download {
respond "what if it was called gyattmpp instead"
}
mta-sts.synth.download {
root * /var/www/mta-sts
file_server
encode gzip
log {
output file /var/log/caddy/mta-sts {
roll_size 10MB
roll_keep 10
}
}
handle_path /robots.txt {
root * /var/www/robots.txt
file_server
encode zstd gzip
}
}
# iceshrimp
beeping.synth.download {
encode zstd gzip
reverse_proxy 127.0.0.1:24042
handle_path /favicon.png {
root * /var/www/website/assets/synth.download/favicon.png
file_server
encode zstd gzip
}
handle_path /_content/Iceshrimp.Assets.Branding/favicon.wy3b9djz5j.png {
root * /var/www/website/assets/synth.download/favicon.png
file_server
encode zstd gzip
}
log {
output file /var/log/caddy/iceshrimp {
roll_size 10MB
roll_keep 10
}
}
handle_path /robots.txt {
root * /var/www/robots.txt
file_server
encode zstd gzip
}
}
# mastodon-chuckya fe for iceshrimp
masto.beeping.synth.download {
root * /var/www/chuckya-fe
file_server
encode zstd gzip
handle_path /favicon.png {
root * /var/www/website/assets/synth.download/favicon.png
file_server
encode zstd gzip
}
try_files {path} /
log {
output file /var/log/caddy/mastofe {
roll_size 10MB
roll_keep 10
}
}
handle_path /robots.txt {
root * /var/www/robots.txt
file_server
encode zstd gzip
}
}
# phanpy for iceshrimp
phanpy.beeping.synth.download {
root * /var/www/phanpy
file_server
encode zstd gzip
try_files {path} /
log {
output file /var/log/caddy/phanpy {
roll_size 10MB
roll_keep 10
}
}
handle_path /robots.txt {
root * /var/www/robots.txt
file_server
encode zstd gzip
}
}
# akkoma fe for iceshrimp
akko.beeping.synth.download {
@redir {
path /oauth/*
path /api/*
path /nodeinfo/*
}
handle @redir {
reverse_proxy 127.0.0.1:24042
encode zstd gzip
}
handle_path /favicon.png {
root * /var/www/website/assets/synth.download/favicon.png
file_server
encode zstd gzip
}
handle {
header Cache-Control "max-age=3600"
try_files {path} /
root * /var/www/akkoma-fe
encode zstd gzip
file_server
}
handle_path /static/* {
header Cache-Control "max-age=3600"
root * /var/www/akkoma-fe/static
encode zstd gzip
file_server
}
log {
output file /var/log/caddy/akkofe {
roll_size 10MB
roll_keep 10
}
}
handle_path /robots.txt {
root * /var/www/robots.txt
file_server
encode zstd gzip
}
}
# sharkey
booping.synth.download {
encode zstd gzip
reverse_proxy 127.0.0.1:60628
log {
output file /var/log/caddy/sharkey {
roll_size 10MB
roll_keep 10
}
}
handle_path /robots.txt {
root * /var/www/robots.txt
file_server
encode zstd gzip
}
handle_path /vite/CHzYc-_9.svg {
root * /var/www/logo.svg
file_server
encode zstd gzip
}
handle_path /vite/D49xyTxq.png {
root * /var/www/troll.png
file_server
encode zstd gzip
}
}
# atproto pds
*.pds.synth.download, pds.synth.download {
encode zstd gzip
reverse_proxy 127.0.0.1:24318
log {
output file /var/log/caddy/pds {
roll_size 10MB
roll_keep 10
}
}
handle_path /robots.txt {
root * /var/www/robots.txt
file_server
encode zstd gzip
}
}
# ask-js
asking.synth.download {
encode zstd gzip
reverse_proxy 127.0.0.1:20617
log {
output file /var/log/caddy/ask-js {
roll_size 10MB
roll_keep 10
}
}
handle_path /robots.txt {
root * /var/www/robots.txt
file_server
encode zstd gzip
}
}
# upload service (dumbdrop)
upload.synth.download {
encode zstd gzip
reverse_proxy 127.0.0.1:31011
log {
output file /var/log/caddy/upload {
roll_size 10MB
roll_keep 10
}
}
handle_path /robots.txt {
root * /var/www/robots.txt
file_server
encode zstd gzip
}
}
# dumbwhois
whois.synth.download {
encode zstd gzip
reverse_proxy 127.0.0.1:22106
log {
output file /var/log/caddy/whois {
roll_size 10MB
roll_keep 10
}
}
handle_path /robots.txt {
root * /var/www/robots.txt
file_server
encode zstd gzip
}
}
# :)
adarkroom.synth.download {
root * /var/www/adarkroom
file_server
encode gzip
log {
output file /var/log/caddy/adarkroom {
roll_size 10MB
roll_keep 10
}
}
handle_path /robots.txt {
root * /var/www/robots.txt
file_server
encode zstd gzip
}
}