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; noos-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:
- Interfaces → Virtual IPs — an IP-Alias for the guest egress range (e.g. a
/29). - Firewall → Aliases —
GUEST_POOL= that range. - 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) ridebr-guesttagged.
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>):
- Interfaces → Devices → VLAN — VLAN
2000+Von parentbr-guest. - Interfaces → Assignments — assign it, name
GUEST_<tri>, IPv4 static the.1of the venue's/22. Each site gets a/22(~1000 guests) from CGNAT100.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(gw100.64.4.1); V=2 →100.64.8.0/22; V=66 →100.65.8.0/22. - 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. - 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:
- Loyalty member logs in with their account → 2 h session.
- 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_.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.