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
}
}