Synopsis #
“The network is down” commonly combines several independent systems: interface state, address assignment, route selection, DNS, PF, NAT, a listening socket, and the application’s own protocol. Changing all of them at once destroys the evidence needed to identify the failed layer.
Start at the failing process, record one destination address and port, and follow that path outward. Use numeric addresses before DNS and a direct socket test before application-specific configuration.
Record the exact failing path #
Write down:
- source host or jail and source network;
- destination name, resolved address, protocol, and port;
- expected receiving interface;
- whether PF translates the source or destination;
- the expected return route.
Capture the error and time. A refusal, timeout, DNS error, and TLS error identify different stages.
Prove local configuration #
On the failing FreeBSD system:
$ ifconfig -a
$ netstat -rn
$ route -n get 198.51.100.20
$ cat /etc/resolv.conf
Replace the documentation address with the actual destination. route -n get shows the selected gateway and interface. An unexpected interface or VPN route must be corrected before firewall rules can make the path reliable.
Inside a Bastille jail, run the same commands through the manager:
# bastille cmd web ifconfig -a
# bastille cmd web netstat -rn
# bastille cmd web cat /etc/resolv.conf
For VNET, the jail has its own routes. For a classic jail, the visible network state reflects the shared host stack and its permitted addresses.
Separate DNS from IP reachability #
Query the configured resolver and then a specified resolver:
$ drill service.example.net A
$ drill @192.0.2.53 service.example.net A
$ getent hosts service.example.net
The specified resolver address is an example. A direct query that works while the default query fails points to resolver selection or /etc/resolv.conf. A correct answer does not prove that the returned address is reachable or that the application uses the same resolver path.
Test the numeric service endpoint:
$ nc -vz 198.51.100.20 443
$ fetch -o /dev/null https://198.51.100.20/
A TLS certificate-name error proves that the TCP and TLS endpoint answered; it is different from a timeout.
Prove the listener at the destination #
On the destination host or jail:
# sockstat -4 -6 -l
# service nginx status
Confirm the exact local address and port. A service bound only to 127.0.0.1 cannot accept traffic sent to a jail, LAN, or public address. A service bound to all addresses may be broader than intended.
Observe PF and packets #
On the filtering host, inspect rules, translation, and states:
# pfctl -sr
# pfctl -sn
# pfctl -ss
# pfctl -vvsr
Capture only the relevant interface, host, and port:
# tcpdump -ni em0 host 198.51.100.20 and port 443
Interpret the evidence in order:
| Evidence | Likely boundary |
|---|---|
| No packet arrives | Upstream route, address, client policy, or wrong interface |
| Packet arrives but no PF state | Filter or translation match |
| State exists and destination receives nothing | Redirection, forwarding, or destination route |
| Destination receives SYN but sends no reply | Listener or local destination policy |
| Reply leaves but client never receives it | Return route, NAT, or upstream filtering |
| TCP succeeds but application fails | TLS, authentication, virtual host, or application protocol |
Do not disable PF as a diagnostic shortcut on a remote or exposed host. Use Change PF safely on a remote host and make one reviewable change.
Apply the model to common integrations #
For private Bastille jails, check the jail listener, the private host-to-jail path, the Bastille PF table, and only then the rdr mapping in Publish a Bastille service through PF
.
For WireGuard, add endpoint DNS, public UDP reachability, wg show handshake time, AllowedIPs, and the post-tunnel route to the same sequence. See Configure a narrow WireGuard tunnel
.
For bhyve, identify the tap interface, bridge membership, guest route, and whether filtering occurs on the physical member, bridge, or tap. See Prepare bhyve and vm-bhyve .