The Handbook

    Theme
    • Guides
        • Check a system before installing FreeBSD
        • Orient a Linux administrator on FreeBSD
        • Supported FreeBSD releases
        • Choose a FreeBSD documentation and support channel
        • Move files safely without GNU mv -t
        • Update, upgrade, or update packages?
        • Upgrade 14.4 or 15.0 to FreeBSD 15.1
        • Choose packages, ports, or poudriere
        • Choose the quarterly or latest package branch
        • Choose a FreeBSD download or package mirror
        • Make a system setting persistent
        • Configure locale, keyboard, and time zone
        • Configure a serial console for recovery
        • Choose a custom kernel, module, or loader setting
        • Run a Linux binary with the compatibility layer
        • Check desktop and laptop hardware before installation
        • Choose and check a graphics driver
        • Choose Xorg or Wayland
        • Build a maintainable desktop baseline
        • Install and check a desktop browser
        • Check laptop Wi-Fi, power, and suspend
        • Check multimedia readiness
        • Choose and configure a printing stack
        • Run Windows applications with Wine
        • Snapshot and replicate a ZFS dataset
        • Choose a filesystem and storage layout
        • Operate ZFS without losing the recovery path
        • Restore files from a ZFS snapshot
        • Read ZFS pool health and run a scrub
        • Replace a failed device in a ZFS mirror
        • Replicate a ZFS dataset over SSH
        • Change PF safely on a remote host
        • Configure a narrow WireGuard tunnel
        • Plan a FreeBSD home server
        • Choose a mail server or an outgoing relay
        • Publish a network service safely
        • Choose a jail network model
        • Choose native jails or a jail manager
        • Choose a jail or a bhyve virtual machine
        • Establish a FreeBSD security baseline
        • Choose a MAC policy
        • Audit security-relevant activity
        • Start a DTrace performance investigation
    • Integrations
        • Create a first jail with Bastille
        • Compare jail managers and OCI tooling
        • Publish a Bastille service through PF
        • Mount a ZFS dataset in a Bastille jail
        • Update and upgrade Bastille jails
        • Back up and restore a Bastille jail
        • Prepare bhyve and vm-bhyve
        • Choose NFS or Samba for file sharing
        • Choose ZFS backup automation
        • Operate a signed poudriere repository
        • Manage FreeBSD configuration with Ansible or Salt
        • Run Motion with webcamd on FreeBSD
        • Design a reverse proxy, certificates, and monitoring
    • FAQ
      • Troubleshooting
          • Recover an interrupted freebsd-update run
          • Resolve a package repository or ABI mismatch
          • Diagnose the FreeBSD boot path
          • Recover with a ZFS boot environment
          • Diagnose DNS, routing, and firewall paths
          • Diagnose network mbuf exhaustion
          • Bind a service to a low port without running it as root
          • Diagnose audio output or input
          • Diagnose webcamd, cuse, and a webcam
          • Fix USB device permissions without opening every device
      • About this handbook
      • Synopsis
      • Understand the path being changed
      • Establish recovery first
      • Capture current evidence
      • Validate before loading
      • Observe the actual match
      • Roll back on uncertainty
      • Primary references

      Change PF safely on a remote host

      Last reviewed
      13 August 2026
      Applies to
      15.1-RELEASE, 15.0-RELEASE, 14.4-RELEASE

      Synopsis #

      PF evaluates translation, filtering, state, interfaces, and anchors as one packet path. A syntactically valid rule can still block administrative access or publish a service on the wrong address. Remote firewall work therefore needs a known recovery channel, a preserved ruleset, an independent session, and observable acceptance criteria.

      This guide supplies a change method, not a universal ruleset. Existing hosts may use Bastille anchors, dynamic tables, VPN interfaces, failover addresses, or provider-specific console access that a generic file cannot preserve.

      Understand the path being changed #

      Write the intended flow before editing:

      source -> receiving interface and address -> translation -> filter -> local socket or forwarded destination -> return route
      

      PF is stateful. Existing SSH traffic can remain in a state even when a new connection would be blocked. A surviving administrative session therefore does not prove that the new rules admit replacement sessions.

      Anchors are part of the active policy. A base ruleset that omits rdr/* or bastille/* can disable dynamic Bastille mappings even when the top-level filter looks correct. Review Publish a Bastille service through PF when jails are involved.

      Establish recovery first #

      Before editing a remote host:

      1. Confirm a working console, out-of-band terminal, or another authorized administrator.
      2. Keep two independent SSH sessions open.
      3. Preserve the exact active configuration and any included files or anchor loaders.
      4. Record the interface, source network, destination address, and management port.
      5. Define the command that restores the previous file and reloads it.

      Do not perform the first PF change on a host that has no tested recovery path.

      Capture current evidence #

      Record configuration and runtime state:

      # cp -p /etc/pf.conf /etc/pf.conf.prechange
      # pfctl -sr
      # pfctl -sn
      # pfctl -sa
      $ netstat -rn
      # sockstat -4 -6 -l
      

      pfctl -sa can be large and may expose internal addressing. Store it with the change record rather than posting it without review.

      Validate before loading #

      Check the candidate file without changing the active rules:

      # pfctl -nf /etc/pf.conf
      

      Review the expansion and ordering of the loaded rules where macros, tables, and anchors make the text ambiguous. Syntax validation confirms only that PF can parse the file. It does not confirm DNS, interface names, route symmetry, or remote access.

      Load the candidate from the first session while leaving the second session idle:

      # pfctl -f /etc/pf.conf
      

      Immediately open a third, new administrative connection from the expected source. Exercise privilege escalation and keep it open. Then test the changed service from the intended client network.

      Observe the actual match #

      Use states and counters instead of guessing:

      # pfctl -ss
      # pfctl -vvsr
      # tcpdump -ni em0 host 192.0.2.10
      

      Replace the example interface and address. Packet capture should be as narrow as possible and stopped after the test. No packet on the receiving interface is an upstream routing or address problem, not a PF rule problem. Packets without an expected state point toward rule matching; states without replies point toward the listener or return path.

      Roll back on uncertainty #

      If a new administrative session or acceptance test fails, restore the preserved file from the still-working session or console:

      # cp -p /etc/pf.conf.prechange /etc/pf.conf
      # pfctl -nf /etc/pf.conf
      # pfctl -f /etc/pf.conf
      

      Do not keep adding broad pass rules to a failed change. Restore the known state, collect evidence, and revise one layer at a time with Diagnose DNS, routing, and firewall paths .

      Primary references #

      • FreeBSD Handbook: Firewalls
      • pf.conf(5)
      • pfctl(8)
      • tcpdump(1)
      • sockstat(1)

      Independent documentation. Not affiliated with or endorsed by the FreeBSD Project or the FreeBSD Foundation.

      Report a bug
      • Synopsis
      • Understand the path being changed
      • Establish recovery first
      • Capture current evidence
      • Validate before loading
      • Observe the actual match
      • Roll back on uncertainty
      • Primary references