I thought it might be interesting to show how the wildcard caddy block changed as a result of discussions arising from this thread.
This is an excerpt of the original Caddyfile including the wildcard Caddy block and a ‘patch’ for load balancing.
office.udance.com.au {
encode gzip zstd
import logging udance.com.au
reverse_proxy 10.1.1.13:8880 10.1.3.132:9006
}
*.udance.com.au {
encode gzip zstd
import logging udance.com.au
map {labels.3} {backend} {online} {mtls} {phpmyadmin} {
# HOSTNAME BACKEND ONLINE mTLS PHPMYADMIN #COMMENT
#---------------------------------------------------------------
# Docker containers
# office 10.1.1.13:8880 yes no no # OnlyOffice
portainer 10.1.1.13:9000 yes no no # Portainer
truecommand 10.1.1.13:8086 yes no no # TrueCommand 2 nightly
tc123 10.1.1.13:8082 yes no no # TrueCommand v1.2.3
tc132 10.1.1.13:8084 yes no no # TrueCommand v1.3.2
nc-fpm 10.1.1.13:8031 yes no no # Nextcloud+Caddy
wordpress 10.1.1.13:5050 yes no no # WordPress
nc-apache 10.1.1.13:8030 yes no no # Nextcloud+Apache
collabora 10.1.1.13:9980 yes no no # Collabora
# Jails
rslsync 10.1.1.22:8888 yes no no # Resilio Sync
cloud 10.1.1.29:80 yes no no # Nextcloud
heimdall 10.1.1.23:80 yes no no # Heimdall
test test.lan:80 yes no yes # test.udance.com.au
blog 10.1.1.54:80 yes no yes # blog.udance.com.au
basil 10.1.1.56:80 yes no yes # basil.udance.com.au
sachika 10.1.1.57:80 yes no yes # sachika.udance.com.au
file file.lan:443 yes yes yes # file.udance.com.au
default unknown yes no no # subdomain does not exist
}
route {
# Error handling
@unknown expression `{backend} == "unknown"`
respond @unknown "Denied" 403
# Site offline
@offline expression `{online} == "no"`
redir @offline https://udance.statuspage.io temporary
@split {
expression `{online} == "split"`
not remote_ip 10.1.1.0/24 10.1.2.0/24
}
redir @split https://udance.statuspage.io temporary
# Authenticate phpMyAdmin on production WordPress sites
@phpmyadmin expression `{phpmyadmin} == "yes"`
route @phpmyadmin {
import authorise /phpmyadmin*
}
# Fix when using the Nextcloud+Apache Docker image with Caddy.
@nc-apache host nc-apache.udance.com.au
route @nc-apache {
redir /.well-known/carddav /remote.php/carddav 301
redir /.well-known/caldav /remote.php/caldav 301
}
# Enable HSTS for Nextcloud
@hsts host cloud.udance.com.au
header @hsts Strict-Transport-Security "max-age=31536000;"
# Secure backend communication
@mtls expression `{mtls} == "yes"`
reverse_proxy @mtls {backend} {
header_up Host {http.reverse_proxy.upstream.hostport}
header_up X-Forwarded-Host {host}
transport http {
tls
}
}
# Unsecured backend communication
@nomtls expression `{mtls} == "no"`
reverse_proxy @nomtls {backend}
}
}
After decoupling reverse_proxy
from the map
directive, this is what the Caddyfile excerpt looks like now. The outcome is the same. The difference is that the API endpoint described earlier in this thread can now be used to monitor the availability of upsteam servers.
*.udance.com.au {
encode gzip zstd
import logging udance.com.au
map {labels.3} {online} {
# HOSTNAME ONLINE # HSTS mTLS PHPMY COMMENT
# ADMIN
#---------------------------------------------------------------
# Docker
collabora yes # no no no Collabora
nc-apache yes # yes no no Nextcloud+Apache
nc-fpm yes # yes no no Nextcloud+Caddy
office yes # no no no OnlyOffice
portainer yes # no no no Portainer
tc123 yes # no no no TrueCommand v1.2.3
tc132 yes # no no no TrueCommand v1.3.2
truecommand yes # no no no TrueCommand 2 nightly
wordpress yes # no no no WordPress
# Jails
basil yes # no no yes basil.udance.com.au
blog yes # no no yes blog.udance.com.au
cloud yes # yes no no Nextcloud
file yes # no yes no file.udance.com.au
heimdall yes # no no no Heimdall
rslsync yes # no no no Resilio Sync
sachika yes # no no yes sachika.udance.com.au
test yes # no no yes test.udance.com.au
default alien # no no no subdomain does not exist
}
route {
### Exception handling ###
# Non-existent subdomain
@unknown expression `{online} == "alien"`
respond @unknown "Denied" 403
# Site offline
@offline expression `{online} == "no"`
redir @offline https://udance.statuspage.io temporary
@split {
expression `{online} == "split"`
not remote_ip 10.1.1.0/24 10.1.2.0/24
}
redir @split https://udance.statuspage.io temporary
# Authenticate phpMyAdmin on production WordPress sites
@phpmyadminhosts header_regexp phpmyadmin host (test|blog|basil|sachika)\.udance\.com\.au
route @phpmyadminhosts {
import authorise /phpmyadmin*
}
# Enable HSTS for Nextcloud sites
@hstshosts header_regexp hsts host (cloud|nc-apache|nc-fpm)\.udance\.com\.au
header @hstshosts Strict-Transport-Security "max-age=31536000;"
# Fix when using the Nextcloud+Apache Docker image with Caddy.
@nc-apachefix host nc-apache.udance.com.au
route @nc-apachefix {
redir /.well-known/carddav /remote.php/carddav 301
redir /.well-known/caldav /remote.php/caldav 301
}
### Reverse Proxies ###
# Docker
@collabora host collabora.udance.com.au
@nc-apache host nc-apache.udance.com.au
@nc-fpm host nc-fpm.udance.com.au
@office host office.udance.com.au
@portainer host portainer.udance.com.au
@tc123 host tc123.udance.com.au
@tc132 host tc132.udance.com.au
@truecommand host truecommand.udance.com.au
@wordpress host wordpress.udance.com.au
reverse_proxy @collabora 10.1.1.13:9980
reverse_proxy @nc-apache 10.1.1.13:8030
reverse_proxy @nc-fpm 10.1.1.13:8031
reverse_proxy @office 10.1.1.13:8880 10.1.3.132:9006
reverse_proxy @portainer 10.1.1.13:9000
reverse_proxy @tc123 10.1.1.13:8082
reverse_proxy @tc132 10.1.1.13:8084
reverse_proxy @truecommand 10.1.1.13:8086
reverse_proxy @wordpress 10.1.1.13:5050
# Jails
@basil host basil.udance.com.au
@blog host blog.udance.com.au
@cloud host cloud.udance.com.au
@file host file.udance.com.au
@heimdall host heimdall.udance.com.au
@rslsync host rslsync.udance.com.au
@sachika host sachika.udance.com.au
@test host test.udance.com.au
reverse_proxy @basil 10.1.1.56
reverse_proxy @blog 10.1.1.54
reverse_proxy @cloud 10.1.1.29
reverse_proxy @heimdall 10.1.1.23
reverse_proxy @rslsync 10.1.1.22:8888
reverse_proxy @sachika 10.1.1.57
reverse_proxy @test test.lan
reverse_proxy @file https://file.lan {
header_up Host {http.reverse_proxy.upstream.hostport}
header_up X-Forwarded-Host {host}
}
}
}
There are basically three sections to the Caddy wildcard block. At the top is a map
block; in the middle is some exception handling, and in the lower third are the reverse proxies. Some observations:
- I still find the
map
handler really useful for an ‘at a glance’ bird’s eye view of what’s happening in the wildcard Caddy block. Time-dependent actions, like whether a site online or not, are an active part of themap
handler. More permanent subdomain specifics are tabulated in the comments section, but dealt with under exception handling. - The exception handling (routing-first) section immediately following the
map
Caddy block serves several purposes.
a. Handles boundary conditions and switching for themap
handler.
b. Addresses subtle differences between subdomains. - In the exception handling section, I found
reg_exp
very useful for describing traits common to a subset of subdomains. For instance, it was an effective alternative to using a combination ofmap
, expression logic and a snippet for phpmyadmin basic auth. - Though I lost the association of subdomains and ip addresses when backends were included in the
map
block, I got that association back again in the lower portion of the wildcard Caddy block where the reverse proxies are documented. The trick here was to match the names of the request handlers for the reverse proxies to the labels used in themap
block. This form of documentation also lends itself to extra upstream servers being included for reverse proxies. - A further benefit of decoupling
reverse_proxy
andmap
is the slightly less abstract logic for mTLS (lastreverse_proxy
in the wildcard Caddy block).