1. The problem I’m having:
I just setup 2.9.0-beta.2 to play with auto_https prefer_wildcard
, now I want to configure DNS-01 support globally so that I don’t have to declare it on every site.
During that process I had some issues getting global DNS-01 settings to work correctly… I was using the global configuration options:
- acme_dns cloudflare {env.CLOUDFLARE_API_TOKEN}
- acme_ca https://acme-v02.api.letsencrypt.org/directory
I read the docs for these directives:
From that it wasn’t at all obvious to me if I’d need anything else but I ran into the following error:
2. Error messages and/or full log output:
{
"identifier": "*.sub.snipped.tld",
"issuer": "acme-v02.api.letsencrypt.org-directory",
"error": "[*.sub.snipped.tld] solving challenges: *.sub.snipped.tld: no solvers available for remaining challenges (configured=[http-01 tls-alpn-01] offered=[dns-01] remaining=[dns-01]) (order=https://acme-v02.api.letsencrypt.org/acme/order/snipped/snipped) (ca=https://acme-v02.api.letsencrypt.org/directory)"
}
3. Caddy version:
v2.9.0-beta.2 h1:LNFhfwLymRDQbPPV9+Wxo/lUrnYcNcco0sxhtoXQd7g=
4. How I installed and ran Caddy:
Caddy was built using xcaddy with the following plugins:
- GitHub - caddy-dns/cloudflare: Caddy module: dns.providers.cloudflare
- GitHub - hslatman/caddy-crowdsec-bouncer: A Caddy module that blocks malicious traffic based on decisions made by CrowdSec.
Used the following AUR package as a baseline: AUR (en) - caddy-custom
Caddy is run through systemd units, with the following added:
[Service]
Environment=CLOUDFLARE_API_TOKEN=snipped
Environment=CROWDSEC_API_KEY=snipped
a. System environment:
- Arch Linux AMD64
- kernel 6.10.10-arch1-1
- systemd v256.6-1
b. Command:
systemd unit
c. Service/unit/compose file:
[Unit]
Description=Caddy webserver
Documentation=https://caddyserver.com/docs/
After=network-online.target
Wants=network-online.target systemd-networkd-wait-online.service
StartLimitIntervalSec=14400
StartLimitBurst=10
[Service]
User=caddy
Group=caddy
# environment: store secrets here such as API tokens
EnvironmentFile=-/var/lib/caddy/envfile
# data directory: uses $XDG_DATA_HOME/caddy
# TLS certificates and other assets are stored here
Environment=XDG_DATA_HOME=/var/lib
# config directory: uses $XDG_CONFIG_HOME/caddy
Environment=XDG_CONFIG_HOME=/etc
# do not print --environ here, as it may contain API tokens!!
ExecStart=/usr/bin/caddy run --config /etc/caddy/Caddyfile
ExecReload=/usr/bin/caddy reload --config /etc/caddy/Caddyfile
# Do not allow the process to be restarted in a tight loop.
Restart=on-abnormal
# Use graceful shutdown with a reasonable timeout
KillMode=mixed
KillSignal=SIGQUIT
TimeoutStopSec=5s
# Sufficient resource limits
LimitNOFILE=1048576
LimitNPROC=512
# Grants binding to port 443...
AmbientCapabilities=CAP_NET_BIND_SERVICE
# ...and limits potentially inherited capabilities to this
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
# Hardening options
LockPersonality=true
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectControlGroups=true
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectSystem=strict
ReadWritePaths=/var/lib/caddy
ReadOnlyPaths=/etc/caddy
ReadOnlyPaths=-/var/lib/caddy/envfile
[Install]
WantedBy=multi-user.target
d. My complete Caddy config:
Caddyfile:
import /etc/caddy/conf.d/*
/etc/caddy/conf.d/01-global
{
admin off
auto_https prefer_wildcard
email my@email.tld
acme_dns cloudflare {env.CLOUDFLARE_API_TOKEN}
acme_ca https://acme-v02.api.letsencrypt.org/directory
log {
format console
}
order crowdsec first
crowdsec {
api_url http://localhost:8080
api_key {env.CROWDSEC_API_KEY}
ticker_interval 15s
}
}
/etc/caddy/conf.d/10-snippets
(common) {
header Server "Adrian in real-time"
}
(cors) {
@options {
method OPTIONS
}
header Access-Control-Allow-Origin "*"
header Access-Control-Allow-Methods "GET, POST, OPTIONS, HEAD, DELETE"
header Access-Control-Allow-Headers "Authorization, Origin, X-Requested-With, Content-Type, Accept"
respond @options 200
}
/etc/caddy/conf.d/99-sites
import /etc/caddy/sites-enabled/*
/etc/caddy/sites-enabled/sigproj
sig.domain1.tld {
crowdsec
reverse_proxy http://localhost:6969
}
/etc/caddy/sites-enabled/projectgroup1
pg1app1.domain1.tld {
@internal {
remote_ip 10.0.0.0/8 fe80::/16 2001:my:ipv6::/56
}
reverse_proxy @internal http://fs01.internal.tld:7878
}
pg1app2.domain1.tld {
@internal {
remote_ip 10.0.0.0/8 fe80::/16 2001:my:ipv6::/56
}
reverse_proxy @internal http://fs01.internal.tld:32768
}
pg1app3.domain1.tld {
@internal {
remote_ip 10.0.0.0/8 fe80::/16 2001:my:ipv6::/56
}
reverse_proxy @internal http://fs01.internal.tld:8989
}
pg1app4.domain1.tld {
@internal {
remote_ip 10.0.0.0/8 fe80::/16 2001:my:ipv6::/56
}
reverse_proxy @internal http://fs01.internal.tld:32769
}
pg1app5.domain1.tld {
reverse_proxy http://localhost:5055
}
pg1app6.domain1.tld {
reverse_proxy fs01.internal.tld:8096
}
mgmt.domain4.tld mgmt.domain1.tld {
@internal {
remote_ip 10.0.0.0/8 fe80::/16 2001:my:ipv6::/56
}
reverse_proxy @internal http://fs01.internal.tld:3000
}
/etc/caddy/sites-enabled/authentik
authentik.domain1.tld {
@internal {
remote_ip 10.0.0.0/8 fe80::/16 2001:my:ipv6::/56
}
reverse_proxy @internal https://web3.internal.tld:8443 {
transport http {
tls_insecure_skip_verify
}
}
}
/etc/caddy/sites-enabled/rssapp
rssapp.domain1.tld {
reverse_proxy http://localhost:58082
}
/etc/caddy/sites-enabled/ctfd
ctf.domain5.tld domain5.tld {
crowdsec
reverse_proxy http://web2.internal.tld:8989
}
#domain6.tld {
# root * /srv/http/ctf/getting-started
# header +Content-Disposition "attachment; filename=\"domain6.tld\"; filename*=\"domain6.tld\""
# file_server {
# index domain6.tld
# }
#}
#web-1.chal.domain5.tld {
# crowdsec
# reverse_proxy http://ctf.internal.tld:8080
#}
#
#xss-1.chal.domain5.tld {
# crowdsec
# reverse_proxy http://ctf.internal.tld:28080
#}
#
#xss-2.chal.domain5.tld {
# crowdsec
# reverse_proxy http://ctf.internal.tld:28081
#}
#
#multi-juicer.domain1.tld {
# crowdsec
# reverse_proxy http://kubelab.internal.tld:3001
#}
/etc/caddy/sites-enabled/myip
http://myip.domain1.tld http://ip.domain1.tld http://echo.domain1.tld http://ipv4.domain1.tld http://ipv6.domain1.tld {
crowdsec
@browser header_regexp User-Agent [Mm]ozilla
redir @browser https://{host}{uri}
reverse_proxy http://web2.internal.tld:9580
}
myip.domain1.tld ip.domain1.tld echo.domain1.tld ipv4.domain1.tld ipv6.domain1.tld {
crowdsec
reverse_proxy http://web2.internal.tld:9580
}
/etc/caddy/sites-enabled/vmhost
vmhost.domain1.tld {
@internal {
remote_ip 10.0.0.0/8 fe80::/16 2001:my:ipv6::/56
}
#tls {
# alpn http/1.1
#}
reverse_proxy @internal https://vmhost.internal.tld {
transport http {
tls_insecure_skip_verify
}
}
}
/etc/caddy/sites-enabled/homeassistant
ha.domain1.tld {
@internal {
remote_ip 10.0.0.0/8 fe80::/16 2001:my:ipv6::/56
}
reverse_proxy @internal http://homeassistant.iot.internal.tld:8123
}
/etc/caddy/sites-enabled/ipfs
gw.domain1.tld {
crowdsec
#reverse_proxy unix//run/ipfs/gateway.sock
reverse_proxy http://localhost:18080
}
*.ipfs.gw.domain1.tld *.ipns.gw.domain1.tld {
crowdsec
import cloudflare_dns
#reverse_proxy unix//run/ipfs/gateway.sock
reverse_proxy http://localhost:18080
}
api.gw.domain1.tld {
@internal {
remote_ip 10.0.0.0/8 fe80::/16 2001:my:ipv6::/56
}
reverse_proxy @internal http://localhost:5001
}
/etc/caddy/sites-enabled/speedtest
speedtest.domain1.tld {
reverse_proxy http://localhost:50180
}
/etc/caddy/sites-enabled/mdm
mdm.corp.domain1.tld {
reverse_proxy http://web3.internal.tld:7900
}
/etc/caddy/sites-enabled/projectgroup2
ps.domain1.tld {
@internal {
remote_ip 10.0.0.0/8 fe80::/16 2001:my:ipv6::/56
}
reverse_proxy @internal http://kubelab.internal.tld:8080
}
pg2app2.domain1.tld {
crowdsec
import common
reverse_proxy http://fs01.internal.tld:8465
}
/etc/caddy/sites-enabled/personal
(adrian) {
header +Permissions-Policy fullscreen=()
import common
}
f.domain2.tld {
import adrian
redir https://s.domain1.tld{uri}
}
finapp.domain1.tld, finapp.domain2.tld, finapp.domain3.tld {
import adrian
redir https://finapp.idnversionofdomain3.tld{uri}
}
http://domain3.tld:80 {
import common
redir https://domain3.tld{uri}
}
domain3.tld {
import adrian
header strict-transport-security "max-age=31556926; includeSubDomains; preload"
redir https://idnversionofdomain3.tld{uri}
}
domain1.tld, domain2.tld, domain4.tld {
import adrian
redir https://idnversionofdomain3.tld{uri}
}
idnversionofdomain3.tld, finapp.idnversionofdomain3.tld, f.domain1.tld {
import adrian
reverse_proxy http://localhost:14000 {
header_down -server
}
}
/etc/caddy/sites-enabled/video
video.domain1.tld videoapi.domain1.tld videoproxy.domain1.tld {
reverse_proxy http://localhost:58080
}
/etc/caddy/sites-enabled/portainer
web2.portainer.domain1.tld {
@internal {
remote_ip 10.0.0.0/8 fe80::/16 2001:my:ipv6::/56
}
reverse_proxy @internal https://web2.internal.tld:9443 {
transport http {
tls_insecure_skip_verify
}
}
}
web3.portainer.domain1.tld {
@internal {
remote_ip 10.0.0.0/8 fe80::/16 2001:my:ipv6::/56
}
reverse_proxy @internal https://web3.internal.tld:9443 {
transport http {
tls_insecure_skip_verify
}
}
}
fs01.portainer.domain1.tld {
@internal {
remote_ip 10.0.0.0/8 fe80::/16 2001:my:ipv6::/56
}
reverse_proxy @internal https://fs01.internal.tld:9443 {
transport http {
tls_insecure_skip_verify
}
}
}
/etc/caddy/sites-enabled/public
st.domain1.tld http://st.domain1.tld:80 {
import common
redir https://public.domain1.tld/speedtest.sh
}
public.domain1.tld p.domain1.tld public.domain4.tld p.domain4.tld public.domain2.tld p.domain2.tld public.domain3.tld p.domain3.tld {
import common
import cors
@internal {
remote_ip 10.0.0.0/8 fe80::/16 2001:my:ipv6::/56
}
file_server @internal {
root /srv/http/public
browse
}
file_server {
root /srv/http/public
}
}
/etc/caddy/sites-enabled/pentest
pwn.domain1.tld {
import cors
header +Content-Security-Policy "default-src 'none'; img-src 'self' data:; media-src 'self'; font-src 'self' cdn.jsdelivr.net; object-src 'self'; script-src 'self'; style-src 'self' cdn.jsdelivr.net"
header +Content-Security-Policy "default-src 'none'; img-src 'self'; media-src 'self'; font-src 'self' cdn.jsdelivr.net; object-src 'self'; script-src 'self'; style-src 'self' cdn.jsdelivr.net"
header +Content-Security-Policy "default-src 'none'; img-src 'self' blob:; media-src 'self'; object-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline' 'nonce-lmao'; style-src 'self' cdn.jsdelivr.net"
root * /srv/http/pwn.domain1.tld
encode gzip
file_server
}
redir-meta.domain1.tld {
redir http://169.254.170.2/v2/metadata
}
redir-file.domain1.tld {
redir file:///etc/passwd
}
redir-cats.domain1.tld {
redir https://placekitten.com/400/400
}
/etc/caddy/sites-enabled/projectgroup3
pg3app1.domain1.tld pg3app1.domain2.tld pg3app1.domain3.tld {
redir https://pg3app1.idnversionofdomain3.tld{uri}
}
pg3app1.idnversionofdomain3.tld {
reverse_proxy http://web2.internal.tld:6363
}
/etc/caddy/sites-enabled/netmgmt
netmgmt.domain1.tld {
@internal {
remote_ip 10.0.0.0/8 fe80::/16 2001:my:ipv6::/56
}
reverse_proxy @internal https://gw.internal.tld {
transport http {
tls_insecure_skip_verify
}
}
}
/etc/caddy/sites-enabled/wildcard-fallthrough
*.domain4.tld *.idnversionofdomain3.tld *.domain3.tld *.domain1.tld *.domain2.tld *.portainer.domain1.tld *.ctf.domain1.tld *.gw.domain1.tld {
@internal {
remote_ip 10.0.0.0/8 fe80::/16 2001:my:ipv6::/56
}
import common
respond status 404
}
5. Links to relevant resources:
The global config that ended up working in the end was:
{
admin off
auto_https prefer_wildcard
email my@email.tld
cert_issuer acme {
dir https://acme-v02.api.letsencrypt.org/directory
resolvers 1.1.1.1 1.0.0.1
dns cloudflare {env.CLOUDFLARE_API_TOKEN}
}
log {
format console
}
order crowdsec first
crowdsec {
api_url http://localhost:8080
api_key {env.CROWDSEC_API_KEY}
ticker_interval 15s
}
}