Public repos are now browsable/clonable anonymously (HTTPS) and over SSH, proxied through the Hetzner VPS. Unlike other public apps this is deliberately not behind the oauth2-proxy/Zitadel gate, since Forgejo already enforces its own per-repo visibility. Root redirects to the .profile landing page. Documents the REQUIRE_SIGNIN_VIEW gotcha and the Zitadel OIDC redirect_uri fix needed whenever FORGEJO_DOMAIN changes.
10 KiB
Caddy Firewall Policies — Missing and Incorrect Rules
Note
For the complete list of custom firewall rules, see 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:
-
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 CaddyIoT10.0.30.6(Home Assistant IP)AnyCADDY10.0.15.5(Caddy IP)443git 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 toBlock All Trafficbelow it. -
ufwon 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):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.comandforgejo.brianpooe.comare each their own single-name Technitium zone (Arecord direct to10.0.15.5and10.0.10.18respectively) so LAN clients resolve them locally while the public*.brianpooe.comwildcard sends everyone else to the VPS. No UniFi policy changes were needed for the HTTPS path — it already flows through the existingALLOW CADDY to Forgejorule 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 second10.0.10.18/32resource (routing peerpi-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 to10.0.10.18:22via asocatcontainer (~/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.