rss logo

OpenBSD PF Firewall: Configuration Guide and Examples

OpenBSD Logo

This page collects practical notes and examples for Packet Filter (PF), OpenBSD's firewall, NAT, and traffic filtering system.

PF rules are stored in /etc/pf.conf and managed with pfctl. The examples below are meant as starting points: always test a ruleset before loading it on a remote system.

Information

  • Main configuration file:
/etc/pf.conf
  • Example configuration file:
/etc/examples/pf.conf
  • Official documentation:
man pf
man pf.conf
man pfctl

The pfctl command

  • Check the syntax of the configuration file without loading it:
OpenBSD# pfctl -nf /etc/pf.conf
  • Check the configuration file and display the parsed rules:
OpenBSD# pfctl -nvf /etc/pf.conf
  • Load rules from the configuration file:
OpenBSD# pfctl -f /etc/pf.conf
  • Enable PF:
OpenBSD# pfctl -e
  • Disable PF:
OpenBSD# pfctl -d
  • Flush only the filter rules. Be careful: with no rules loaded, the firewall will no longer enforce your ruleset:
OpenBSD# pfctl -F rules
  • Flush everything, including rules, states, tables, sources, and statistics:
OpenBSD# pfctl -F all
  • Show the currently loaded filter rules:
OpenBSD# pfctl -s rules
  • Show PF status and counters:
OpenBSD# pfctl -s info
  • Show all available PF information:
OpenBSD# pfctl -s all
  • Show the contents of the state table:
OpenBSD# pfctl -s state
  • Kill all state entries originating from a host or a network:
OpenBSD# pfctl -k host
OpenBSD# pfctl -k network

Macros

Macros make rules easier to read and maintain. They are especially useful for interfaces, networks, and lists of ports.

  • Example:
tcp_services = "{ ssh, domain, www, https }"
  • The macro can then be used inside a rule:
pass out proto tcp to any port $tcp_services keep state

Enable PF at boot

  • Edit /etc/rc.conf.local:
pf=YES
pf_rules=/etc/pf.conf

Using /etc/rc.conf.local keeps local changes separate from the default /etc/rc.conf file.

Router mode

💡 Note: PF reads rules from top to bottom, but the last matching rule wins unless a rule uses the quick keyword. When a packet matches a quick rule, PF stops evaluating later rules for that packet.

Enable router mode temporarily

  • Enable IPv4 forwarding:
OpenBSD# sysctl net.inet.ip.forwarding=1
  • Enable IPv6 forwarding:
OpenBSD# sysctl net.inet6.ip6.forwarding=1

Enable router mode permanently

Edit /etc/sysctl.conf:

net.inet.ip.forwarding=1
net.inet6.ip6.forwarding=1

Examples

💡 Note: NAT and redirection rules should be placed before filtering rules in the ruleset.

NAT from LAN to WAN, allow all outbound traffic

# MACROS
ext_if = "re0"
int_if = "re1"
localnet = $int_if:network

# NAT
match out on $ext_if from $localnet to any nat-to $ext_if

# FILTER RULES
set skip on lo
block all
pass from { self, $localnet } to any keep state

NAT from LAN to WAN with basic packet filtering

# MACROS
ext_if = "re0"
int_if = "re1"
localnet = $int_if:network
tcp_services = "{ ssh, domain, www, https, pop3, nntp, cvspserver, 2628, 5999, 8000, 8080 }"
udp_services = "{ domain, ntp }"

# NAT
match out on $ext_if from $localnet to any nat-to $ext_if

# FILTER RULES
set skip on lo
block all
pass in on $int_if inet proto tcp from $localnet to any port $tcp_services keep state
pass in on $int_if inet proto udp from $localnet to any port $udp_services keep state
pass out on $ext_if inet from $localnet to any keep state

NAT, port forwarding, and packet filtering

This example allows HTTP, HTTPS, and DNS from the 192.168.2.0/24 LAN. It also forwards inbound RDP connections to a host inside the LAN.

# MACROS
ext_if = "em0"
lan_if = "em1"
lan_net = "192.168.2.0/24"
rdp_host = "192.168.2.200"
tcp_services = "{ domain, www, https }"
udp_services = "{ domain }"

# OPTIONS
set skip on lo

# DEFAULT POLICY
block return

# NAT
match out on $ext_if inet from $lan_net to any nat-to $ext_if

# REDIRECTION: forward incoming RDP to the internal host
pass in quick on $ext_if inet proto tcp from any to ($ext_if) port 3389 rdr-to $rdp_host port 3389

# FILTER RULES
pass in on $lan_if inet proto tcp from $lan_net to any port $tcp_services modulate state
pass in on $lan_if inet proto udp from $lan_net to any port $udp_services keep state
pass out on $ext_if inet from $lan_net to any keep state
pass out on $lan_if inet proto tcp to $rdp_host port 3389 keep state

# Allow SSH access to the OpenBSD firewall itself
pass in inet proto tcp from any to self port ssh keep state

# By default, do not permit remote connections to X11
block return in on ! lo0 proto tcp to port 6000:6010

SSH redirection example

Redirection rule

  • Redirect incoming SSH traffic to 10.0.0.2:
pass in quick on $ext_if proto tcp from any to ($ext_if) port ssh rdr-to 10.0.0.2 port ssh
  • Allow the forwarded SSH connection:
pass out on $int_if proto tcp to 10.0.0.2 port ssh keep state