Skip to content

Firewall — guest edge (fw-guest-1): setup runbook

Single-node OPNsense for guest Wi-Fi across every venue. Guest VLAN 20 at each venue is bridged over the guest VPLS to the colo; this box terminates it, gives each venue a gateway / DHCP / captive-portal, and NATs guest to a distinct public egress (reputation isolation from the corporate edge). Not HA — one node (guest is best-effort). The corporate edge is the separate HA pair in firewall-opnsense / its setup runbook.

Single node — none of the HA machinery

No CARP VIPs, no pfSync, no config-sync. Egress addresses are plain interface IPs / IP-Aliases; there's no master/backup to elect.

1. Base system

  • Install OPNsense; set the OOB mgmt IP from the console.
  • System → Settings → General — hostname fw-guest-1, DNS = the filtering DNS servers, NTP, timezone.
  • Administration — GUI/SSH locked to OOB, HTTPS, 2FA.
  • System → Firmware → Plugins — os-crowdsec (guest is the #1 CrowdSec candidate), os-lldpd. Captive Portal is in the base; no os-frr (WAN is a static default route).

2. WAN + default route + NAT

WAN — assign the guest uplink; set a static gateway + default route (or DHCP/PPPoE from the upstream). The guest egress must be a distinct public range from the corporate /28 — guest abuse must never taint corporate IP reputation.

Do you need a source pool here? — yes, if you can

This box aggregates every venue's guest Wi-Fi, so a single NAT IP risks port exhaustion (64 k ports per destination) and pins all guest reputation on one address. If you have a small guest range, pool it:

  1. Interfaces → Virtual IPs — an IP-Alias for the guest egress range (e.g. a /29).
  2. Firewall → Aliases — GUEST_POOL = that range.
  3. Firewall → NAT → Outbound = Hybrid → rule: source = the guest subnets, translation = GUEST_POOL, Pool Options = Round Robin.

If you only have one guest public IP, NAT to it and just watch state counts at scale. (No CARP here — it's a single node — so these are plain IP-Aliases, not CARP VIPs.)

3. Core — the guest VPLS bridge

The colo CCRs hand guest off as a per-venue VLAN (2000 + venue_id) on a guest trunk, from both CR-COLO-1 and CR-COLO-2 for redundancy. Bridge the two uplinks so either path works:

  • Interfaces → Assignments — assign the two guest uplinks: GUEST_A (→ CR-COLO-1), GUEST_B (→ CR-COLO-2). MTU 1500 (guest frames arrive decapsulated — plain 802.1Q, not MPLS).
  • Interfaces → Devices → Bridge — create br-guest = GUEST_A + GUEST_B; enable RSTP.
  • The per-venue guest VLANs (2000 + venue_id) ride br-guest tagged.

One active path per venue — VPLS DF, not STP

The guest VPLS designated-forwarder election already blocks the standby attachment, so only one CCR forwards a given venue at a time (loop-free by design). br-guest's RSTP is only a safety net against a mis-cable — don't rely on it for the L2 topology.

4. Per-venue guest VLAN — repeat for each venue

For venue V (venue_id = V, VLAN 2000 + V, trigram <tri>):

  1. Interfaces → Devices → VLAN — VLAN 2000+V on parent br-guest.
  2. Interfaces → Assignments — assign it, name GUEST_<tri>, IPv4 static the .1 of the venue's /22. Each site gets a /22 (~1000 guests) from CGNAT 100.64.0.0/10: venue V → 100.64.0.0 + V × 1024 = 100.[64 + V÷64].[(4V) mod 256].0/22. E.g. V=1 → 100.64.4.0/22 (gw 100.64.4.1); V=2 → 100.64.8.0/22; V=66 → 100.65.8.0/22.
  3. Services → DHCPv4 → [GUEST_] — range across the /22 (V=1: 100.64.4.10 – 100.64.7.254), gateway the .1, DNS = the filtering DNS servers, short lease.
  4. Services → Captive Portal — loyalty login (2-tier). Add the interface to the guest zone (one shared zone, or per-venue zones for per-site branding). Access is tiered by the loyalty scheme:
  5. Loyalty member logs in with their account → 2 h session.
  6. Non-member ("just browse" / no account) → 15 min session.

Native implementation = RADIUS auth + a zone default timeout: - Zone → Authentication = RADIUS → the loyalty back-end (or a FreeRADIUS broker that checks membership). A successful member auth returns Session-Timeout = 7200 → the 2 h session. - Zone → Hard timeout = 900 s (15 min) → the default for the click-through (non-member) path. Set an idle timeout + per-client bandwidth caps too.

!!! tip "If the loyalty scheme is web/OAuth, not RADIUS" OPNsense CP is form/RADIUS/voucher-based, not OAuth. For a web/API login use a custom/external portal template that calls the loyalty API then authorises the session via the CP backend — more work. A small RADIUS shim in front of the loyalty DB is usually simpler and gives the per-tier Session-Timeout for free.

!!! note "Cap endless 15-min renewals" A non-member can re-accept every 15 min — to limit that (e.g. one free window per device per day) add a per-MAC daily quota / "seen" list in the template. 5. Firewall → Rules → [GUEST_] — guest isolation, in order: - Allow guest → the FW's own .1 on DNS (to the filter) + captive-portal ports. - Block guest → RFC1918 + the venue/corporate supernets (no lateral to internal). - Allow guest → internet (any, !RFC1918). 6. NAT — the §2 outbound rule already covers guest; make sure its source includes the guest supernet (100.64.0.0/16 or the per-venue nets).

Why CGNAT 100.64.0.0/10, not 172.16

Guest uses CGNAT 100.64.0.0/10 (RFC 6598) — non-conflicting with the estate's 10/8 (venues + colo), the 172.16/12 infra/OOB, and guests' own home devices (172.16/12 also clashes with Docker 172.17 and some VPNs). Each site is a /22 (~1000 guests) — venue V = the V-th /22 of the /10. Overall map: 10/8 internal · 172.16/12 infra/OOB · 100.64/10 guest.

The only per-venue variables are V, the VLAN 2000+V, the venue's /22 (100.64.0.0 + V×1024), and the interface name GUEST_<tri> — everything else is identical, so it's a quick copy per site.

5. CrowdSec (deploy it here above all)

Guest Wi-Fi is the most-abused segment, so this is the best CrowdSec home: install os-crowdsec, enable the agent + firewall bouncer, add the OPNsense collection, and enrol in the free Console for the Community Blocklist. Steps mirror the corporate edge §11b.

6. Verify

# per venue
Interfaces → GUEST_<tri>               -> up, the venue /22 gateway (e.g. 100.64.4.1/22)
# a guest client on that VLAN:
  - gets a lease in the venue /22, DNS = the filtering servers
  - hits the loyalty portal: member -> 2h (RADIUS Session-Timeout 7200), non-member -> 15m
  - reaches the internet; CANNOT reach RFC1918 / venue / corporate (blocked)
Services → Captive Portal → Sessions   -> timeouts 7200s (member) / 900s (non-member)
# core
Interfaces → Devices → br-guest        -> GUEST_A + GUEST_B members, RSTP one forwarding/venue
Firewall → NAT → Outbound              -> guest sources -> GUEST_POOL (Round Robin) or single IP
Services → CrowdSec → Decisions        -> bouncer active, community blocklist populated

What's deliberately NOT here (vs the corporate edge)

  • No BGP — WAN is a static default route; guest prefixes are NAT'd, not advertised.
  • No CARP / pfSync / HA — single node.
  • No server VLANs / MLAG trunk — this box only sees guest VPLS + WAN + OOB.