{ admin localhost:2019 } #### Shared Snippets (common) { tls {{CADDY_TLS_EMAIL:-admin@example.com}} { dns cloudflare {env.CLOUDFLARE_DNS_TOKEN} # DNS-01 must see the public Cloudflare zone, not the internal Technitium split-DNS zone. # Exclude the Caddy DMZ host from DNS DNAT so these queries are not redirected to Technitium. resolvers 1.1.1.1 1.0.0.1 } header { # Enable HSTS Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" X-XSS-Protection 0 # Prevent browsers from incorrectly detecting non-scripts as scripts and MIME type sniffing X-Content-Type-Options nosniff Referrer-Policy same-origin -Server X-Frame-Options "SAMEORIGIN" Permissions-Policy "geolocation=(self), microphone=(), camera=(), interest-cohort=()" import content-security-general } } #### Content Security Snippets (content-security-general) { # Default setup # Disable unsafe inline/eval and plugins, only load scripts and stylesheets from same origin, fonts from google, # and images from same origin and imgur. Sites should aim for policies like this. Content-Security-Policy "frame-ancestors 'self' https://*.{{CADDY_BASE_DOMAIN:-home.example.com}}" #"default-src 'self'; style-src 'self'; script-src 'self'; font-src 'self'; img-src 'self'; form-action 'self'; connect-src 'self'; frame-ancestors 'none';" #default-src 'none'; font-src https://fonts.gstatic.com; img-src 'self' https://i.imgur.com; object-src 'none'; script-src 'self'; style-src 'self' } (content-security-basic) { # Most basic setup # Disable unsafe inline/eval, only load resources from same origin except also allow images from imgur # Also disables the execution of plugins Content-Security-Policy default-src 'self'; img-src 'self' https://i.imgur.com; object-src 'none' } (content-security-api) { # API # Disable the loading of any resources and disable framing, recommended for APIs to use Content-Security-Policy default-src 'none'; frame-ancestors 'none' } # Shared block for office admin apps with self-signed upstream TLS. (office-admin-upstream) { transport http { tls_insecure_skip_verify } header_up Host {upstream_hostport} } # Like office-admin-upstream but also strips the Origin header. # UniFi devices (UDM, UNAS) validate Origin on WebSocket upgrades and reject # connections where Origin doesn't match their own hostname, causing real-time # dashboard stats to show N/A. Stripping Origin bypasses that check. (unifi-upstream) { transport http { tls_insecure_skip_verify } header_up Host {upstream_hostport} header_up -Origin } # Shared security headers for office admin apps. (office-admin-security) { header { Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" X-Content-Type-Options "nosniff" X-Frame-Options "SAMEORIGIN" Referrer-Policy "no-referrer" } encode gzip } #### VLAN 1 — LAN (TRUSTED zone, 10.0.1.x) udm.{{CADDY_BASE_DOMAIN:-home.example.com}} { import common reverse_proxy {{CADDY_LAN_PREFIX:-10.0.1}}.1:443 { import unifi-upstream } import office-admin-security } switchlite8poe.{{CADDY_BASE_DOMAIN:-home.example.com}} { import common reverse_proxy {{CADDY_LAN_PREFIX:-10.0.1}}.6 } #### VLAN 10 — Office (TRUSTED zone, 10.0.10.x) ## Physical / static devices dns01.{{CADDY_BASE_DOMAIN:-home.example.com}} { import common reverse_proxy {{DNS_BIND_IP:-10.0.10.5}}:5380 } pbs.{{CADDY_BASE_DOMAIN:-home.example.com}} { import common reverse_proxy {{CADDY_OFFICE_PREFIX:-10.0.10}}.8:8007 { import office-admin-upstream } import office-admin-security } proxmox.{{CADDY_BASE_DOMAIN:-home.example.com}} { import common reverse_proxy {{CADDY_OFFICE_PREFIX:-10.0.10}}.10:8006 { import office-admin-upstream } import office-admin-security } nas.{{CADDY_BASE_DOMAIN:-home.example.com}} { import common reverse_proxy {{CADDY_OFFICE_PREFIX:-10.0.10}}.24:5000 } unas.{{CADDY_BASE_DOMAIN:-home.example.com}} { import common reverse_proxy {{CADDY_OFFICE_PREFIX:-10.0.10}}.25:443 { import unifi-upstream } import office-admin-security } ## LXC Apps (.6–.19; all in use) bento-pdf.{{CADDY_BASE_DOMAIN:-home.example.com}} { import common reverse_proxy {{CADDY_OFFICE_PREFIX:-10.0.10}}.6:8080 } it-tools.{{CADDY_BASE_DOMAIN:-home.example.com}} { import common reverse_proxy {{CADDY_OFFICE_PREFIX:-10.0.10}}.7:80 } paperless-ngx.{{CADDY_BASE_DOMAIN:-home.example.com}} { import common reverse_proxy {{CADDY_OFFICE_PREFIX:-10.0.10}}.11:8000 { header_up Host {host} } encode gzip } beszel.{{CADDY_BASE_DOMAIN:-home.example.com}} { import common reverse_proxy {{CADDY_OFFICE_PREFIX:-10.0.10}}.13:8090 } ytd.{{CADDY_BASE_DOMAIN:-home.example.com}} { import common reverse_proxy {{CADDY_OFFICE_PREFIX:-10.0.10}}.14:3087 } immich.{{CADDY_BASE_DOMAIN:-home.example.com}} { import common reverse_proxy {{CADDY_OFFICE_PREFIX:-10.0.10}}.15:2283 } vaultwarden.{{CADDY_BASE_DOMAIN:-home.example.com}} { import common reverse_proxy {{CADDY_OFFICE_PREFIX:-10.0.10}}.16:8800 } gramps.{{CADDY_BASE_DOMAIN:-home.example.com}} { import common reverse_proxy {{CADDY_OFFICE_PREFIX:-10.0.10}}.17:80 } koffan.{{CADDY_BASE_DOMAIN:-home.example.com}} { import common reverse_proxy {{CADDY_OFFICE_PREFIX:-10.0.10}}.12:8080 } # Forgejo (LXC 114, .18) -- publicly exposed at git.brianpooe.com (proxied via # the VPS over NetBird for outside visitors) as well as reachable directly on # the LAN via split-horizon DNS. One canonical hostname everywhere, so this # is NOT under CADDY_BASE_DOMAIN like other internal-only apps. # Git-over-SSH is not proxyable through Caddy -- forgejo.brianpooe.com # resolves straight to the LXC (10.0.10.18) instead, bypassing Caddy entirely. # Root redirects to the .profile landing page instead of the repo explore # list -- see the Forgejo README's "Public Exposure" section. git.brianpooe.com { import common redir / /brianpooe 302 reverse_proxy {{CADDY_OFFICE_PREFIX:-10.0.10}}.18:3000 } # Hermes Agent web dashboard (LXC 113, not docker — systemd service) hermes.{{CADDY_BASE_DOMAIN:-home.example.com}} { import common # Upstream bug: with a single password-only auth provider the gate # auto-redirects to the OAuth-only /auth/login endpoint, which 500s. # Send browsers to the server-rendered /login form instead. redir /auth/login* /login reverse_proxy {{CADDY_OFFICE_PREFIX:-10.0.10}}.19:9119 } ## Arr Stack — LXC 101 (.20) bazarr.{{CADDY_BASE_DOMAIN:-home.example.com}} { import common reverse_proxy {{CADDY_OFFICE_PREFIX:-10.0.10}}.20:6767 } emby.{{CADDY_BASE_DOMAIN:-home.example.com}} { import common reverse_proxy {{CADDY_OFFICE_PREFIX:-10.0.10}}.20:8096 } flaresolverr.{{CADDY_BASE_DOMAIN:-home.example.com}} { import common reverse_proxy {{CADDY_OFFICE_PREFIX:-10.0.10}}.20:8191 } gluetun.{{CADDY_BASE_DOMAIN:-home.example.com}} { import common reverse_proxy {{CADDY_OFFICE_PREFIX:-10.0.10}}.20:8000 } prowlarr.{{CADDY_BASE_DOMAIN:-home.example.com}} { import common reverse_proxy {{CADDY_OFFICE_PREFIX:-10.0.10}}.20:9696 } qbittorrent.{{CADDY_BASE_DOMAIN:-home.example.com}} { import common reverse_proxy {{CADDY_OFFICE_PREFIX:-10.0.10}}.20:8080 } radarr.{{CADDY_BASE_DOMAIN:-home.example.com}} { import common reverse_proxy {{CADDY_OFFICE_PREFIX:-10.0.10}}.20:7878 } sabnzbd.{{CADDY_BASE_DOMAIN:-home.example.com}} { import common reverse_proxy {{CADDY_OFFICE_PREFIX:-10.0.10}}.20:8282 } seerr.{{CADDY_BASE_DOMAIN:-home.example.com}} { import common reverse_proxy {{CADDY_OFFICE_PREFIX:-10.0.10}}.20:5055 } sonarr.{{CADDY_BASE_DOMAIN:-home.example.com}} { import common reverse_proxy {{CADDY_OFFICE_PREFIX:-10.0.10}}.20:8989 } #### VLAN 30 — IoT (IoT zone, 10.0.30.x) zigbee.{{CADDY_BASE_DOMAIN:-home.example.com}} { import common reverse_proxy {{CADDY_IOT_PREFIX:-10.0.30}}.5 } # Requires in HA configuration.yaml: # http: # use_x_forwarded_for: true # trusted_proxies: # - ha.{{CADDY_BASE_DOMAIN:-home.example.com}} { import common reverse_proxy {{CADDY_IOT_PREFIX:-10.0.30}}.6:8123 } z2m.{{CADDY_BASE_DOMAIN:-home.example.com}} { import common reverse_proxy {{CADDY_IOT_PREFIX:-10.0.30}}.6:8080 }