# 🧱 Firewall & Network Segmentation > [!NOTE] > No blanket "anything on the internal network can talk to anything else on it" rule, anywhere. Every real flow is explicit; everything else is logged and dropped. ```mermaid flowchart TD Internet(["Internet"]) --> Edge subgraph Edge["Edge Firewall"] Bogon["Bogon block +
IP-reputation blacklist"] NonstdSSH["SSH — nonstandard port only"] PubProxy["Public reverse-proxy layer"] IDS["Inline IDS/IPS"] end Edge -->|explicit rules only| LAN subgraph LAN["Internal Network"] direction TB Rule1["DNS · Secrets/PKI · Reverse proxy
Metrics/logs · Security agents
NTP · Admin SSH · File sharing"] Catchall["Everything else:
default-deny + log"] end ``` ## The one rule that matters most Every real service-to-service flow — DNS lookups, secrets/PKI traffic, reverse-proxy access, metrics/log shipping, security-agent reporting, NTP, admin SSH, file sharing, and a handful of specific app-to-app integrations — gets its own explicit allow rule. Anything not explicitly listed hits a default-deny-and-log catch-all. **This catch-all is the fleet's actual lateral-movement boundary**, not any single host's own firewall. > [!TIP] > A few explicit rules are narrower than they look on purpose — inbound access to the secrets platform's own admin API, for instance, is gated to only the two or three services that ever legitimately need it, not the whole fleet. ## Named groups, not raw addresses Every rule references named groups instead of hardcoded addresses — adding a host to a group is a single membership edit, not a hunt through every rule that needs to reach it. Groups nest: a "whole managed fleet" group is composed of several narrower per-role groups, so a rule against the broad group automatically covers new members of any narrower one. ## Per-host firewalls: minimal, not blanket Each host's own local firewall opens only the specific ports it actually needs. One host goes further: instead of one open port for its most sensitive service, individual source IPs get their own explicit rule. > [!WARNING] > **A real, documented gap**: on Docker hosts, container-published ports bypass the host's own firewall entirely — the container runtime's own network rules are evaluated first. Per-host firewall rules only actually protect the small number of *native*, non-containerized services on that host. The network-level segmentation above (and the edge firewall further out) still applies regardless — defense-in-depth gap, not an open-to-the-world one. ## Edge firewall: three jobs, one device ```mermaid flowchart LR EdgeFW["Edge Firewall"] --> J1["Public ingress
bogon block, IP-reputation feed,
nonstandard SSH port"] EdgeFW --> J2["Second reverse-proxy layer
for genuinely public apps only,
automated wildcard certs"] EdgeFW --> J3["Security tooling
inline IDS/IPS, cert renewal"] ``` > [!IMPORTANT] > **Failure impact, thought through in advance**: if the edge firewall goes down, public access and outbound internet both go with it — but every internal service that doesn't depend on WAN reachability keeps running normally. The blast radius is deliberately bounded to "the edge," not "the fleet." ## The hypervisor's own firewall: deliberately unused The virtualization platform underneath everything has its own built-in firewall capability, left with an empty ruleset on purpose. The edge firewall and each host's own local firewall already cover the two layers that matter — a third overlapping layer would add complexity without meaningfully improving posture. Recognizing when *not* to add a redundant control is as much a design decision as adding one. --- Related stacks: [`stacks/platform/caddy/`](../stacks/platform/caddy/), [`stacks/platform/caddy-internal/`](../stacks/platform/caddy-internal/), [`stacks/platform/docker-socket-proxy/`](../stacks/platform/docker-socket-proxy/)