# Caddy Firewall Policies — Missing and Incorrect Rules > [!NOTE] > For the complete list of custom firewall rules, see [firewall-rules.md](firewall-rules.md). Audit findings from the zone policy review. Apply these in UniFi **Settings → Policy Engine → Policy Table** in the order shown. All custom policies must be above the built-in `Block All Traffic` terminal rule. --- ## 0. Immediate Cleanup — Redundant and Incorrect Rules Sort these out before adding anything new. ### Delete — completely redundant | Policy | Why | |---|---| | `Allow CADDY to Gateway DNS` | The Caddy Pi's `resolv.conf` points to Technitium at `10.0.10.5` (TRUSTED). `Allow CADDY to DNS` already covers that. The UDM Gateway DNS forwarder is not in use. | ### Keep — intra-zone rules that are actually required UniFi's documentation implies intra-zone traffic is allowed by default, but in practice this does **not** apply to custom zones — removing these breaks intra-zone traffic. | Policy | Why to keep | |---|---| | `Allow TRUSTED to TRUSTED Any` | Without this rule, devices within TRUSTED cannot reach each other (confirmed: removing it broke SSH to Technitium). | | `Allow IoT to IoT Any` | Same — IoT intra-zone traffic requires an explicit allow for custom zones. | ### Fix — wrong action | Policy | Problem | Fix | |---|---|---| | `Allow FAMILY to CADDY` | Action is set to **Block** instead of Allow. FAMILY devices cannot reach Caddy at all. | Edit the policy and change Action to **Allow**. | ### Review — direct pinholes that bypass Caddy These work but contradict the network design (§7.3/7.4), which says MEDIA and FAMILY should only reach internal services through Caddy. Once the `ALLOW CADDY to arr stack` backend policy (§2 below) is in place, both zones can reach Emby via `emby.yourdomain.com` through Caddy instead. | Policy | Impact of keeping | Impact of removing | |---|---|---| | `Allow MEDIA to Emby` | Apple TV can connect directly to LXC — wider attack surface, bypasses Caddy logging and auth. | Apple TV must use `emby.yourdomain.com` via Caddy. Requires Caddy backend policy in place first. | | `Allow FAMILY to EMBY` | Family devices bypass Caddy for Emby. | Family devices must use `emby.yourdomain.com` via Caddy. Requires Caddy backend policy in place first. | Remove these only **after** the `ALLOW CADDY to arr stack` policy is created and confirmed working. --- ## 1. Fix: Allow FAMILY to CADDY (currently set to Block) The existing `Allow FAMILY to CADDY` policy has **Action = Block**. Edit it and change the action to **Allow**. | Field | Value | |---|---| | Name | `Allow FAMILY to CADDY` | | Action | **Allow** (currently wrong — set to Block) | | Source zone | `FAMILY` | | Source IP | Any | | Destination zone | `CADDY` | | Destination IP | Any | | Destination port | `443` | | Protocol | TCP | --- ## 2. CADDY → TRUSTED Backend App Policies Caddy at `10.0.15.5` cannot proxy to any backend apps in the TRUSTED zone until these policies exist. Create one policy per row. Every policy in this table uses: - **Action:** Allow - **Source zone:** `CADDY` - **Source IP:** `10.0.15.5` - **Destination zone:** `TRUSTED` - **Protocol:** TCP | Policy name | Destination IP | Destination port(s) | Purpose | |---|---|---|---| | `ALLOW CADDY to arr stack` | `10.0.10.20` | `5055,6767,7878,8000,8080,8096,8191,8282,8989,9696` | Arr-stack LXC (bazarr, radarr, gluetun, qbittorrent, emby, flaresolverr, sabnzbd, sonarr, prowlarr, seerr) | | `ALLOW CADDY to Technitium UI` | `10.0.10.5` | `5380` | Technitium admin UI | | `ALLOW CADDY to Proxmox` | `10.0.10.10` | `8006` | Proxmox VE | | `ALLOW CADDY to PBS` | `10.0.10.8` | `8007` | Proxmox Backup Server | | `ALLOW CADDY to LAN web devices` | `10.0.1.6` | `80` | Lite-8 PoE switch UI | | `ALLOW CADDY to Beszel` | `10.0.10.13` | `8090` | Beszel monitoring | | `ALLOW CADDY to Gramps` | `10.0.10.17` | `80` | Gramps Web | | `ALLOW CADDY to Immich` | `10.0.10.15` | `2283` | Immich | | `ALLOW CADDY to IT Tools` | `10.0.10.7` | `80` | IT Tools | | `ALLOW CADDY to Paperless` | `10.0.10.11` | `8000` | Paperless-ngx | | `ALLOW CADDY to YTD` | `10.0.10.14` | `3087` | Youtarr | | `ALLOW CADDY to Bento PDF` | `10.0.10.6` | `8080` | Bento PDF | | `ALLOW CADDY to Koffan` | `10.0.10.12` | `8080` | Koffan (LXC 111) | | `ALLOW CADDY to Hermes` | `10.0.10.19` | `9119` | Hermes Agent dashboard (LXC 113) | | `ALLOW CADDY to Forgejo` | `10.0.10.18` | `3000,22` | Forgejo web UI (LXC 114) and git-over-SSH for hosts pulling from raspberrypi's zone | --- ## 3. CADDY → IoT Backend Policies Caddy cannot proxy to Home Assistant or Zigbee until these exist. Every policy in this table uses: - **Action:** Allow - **Source zone:** `CADDY` - **Source IP:** `10.0.15.5` - **Destination zone:** `IoT` - **Protocol:** TCP | Policy name | Destination IP | Destination port(s) | Purpose | |---|---|---|---| | `ALLOW CADDY to Home Automation` | `10.0.30.6` | `8080,8123` | Zigbee2MQTT and Home Assistant | | `ALLOW CADDY to Zigbee` | `10.0.30.5` | `80` | SLZB-06U Zigbee coordinator UI | --- ## 3a. IoT → CADDY Policies (reverse direction) The Home Assistant host itself also needs to reach Caddy directly — it pulls `homelab-blueprint` from Forgejo (`git.brianpooe.com`, proxied through Caddy) to deploy its own docker-compose stack. **Two independent layers enforce traffic into the Caddy host — both must allow it, and both were found misconfigured while wiring this up:** 1. **UniFi zone-based firewall** (router-level, VLAN → VLAN): | Policy name | Source zone | Source IP | Src. Port | Destination zone | Destination IP | Destination port(s) | Purpose | |---|---|---|---|---|---|---|---| | `Allow HA to Caddy` | `IoT` | `10.0.30.6` (Home Assistant IP) | `Any` | `CADDY` | `10.0.15.5` (Caddy IP) | `443` | git fetch/pull of homelab-blueprint from Forgejo | Src. Port must be `Any`, not restricted to HA's own listening ports (8080/8123) — this is outbound traffic *from* HA, so the source port is a random ephemeral port, not the port HA listens on. Setting Src. Port to a "Home Assistant" port object here was the original bug: the rule never matched, so traffic fell through to `Block All Traffic` below it. 2. **`ufw` on the Caddy host itself** (10.0.15.5) — a second, separate enforcement point the UniFi rule doesn't touch. It allow-lists inbound 80/443 per source subnet and did not include the IoT VLAN (`10.0.30.0/24`): ```bash sudo ufw allow from 10.0.30.0/24 to any port 80,443 proto tcp ``` ## 3b. CADDY / IoT → Forgejo git-over-SSH (port 22) Both raspberrypi (CADDY zone) and Home Assistant (IoT zone) clone `homelab-blueprint` over SSH directly against the Forgejo LXC (`10.0.10.18:22`), bypassing Caddy entirely — SSH can't be proxied through Caddy (HTTP/S only), so this traffic goes straight to the TRUSTED zone. `10.0.10.18` has no host-level firewall (`ufw` not installed, `iptables INPUT` policy `ACCEPT`), so only the UniFi zone rule matters here — no second enforcement layer to fight, unlike the Caddy host above. Forgejo's git-SSH runs on the standard port 22 on this LXC, not the usual `2222` (see `docker-compose-files/forgejo/README.md`) — the LXC's own admin sshd was relocated to port **2200** (systemd `ssh.socket` override) to free 22 up. Direct admin SSH to this LXC now needs `-p 2200`; `pct exec 114` from the Proxmox host is unaffected and still works normally. | Policy name | Source zone | Source IP | Destination zone | Destination IP | Destination port(s) | Purpose | |---|---|---|---|---|---|---| | `ALLOW CADDY to Forgejo` (existing rule, port added) | `CADDY` | `10.0.15.5` | `TRUSTED` | `10.0.10.18` | `3000,22` | Forgejo web UI + git-over-SSH from raspberrypi | | `Allow HA to Forgejo SSH` | `IoT` | `10.0.30.6` | `TRUSTED` | `10.0.10.18` | `22` | git-over-SSH from homeassistant | `ufw status` on that host is the fastest way to check which subnets can already reach it before assuming a UniFi rule is the problem. ## 3c. Public Exposure of Forgejo (Hetzner VPS) Forgejo is also reachable from the public internet, at `git.brianpooe.com` (HTTPS, public repos only — anonymous browsing/cloning, like GitHub) and `ssh://git@forgejo.brianpooe.com` (SSH, requires a registered account key — self-registration is disabled, so this is effectively you-only). Full detail lives in `docker-compose-files/forgejo/README.md` ("Public Exposure") and, on the VPS itself, `~/netbird-vps/docs/netbird-acl-playbook.md` and `public-app-sso.md`. Summary of what changed on the LAN side of that path: - **DNS is split-horizon**, not a new firewall rule: `git.brianpooe.com` and `forgejo.brianpooe.com` are each their own single-name Technitium zone (`A` record direct to `10.0.15.5` and `10.0.10.18` respectively) so LAN clients resolve them locally while the public `*.brianpooe.com` wildcard sends everyone else to the VPS. No UniFi policy changes were needed for the HTTPS path — it already flows through the existing `ALLOW CADDY to Forgejo` rule above. - **The public SSH path is a new NetBird route**, not a UniFi rule: the VPS NetBird peer previously only routed to the Pi Caddy (`10.0.15.5/32`). A second `10.0.10.18/32` resource (routing peer `pi-caddy`, "Model 2" in the playbook) was added so the VPS can reach Forgejo's git-SSH directly. The VPS forwards its own (now-freed) public port 22 to `10.0.10.18:22` via a `socat` container (`~/netbird-vps/ssh-forward/`) — the VPS's own admin sshd was relocated to port 2200 to make room. --- ## 4. Public DNS Block Policies (per zone) Without these, devices can bypass Technitium by querying public resolvers (`8.8.8.8`, `1.1.1.1`, etc.) directly. Place each policy **above** the broad `External` allow for its zone. Every policy in this table uses: - **Action:** Block - **Source IP:** Any - **Destination zone:** `External` - **Destination IP:** Any - **Protocol:** TCP/UDP - **Destination port:** `53` | Policy name | Source zone | |---|---| | `BLOCK FAMILY public DNS` | `FAMILY` | | `BLOCK MEDIA public DNS` | `MEDIA` | | `BLOCK IoT public DNS` | `IoT` | | `BLOCK GUEST public DNS` | `GUEST` | --- ## Policy Order Reference Within each zone's rule set, the evaluation order must be: ``` specific allows (DNS, DHCP, Caddy, app pinholes) public DNS block (above the External allow) External allow Gateway DHCP allow Gateway block Block All Traffic ← built-in terminal rule ``` If a broad block rule sits above a specific allow, the block wins and the intended traffic is dropped. Use **Reorder** in UniFi to verify positions after adding new policies.