Scenario
A network engineer added VLAN 40 for a new contractor workspace on Friday afternoon. The interface, DHCP scope, and firewall rules were all created from the existing VLAN 20 template. On Monday morning the contractors report that “the internet is down”, but the helpdesk finds they can reach the intranet, the print server, and the firewall’s own login page. Nothing was changed on the WAN, and every other network on the same firewall is healthy.
You have console and GUI access. The change that actually caused this was made by someone else, several months ago, and is not in the recent change log.
Architecture (brief)
- One OPNsense firewall, single WAN (
igb0), trunk to the access switch onigb1. - Internal networks: LAN 192.168.10.0/24 (
igb1), VLAN 20 192.168.20.0/24 (vlan20), VLAN 40 10.40.0.0/24 (vlan40, new). - Unbound runs on the firewall and answers for all internal networks.
- Outbound NAT was set to Hybrid mode during an unrelated project so that one accounting server could keep a fixed source port.
Symptoms
- VLAN 40 clients receive a lease, correct gateway, and correct DNS servers.
ping 10.40.0.1(the firewall) succeeds. The GUI loads.nslookup www.example.comreturns an answer.ping 1.1.1.1times out.curl https://example.comhangs then fails.- No ICMP unreachable, no TCP reset - the traffic simply disappears.
- LAN and VLAN 20 are unaffected.
Available evidence
-
Live log, filtered on the VLAN 40 source, shows the flows being permitted:
vlan40 pass 10.40.0.51:41022 1.1.1.1:443 tcp let out anything from firewall host itself vlan40 pass 10.40.0.51 1.1.1.1 icmp echoreq -
The outbound NAT rule set as pf sees it:
root@fw01:~ # pfctl -sn nat on igb0 inet from 192.168.10.0/24 to any -> (igb0) round-robin nat on igb0 inet from 192.168.20.0/24 to any -> (igb0) round-robin -
A capture on the WAN interface while a VLAN 40 host pings:
root@fw01:~ # tcpdump -ni igb0 host 1.1.1.1 09:14:02.118455 IP 10.40.0.51 > 1.1.1.1: ICMP echo request, id 4211, seq 1, length 64 09:14:03.121004 IP 10.40.0.51 > 1.1.1.1: ICMP echo request, id 4211, seq 2, length 64 -
State table entries for the client:
root@fw01:~ # pfctl -ss | grep 10.40.0.51 all icmp 1.1.1.1:8 <- 10.40.0.51:4211 0:0 all tcp 93.184.216.34:443 <- 10.40.0.51:41022 SYN_SENT:CLOSED -
Firewall > NAT > Outbound shows mode Hybrid outbound NAT rule generation with an empty manual rule list above the generated rules.
-
The firewall’s own
ping -c 2 1.1.1.1succeeds with 12 ms round trip.
Student investigation
Work the packet’s path, not the client’s complaint.
- The log says
pass. What is the next stage after the filter decision, and does the log tell you anything at all about it? - Compare the source address in the WAN capture with the source address you would expect a translated packet to carry. What does that single line rule out?
- Enumerate what pf actually loaded. What does
pfctl -snlist, and which internal subnets are missing from it? - Ask why DNS behaved differently from ICMP. Which host actually sent the query that reached the internet, and from which interface?
- Look at the outbound NAT mode. What is the practical difference between Automatic and Hybrid when a new interface is created afterwards?
- Before changing anything, predict the exact line you expect to see in the WAN capture after the fix. If your prediction and the result disagree, your model is wrong, not the firewall.
Validation
Reproduce the failure deliberately so you know you understand it:
- With the fix in place, temporarily disable the new outbound NAT rule and confirm the WAN capture immediately shows the private source again.
- Re-enable it, flush only the affected states, and confirm recovery without touching any other subnet.
- Check that VLAN 20 traffic was never affected during either step, using its own capture rather than an assumption.
Root cause
Reveal the root cause after you have formed a hypothesis
Outbound NAT is in Hybrid mode. Hybrid keeps the automatically generated rules and lets you add manual ones, but the generated set in this configuration only covers the internal subnets that existed when the mode was applied. VLAN 40 was created later, so no rule - manual or generated - matches source 10.40.0.0/24.
pf’s filter stage and pf’s translation stage are separate. The filter said
pass, which is faithfully logged. The translation stage then found no
matching nat rule and left the packet untouched, so it left igb0 with
source 10.40.0.51. The ISP’s first-hop router drops RFC 1918 sources
silently, which is why there is no ICMP error to diagnose from.
DNS looked healthy because the client’s resolver is Unbound on the firewall. The client-to-firewall leg never needs NAT, and the firewall-to-upstream leg is sourced from the WAN address, which needs no translation either. That asymmetry is the tell: name resolution working while IP connectivity fails points at the egress path, not at the client.
Remediation
-
Firewall > NAT > Outbound > Add.
-
Interface
WAN, TCP/IP version IPv4, protocolany. -
Source:
10.40.0.0/24- or, preferred, an aliasRFC1918_Internalcontaining every internal subnet. -
Destination:
any. Translation target: Interface address. -
Description:
Outbound NAT for VLAN 40 - CHG-2291. -
Order it below any static-port or one-to-one rules, above the generated block.
-
Apply, then clear only the broken flows:
root@fw01:~ # pfctl -k 10.40.0.0/24 -
Raise a follow-up ticket to evaluate returning to Automatic mode if the original reason for Hybrid no longer applies.
Verification
# From a VLAN 40 client
$ ping -c 4 1.1.1.1
$ curl -s https://ifconfig.io # must return the WAN public address
# On the firewall
root@fw01:~ # pfctl -sn | grep 10.40
root@fw01:~ # pfctl -vsn # evaluation counter must increment
root@fw01:~ # tcpdump -ni igb0 host 1.1.1.1
root@fw01:~ # pfctl -ss | grep 10.40.0.51
The check that can actually fail is the evaluation counter: a rule that exists but never matches produces exactly the original symptom. Confirm the counter moves under live test traffic, and repeat the client test from a second host so the result is not one machine’s cache.
Prevention
- Add “create outbound NAT rule” to the VLAN provisioning checklist so the translation ships with the subnet.
- Source outbound NAT from an alias of internal subnets; adding a network then touches one object, not several rules.
- Default to Automatic outbound NAT. If Hybrid or Manual is required, record why in the rule description and review it at each rule-hygiene cycle.
- Add a post-change probe that curls an external echo service from every internal VLAN and asserts the expected public address. One test catches missing NAT, wrong NAT, and policy-routing errors together.