Restrict Netgate 2100 DMZ Egress
A DMZ is useful only when its policy remains narrow after the first service works. This example places a Raspberry Pi 5 at 192.168.60.10 on isolated LAN4 (192.168.60.0/24) behind a Netgate 2100. Nginx listens on loopback and cloudflared publishes the web service through outbound connections. The policy below allows the Pi to obtain what it needs, blocks private-network escape, logs decisions, and avoids brittle lists of Cloudflare CDN addresses.
Define site-specific aliases before writing rules
Use aliases to make intent and review visible. Replace every bracketed inventory item with the actual site value before applying a broad internet rule; an empty or guessed alias is not a security boundary. The inventory must include every trusted and LAN subnet, not just the DMZ:
| Alias | Contents | Purpose |
|---|---|---|
DMZ_PI | 192.168.60.10 | The only Pi that needs this publication policy. |
DMZ_NET | 192.168.60.0/24 | Destination block and audit context. |
TRUSTED_LAN_NETS | Reference trusted LAN 192.168.50.0/24, plus every site trusted, user, IoT, guest, security-lab, and other LAN/VLAN subnet; include each actual CIDR | Prevents DMZ access to any internal segment, not just the administrator LAN. |
PFSENSE_INTERFACE_ADDRS | Every pfSense address: LAN4 192.168.60.1, the configured address for trusted 192.168.50.0/24, each other LAN/VLAN address, management address, loopback/VIP where used, and the actual WAN address | Prevents the Pi from reaching firewall control-plane addresses. |
MGMT_DESTINATIONS | All management hosts, switches, access points, hypervisors, and consoles by site-specific address/subnet | Protects administration services even when they are not on the trusted LAN. |
STORAGE_DESTINATIONS | NAS, storage, and application data addresses/subnets | Prevents a compromised public host from browsing private data. |
BACKUP_DESTINATIONS | Backup server, repository, and backup-management addresses/subnets | Prevents deletion or tampering with recovery data. |
DMZ_DNS | The chosen resolver address or addresses | DNS only; do not silently substitute arbitrary resolvers. |
DMZ_NTP | An approved time service, if one is required | NTP only; use the service’s supported names or addresses. |
RFC1918_PRIVATE | 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 | Safety net for private IPv4 destinations not captured in the site inventory. |
PRIVATE_IPV6 | fc00::/7, fe80::/10, and site-local/link-local destinations in use | IPv6 equivalent where LAN4 has IPv6 enabled; maintain separately from IPv4 aliases. |
Make separate destination groups when the pfSense version requires address-family-specific aliases: DMZ_PRIVATE_DESTINATIONS_V4 from the site-specific IPv4 aliases plus RFC1918_PRIVATE, and DMZ_PRIVATE_DESTINATIONS_V6 from the IPv6 site aliases plus PRIVATE_IPV6. Keep the component aliases in the configuration so an audit can identify whether a hit was to a firewall, management, storage, backup, LAN, or generic private address. Do not build an allowlist of guessed Cloudflare IP addresses. Cloudflare endpoint ranges and service behavior can change, while cloudflared needs outbound connectivity to its documented tunnel endpoints. Permit the documented tunnel transport by protocol and port, and log it. Likewise, package mirrors and application APIs may use changing CDNs; constrain destination ports and run a host-level update policy instead of copying a stale IP list.
Order rules from specific safety boundaries to required traffic
On Firewall > Rules > LAN4, remember that these rules evaluate packets entering LAN4 from DMZ clients; they are not WAN rules and they do not describe traffic arriving from a trusted LAN. Order rules top to bottom and give each a description. A useful pattern is:
- Block and log DMZ to private destinations: source
DMZ_NET, destinationDMZ_PRIVATE_DESTINATIONS_V4and its equivalentDMZ_PRIVATE_DESTINATIONS_V6rule. Each address-family-specific rule contains the trusted/LAN, pfSense-interface, management, storage, backup, and private-range aliases. Keep both rules above every internet allow. - Allow and log Pi to the selected DNS resolver: UDP and TCP 53 to
DMZ_DNS, only if the Pi is not using the Netgate as its resolver. - Allow and log time: UDP 123 to
DMZ_NTPif required. A local Netgate time service can be preferable to broad external NTP. - Allow and log tunnel transport: from
DMZ_PIto the Cloudflare-documented tunnel service on TCP or UDP 7844, including the fallback behavior required by the installed connector version. Do not narrow it to an invented address list. - Allow and log Tailscale outbound transport when it is enabled on the Pi: TCP 443 for control and DERP relay behavior, UDP 3478 for STUN/NAT discovery, and UDP 41641 for direct peer paths. These are stateful outbound flows; do not create an inbound WAN rule or a broad inbound
tailscale0trust rule. - Allow updates deliberately: from
DMZ_PIto the required repository or service on TCP 443 (and TCP 80 only when a real repository requires it). Record the dependency and revisit it after changes. - Allow application dependencies: only named destinations and ports that the application genuinely needs.
- Reject and log the remaining LAN4 traffic: especially new outbound connections from the DMZ that have no documented owner.
Place the private-network block before any broad “DMZ to any on TCP 443” rule. A broad 443 rule below the private block may be acceptable for a connector’s changing public endpoints, but it must not accidentally permit internal destinations. If the firewall’s default deny already blocks the remainder, an explicit logged reject can make the audit result clearer; avoid duplicate rules that obscure counters. Every block rule must have logging enabled and a named counter test; do not accept “the default deny probably caught it” as evidence.
Every allow above is a LAN4 egress pass rule: pfSense sees the packet entering LAN4 from the DMZ client, permits the documented outbound destination and port, and creates state so the legitimate reply can return. The UDP/41641, UDP/3478, TCP/443, and DNS rules are not WAN inbound passes. Do not add an unsolicited WAN rule or port forward for Tailscale, Cloudflare Tunnel, SSH, or the Nginx origin.
Apply IPv4 and IPv6 deliberately
IPv4 CGNAT does not make a DMZ host safe from outbound mistakes. Keep RFC1918, loopback, link-local, router, management, and storage destinations blocked as appropriate. If the ISP delegates IPv6 and LAN4 has global addresses, IPv6 may bypass IPv4 NAT entirely. Either give IPv6 an intentional prefix and matching host/firewall policy, or do not advertise usable IPv6 on the DMZ. Review AAAA records and router advertisements before publication; an unplanned AAAA path can expose the Pi directly.
Do not assume an IPv4 alias automatically protects IPv6. Create address-family-specific rules where pfSense requires them, and test both families from outside and from the DMZ. Keep management of the Netgate 2100 on the trusted management interface only.
Turn on useful egress logging
- Enable logging on the private-network blocks, connector transport, update rule, and final reject. Use descriptions that identify owner, protocol, and reason.
- Send firewall logs to a protected collector only through an explicitly allowed path; otherwise retain enough local history to investigate rule changes and unexpected egress.
- Correlate Netgate rule counters with
cloudflared, Nginx, Tailscale, DNS, and Pi service logs. A successful public request should produce the expected outbound connector evidence, not an inbound WAN pass. - Watch for repeated rejects, DNS requests to unapproved resolvers, new destinations, and traffic from any DMZ address other than
192.168.60.10.
Verify every block, counter, and log
- From the Pi, resolve names with the approved resolver and confirm the tunnel comes up.
- For each member of
TRUSTED_LAN_NETS,PFSENSE_INTERFACE_ADDRS,MGMT_DESTINATIONS,STORAGE_DESTINATIONS, andBACKUP_DESTINATIONS, make an authorized connection attempt from the Pi to a representative address and port. Confirm the matching block rule’s counter increments and its log contains the expected source, destination, interface, and description. - Test representative unused addresses in each RFC1918 range and, when enabled,
PRIVATE_IPV6. Confirm the private safety-net block logs and increments without relying on a broad 443 rule. - Confirm package updates can reach their documented repository only after all private block tests pass. A successful public 443 connection must match the intended outbound rule, never a private destination.
- Stop the connector and confirm it cannot be restarted by an unprivileged service account without the intended service permissions.
- Generate a denied connection that reaches the final reject and find it in the Netgate log with the expected rule description. Check counters for every block and final reject, then repeat after a reboot or ruleset reload.
- After any alias or rule edit, export a configuration backup and repeat the external publication and DMZ isolation tests.
Continue with Logging Updates Backups and Recovery before calling the deployment complete.
Sequence navigation
Previous: Use Tailscale for Private Raspberry Pi Administration · Next: Logging Updates Backups and Recovery
dispelled