1. The problem I’m having:
I have a single Caddy server that is available over two domains (one domain’s nameservers became unreliable so I added another one and keep using both). The domains’ contents are supposed to be equivalent with the obvious difference of a different certificate location path and the domain name itself. I have two specific subdomains that don’t use mTLS and multiple others that do. This setup results in 4 blocks that each share a lot of the settings (of which 2 blocks share even more and the other 2 share even more too).
In my Caddyfile I have these 4 blocks in order of appearance:
- DOMAIN2, non-mTLS
- DOMAIN, non-mTLS
- DOMAIN2, mTLS
- DOMAIN, mTLS
I handle the certificate renewal myself in other containers, the logs are rotated only in a single config instance as I’ve seen it recommended somewhere.
Is there a way I can decrease all this repetition, or to streamline my configuration in any other way?
2. Error messages and/or full log output:
N/A
3. Caddy version:
v2.10.2
4. How I installed and ran Caddy:
a. System environment:
Fedora Server 43, x86_64, docker-ce v28.5.2, Docker Compose v2.40.3
b. Command:
docker compose up -d --build
```
$ cat caddy.dockerfile
FROM caddy:2-builder AS builder
RUN xcaddy build \
--with github.com/hslatman/caddy-crowdsec-bouncer/http \
--with github.com/porech/caddy-maxmind-geolocation
FROM caddy:2
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
```
c. Service/unit/compose file:
N/A
d. My complete Caddy config:
```
# global options block
{
## This sets up the endpoint, but does not do any actions
crowdsec {
api_key <API_KEY>
api_url http://crowdsec:8080
}
# this ensures that the CrowdSec module is executed before any other HTTP handlers
order crowdsec first
# this only affects loggers not touched later (= only startup logs)
log {
format json {
time_format iso8601
}
}
auto_https disable_certs
}
(protected_endpoint) {
forward_auth authelia:9091 {
uri /api/authz/forward-auth
copy_headers Remote-User Remote-Groups Remote-Name Remote-Email
}
}
(mutual_tls) {
tls {
protocols tls1.3
client_auth {
mode require_and_verify
trust_pool file {
pem_file /etc/caddy/ca.crt
}
}
}
}
(log_stderr) {
log {
format json {
time_format iso8601
}
output stderr
}
}
(log_main_rotated) {
log {
format json {
time_format iso8601
}
output file /logs/access.log {
roll_size 30MiB
roll_uncompressed
roll_keep 3
}
}
}
(log_main_unrotated) {
log {
format json {
time_format iso8601
}
output file /logs/access.log
}
}
(geo_restrictions) {
@denied {
# block (NOT DE) and (NOT local) = allow either DE or local
not maxmind_geolocation {
db_path "/usr/share/GeoIP/GeoLite2-Country.mmdb"
allow_countries DE
}
not remote_ip private_ranges
}
@deniedLOCAL {
not remote_ip private_ranges
}
}
www.{$DOMAIN2}, wiki.{$DOMAIN2} {
# this is for crowdsec parsing...
import log_main_rotated
# ...and let's keep the stderr output for Portainer as well
import log_stderr
# only make this work for DE IPs (or local IPs)
import geo_restrictions
tls /etc/letsencrypt2/live/{$DOMAIN2}/fullchain.pem /etc/letsencrypt2/live/{$DOMAIN2}/privkey.pem
# this does the actual crowdsec check
crowdsec
@wiki host wiki.{$DOMAIN2}
handle @wiki {
abort @denied
reverse_proxy dokuwiki:80
}
@www host www.{$DOMAIN2}
handle @www {
abort @denied
reverse_proxy php-apache:80
}
}
www.{$DOMAIN}, wiki.{$DOMAIN} {
# this is for crowdsec parsing...
import log_main_unrotated
# ...and let's keep the stderr output for Portainer as well
import log_stderr
# only make this work for DE IPs (or local IPs)
import geo_restrictions
tls /etc/letsencrypt/live/{$DOMAIN}/fullchain.pem /etc/letsencrypt/live/{$DOMAIN}/privkey.pem
# this does the actual crowdsec check
crowdsec
@wiki host wiki.{$DOMAIN}
handle @wiki {
abort @denied
#import protected_endpoint
reverse_proxy dokuwiki:80
}
@www host www.{$DOMAIN}
handle @www {
abort @denied
#import protected_endpoint
reverse_proxy php-apache:80
}
}
*.{$DOMAIN2} {
import mutual_tls
# this is for crowdsec parsing...
import log_main_unrotated
# ...and let's keep the stderr output for Portainer as well
import log_stderr
# only make this work for DE IPs (or local IPs)
import geo_restrictions
tls /etc/letsencrypt2/live/{$DOMAIN2}/fullchain.pem /etc/letsencrypt2/live/{$DOMAIN2}/privkey.pem
# can we check here?
crowdsec
@auth host auth.{$DOMAIN2}
handle @auth {
abort @denied
reverse_proxy authelia:9091
}
@dozzle host dozzle.{$DOMAIN2}
handle @dozzle {
abort @denied
#abort @deniedLOCAL
import protected_endpoint
reverse_proxy dozzle:8080
}
# more
# subdomains
# here
}
*.{$DOMAIN} {
import mutual_tls
# this is for crowdsec parsing...
import log_main_unrotated
# ...and let's keep the stderr output for Portainer as well
import log_stderr
# only make this work for DE IPs (or local IPs)
import geo_restrictions
tls /etc/letsencrypt/live/{$DOMAIN}/fullchain.pem /etc/letsencrypt/live/{$DOMAIN}/privkey.pem
# can we check here?
crowdsec
@auth host auth.{$DOMAIN}
handle @auth {
abort @denied
reverse_proxy authelia:9091
}
@dozzle host dozzle.{$DOMAIN}
handle @dozzle {
abort @denied
#abort @deniedLOCAL
import protected_endpoint
reverse_proxy dozzle:8080
}
# more
# subdomains
# here
}
```
5. Links to relevant resources:
N/A