1. The problem I’m having:
I’m migrating to Caddy away from Nginx. As part of my migration, I’m trying to replicate my cipher order of preference (AES256 > ChaCha20 > AES128) that I had set with Nginx using:
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256;
ssl_conf_command Options PrioritizeChaCha;
ssl_protocols TLSv1.3 TLSv1.2;
ssl_prefer_server_ciphers on;
Caddy’s defaults already replicated 3/4 of these Nginx options by default, except for the order of preference for my ciphers (Caddy’s default is AES128 > ChaCha20 > AES256). To replicate my old Nginx behaviour, I added this section to my Caddyfile under the “tls” directive (full Caddyfile attached at the bottom):
ciphers TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256 TLS_AES_128_GCM_SHA256
This, however, doesn’t actually change the order, and, for some reason, also completely disables TLSv1.2.
I looked through Caddy’s documentation for the tls directive, to make sure that I’m not entering something wrong, but it seems that I’m following Caddy’s configuration convention for setting the cipher order of preference. The documentation does mention that TLSv1.3 cipher suites are not customizable, ( TLS_AES_128_GCM_SHA256
TLS_CHACHA20_POLY1305_SHA256
TLS_AES_256_GCM_SHA384
are mandated by the TLSv1.3 spec), but with Nginx, I was always able to set their order of preference.
I’m very new to using Caddy, so any help is greatly appreciated.
2. Error messages and/or full log output:
May 04 10:48:35 rprusa.net systemd[1]: Starting Caddy web server...
May 04 10:48:35 rprusa.net caddy[122072]: {"level":"info","ts":1746370115.847155,"msg":"using config from file","file":"/etc/caddy/Caddyfile"}
May 04 10:48:35 rprusa.net caddy[122072]: {"level":"warn","ts":1746370115.8474283,"msg":"No files matching import glob pattern","pattern":"/etc/caddy/conf.d/*"}
May 04 10:48:35 rprusa.net caddy[122072]: {"level":"info","ts":1746370115.8489785,"msg":"adapted config to JSON","adapter":"caddyfile"}
May 04 10:48:35 rprusa.net caddy[122072]: {"level":"warn","ts":1746370115.8490016,"msg":"Caddyfile input is not formatted; run 'caddy fmt --overwrite' to fix inconsistencies","adapter":"caddyfile","file":"/etc/caddy/Caddyfile","line":12}
May 04 10:48:35 rprusa.net caddy[122072]: {"level":"info","ts":1746370115.849689,"logger":"http.auto_https","msg":"enabling automatic HTTP->HTTPS redirects","server_name":"srv0"}
May 04 10:48:35 rprusa.net caddy[122072]: {"level":"info","ts":1746370115.8498828,"logger":"tls.cache.maintenance","msg":"started background certificate maintenance","cache":"0xc000474800"}
May 04 10:48:35 rprusa.net caddy[122072]: {"level":"info","ts":1746370115.850025,"logger":"tls.cache.maintenance","msg":"stopped background certificate maintenance","cache":"0xc000474800"}
May 04 10:48:35 rprusa.net caddy[122072]: Valid configuration
May 04 10:48:35 rprusa.net caddy[122082]: caddy.HomeDir=/var/lib/caddy
May 04 10:48:35 rprusa.net caddy[122082]: caddy.AppDataDir=/var/lib/caddy
May 04 10:48:35 rprusa.net caddy[122082]: caddy.AppConfigDir=/etc/caddy
May 04 10:48:35 rprusa.net caddy[122082]: caddy.ConfigAutosavePath=/var/lib/caddy/autosave.json
May 04 10:48:35 rprusa.net caddy[122082]: caddy.Version=v2.9.1
May 04 10:48:35 rprusa.net caddy[122082]: runtime.GOOS=linux
May 04 10:48:35 rprusa.net caddy[122082]: runtime.GOARCH=amd64
May 04 10:48:35 rprusa.net caddy[122082]: runtime.Compiler=gc
May 04 10:48:35 rprusa.net caddy[122082]: runtime.NumCPU=4
May 04 10:48:35 rprusa.net caddy[122082]: runtime.GOMAXPROCS=4
May 04 10:48:35 rprusa.net caddy[122082]: runtime.Version=go1.24.1
May 04 10:48:35 rprusa.net caddy[122082]: os.Getwd=/
May 04 10:48:35 rprusa.net caddy[122082]: LANG=C.UTF-8
May 04 10:48:35 rprusa.net caddy[122082]: PATH=/usr/local/sbin:/usr/local/bin:/usr/bin
May 04 10:48:35 rprusa.net caddy[122082]: NOTIFY_SOCKET=/run/systemd/notify
May 04 10:48:35 rprusa.net caddy[122082]: USER=caddy
May 04 10:48:35 rprusa.net caddy[122082]: LOGNAME=caddy
May 04 10:48:35 rprusa.net caddy[122082]: HOME=/var/lib/caddy
May 04 10:48:35 rprusa.net caddy[122082]: INVOCATION_ID=93da5c7862744776803b08c3de10b8c4
May 04 10:48:35 rprusa.net caddy[122082]: JOURNAL_STREAM=8:119429
May 04 10:48:35 rprusa.net caddy[122082]: SYSTEMD_EXEC_PID=122082
May 04 10:48:35 rprusa.net caddy[122082]: MEMORY_PRESSURE_WATCH=/sys/fs/cgroup/system.slice/caddy.service/memory.pressure
May 04 10:48:35 rprusa.net caddy[122082]: MEMORY_PRESSURE_WRITE=c29tZSAyMDAwMDAgMjAwMDAwMAA=
May 04 10:48:35 rprusa.net caddy[122082]: XDG_DATA_HOME=/var/lib
May 04 10:48:35 rprusa.net caddy[122082]: XDG_CONFIG_HOME=/etc
May 04 10:48:35 rprusa.net caddy[122082]: {"level":"info","ts":1746370115.9751687,"msg":"using config from file","file":"/etc/caddy/Caddyfile"}
May 04 10:48:35 rprusa.net caddy[122082]: {"level":"warn","ts":1746370115.9754279,"msg":"No files matching import glob pattern","pattern":"/etc/caddy/conf.d/*"}
May 04 10:48:35 rprusa.net caddy[122082]: {"level":"info","ts":1746370115.9765553,"msg":"adapted config to JSON","adapter":"caddyfile"}
May 04 10:48:35 rprusa.net caddy[122082]: {"level":"warn","ts":1746370115.9765732,"msg":"Caddyfile input is not formatted; run 'caddy fmt --overwrite' to fix inconsistencies","adapter":"caddyfile","file":"/etc/caddy/Caddyfile","line":12}
May 04 10:48:35 rprusa.net caddy[122082]: {"level":"info","ts":1746370115.9775393,"logger":"admin","msg":"admin endpoint started","address":"unix//run/caddy/admin.socket","enforce_origin":false,"origins":["","//127.0.0.1","//::1"]}
May 04 10:48:35 rprusa.net caddy[122082]: {"level":"info","ts":1746370115.9778616,"logger":"tls.cache.maintenance","msg":"started background certificate maintenance","cache":"0xc0006e6580"}
May 04 10:48:35 rprusa.net caddy[122082]: {"level":"info","ts":1746370115.9778981,"logger":"http.auto_https","msg":"enabling automatic HTTP->HTTPS redirects","server_name":"srv0"}
May 04 10:48:35 rprusa.net caddy[122082]: {"level":"info","ts":1746370115.9788747,"logger":"http","msg":"enabling HTTP/3 listener","addr":":443"}
May 04 10:48:35 rprusa.net caddy[122082]: {"level":"info","ts":1746370115.9789827,"msg":"failed to sufficiently increase receive buffer size (was: 208 kiB, wanted: 7168 kiB, got: 416 kiB). See https://github.com/quic-go/quic-go/wiki/UDP-Buffer-Sizes for details."}
May 04 10:48:35 rprusa.net caddy[122082]: {"level":"info","ts":1746370115.9791396,"logger":"http.log","msg":"server running","name":"srv0","protocols":["h1","h2","h3"]}
May 04 10:48:35 rprusa.net caddy[122082]: {"level":"warn","ts":1746370115.9791992,"logger":"http","msg":"HTTP/2 skipped because it requires TLS","network":"tcp","addr":":80"}
May 04 10:48:35 rprusa.net caddy[122082]: {"level":"warn","ts":1746370115.9792056,"logger":"http","msg":"HTTP/3 skipped because it requires TLS","network":"tcp","addr":":80"}
May 04 10:48:35 rprusa.net caddy[122082]: {"level":"info","ts":1746370115.979211,"logger":"http.log","msg":"server running","name":"remaining_auto_https_redirects","protocols":["h1","h2","h3"]}
May 04 10:48:35 rprusa.net caddy[122082]: {"level":"info","ts":1746370115.979216,"logger":"http","msg":"enabling automatic TLS certificate management","domains":["www.rprusa.net","rprusa.net"]}
May 04 10:48:35 rprusa.net caddy[122082]: {"level":"info","ts":1746370115.980306,"msg":"autosaved config (load with --resume flag)","file":"/var/lib/caddy/autosave.json"}
May 04 10:48:35 rprusa.net caddy[122082]: {"level":"info","ts":1746370115.9803858,"msg":"serving initial configuration"}
May 04 10:48:35 rprusa.net systemd[1]: Started Caddy web server.
May 04 10:48:35 rprusa.net caddy[122082]: {"level":"info","ts":1746370115.98078,"logger":"tls","msg":"storage cleaning happened too recently; skipping for now","storage":"FileStorage:/var/lib/caddy","instance":"c59037b7-05dc-40ee-b886-a81d1badf835","try_again":1746456515.9807782,"try_again_in":86399.999999731}
May 04 10:48:35 rprusa.net caddy[122082]: {"level":"info","ts":1746370115.98105,"logger":"tls","msg":"finished cleaning storage units"}
3. Caddy version:
2.9.1
4. How I installed and ran Caddy:
a. System environment:
Arch Linux x86
Caddy v2.9.1 from Arch Linux repos
b. Command:
sudo systemctl enable --now caddy.service
c. Service/unit/compose file:
# caddy.service
#
# For using Caddy with a config file.
#
# Make sure the ExecStart and ExecReload commands are correct
# for your installation.
#
# See https://caddyserver.com/docs/install for instructions.
#
# WARNING: This service does not use the --resume flag, so if you
# use the API to make changes, they will be overwritten by the
# Caddyfile next time the service is restarted. If you intend to
# use Caddy's API to configure it, add the --resume flag to the
# `caddy run` command or use the caddy-api.service file instead.
[Unit]
Description=Caddy web server
Documentation=https://caddyserver.com/docs/
After=network-online.target
Wants=network-online.target
StartLimitIntervalSec=14400
StartLimitBurst=10
[Service]
Type=notify
User=caddy
Group=caddy
Environment=XDG_DATA_HOME=/var/lib
Environment=XDG_CONFIG_HOME=/etc
ExecStartPre=/usr/bin/caddy validate --config /etc/caddy/Caddyfile
ExecStart=/usr/bin/caddy run --environ --config /etc/caddy/Caddyfile
ExecReload=/usr/bin/caddy reload --config /etc/caddy/Caddyfile --force
ExecStopPost=/usr/bin/rm -f /run/caddy/admin.socket
# Do not allow the process to be restarted in a tight loop. If the
# process fails to start, something critical needs to be fixed.
Restart=on-abnormal
# Use graceful shutdown with a reasonable timeout
TimeoutStopSec=5s
LimitNOFILE=1048576
LimitNPROC=512
# Hardening options
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
DevicePolicy=closed
LockPersonality=true
MemoryAccounting=true
MemoryDenyWriteExecute=true
NoNewPrivileges=true
PrivateDevices=true
PrivateTmp=true
ProcSubset=pid
ProtectClock=true
ProtectControlGroups=true
ProtectHome=true
ProtectHostname=true
ProtectKernelLogs=true
ProtectKernelModules=true
ProtectKernelTunables=true
ProtectProc=invisible
ProtectSystem=strict
RemoveIPC=true
ReadWritePaths=/var/lib/caddy /var/log/caddy /run/caddy
RestrictNamespaces=true
RestrictRealtime=true
RestrictSUIDSGID=true
[Install]
WantedBy=multi-user.target
d. My complete Caddy config:
# The Caddyfile is an easy way to configure your Caddy web server.
#
# https://caddyserver.com/docs/caddyfile
#
# The configuration below serves a welcome page over HTTP on port 80.
# To use your own domain name (with automatic HTTPS), first make
# sure your domain's A/AAAA DNS records are properly pointed to
# this machine's public IP, then replace the line below with your
# domain name.
#
# https://caddyserver.com/docs/caddyfile/concepts#addresses
{
# Restrict the admin interface to a local unix file socket whose directory
# is restricted to caddy:caddy. By default the TCP socket allows arbitrary
# modification for any process and user that has access to the local
# interface. If admin over TCP is turned on one should make sure
# implications are well understood.
admin "unix//run/caddy/admin.socket"
preferred_chains {
root_common_name "ISRG Root X2"
}
}
(tls_options) {
tls {
ciphers TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256 TLS_AES_128_GCM_SHA256
}
}
(security_headers) {
header {
# HSTS
Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
## WebSec headers
X-Content-Type-Options "nosniff"
Referrer-Policy "no-referrer"
Cross-Origin-Opener-Policy "same-origin"
Cross-Origin-Embedder-Policy "require-corp"
Origin-Agent-Cluster "?1"
# CSP
Content-Security-Policy "default-src 'none'; child-src 'self'; connect-src 'self' https://rprusa.net/; font-src 'self'; img-src 'self'; manifest-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; webrtc 'block'; form-action 'none'; frame-ancestors 'none'; base-uri 'none'"
# obsolete and replaced with Content-Security-Policy frame-ancestors 'none'
X-Frame-Options "DENY"
# obsolete, unsafe and replaced with strong Content-Security-Policy
X-XSS-Protection "0"
# Permission manifest
Permissions-Policy "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), bluetooth=(), camera=(), clipboard-read=(), clipboard-write=(), display-capture=(), document-domain=(), encrypted-media=(), fullscreen=(), gamepad=(), geolocation=(), gyroscope=(), hid=(), idle-detection=(), interest-cohort=(), keyboard-map=(), local-fonts=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), serial=(), speaker-selection=(), sync-xhr=(), usb=(), xr-spatial-tracking=()"
}
}
rprusa.net {
root * /website/public
import security_headers
import tls_options
file_server
}
www.rprusa.net {
redir https://rprusa.net[uri]
}
# Import additional caddy config files in /etc/caddy/conf.d/
import /etc/caddy/conf.d/*