Global DNS-01 configuration question

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:

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:

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

That’s a pretty complex config, wow.

Could you run caddy adapt -p on your config to see what it produces with either version of the config?

The bits that matter are the apps > tls > automation, apps > tls > certificates (if any) and apps > http > servers > automatic_https.

I’d like to see how those differ, to understand why it works with one and not the other. There’s some pretty complex logic to consolidate the TLS automation config from Caddyfile to JSON, it might go wrong somewhere in there.

Since your config isn’t just one file, I can’t really easily test against it (I’d have to reconstruct the config structure with the imports etc).

1 Like

Yeah that’s why I kept it short initially hehe, I judged that those were the important parts, but that isn’t my call obviously!

I’m at a conference right now, I’ll do what you’re asking when I get home this evening.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.