FreeBSD.software
Home/Guides/PF vs IPFW: FreeBSD Firewall Comparison
comparison·2026-03-29·17 min read

PF vs IPFW: FreeBSD Firewall Comparison

Head-to-head comparison of PF and IPFW firewalls on FreeBSD. Covers syntax, features, NAT, QoS, logging, performance, and when to use each.

PF vs IPFW: FreeBSD Firewall Comparison

FreeBSD ships with two production-grade firewalls in the base system: PF (Packet Filter) and IPFW (IP Firewall). Both are mature, well-documented, and capable of protecting anything from a single VPS to a high-throughput network edge. Choosing between them is one of the first decisions a FreeBSD administrator makes -- and it is not always obvious which one to pick.

This guide puts PF and IPFW side by side. We compare syntax, stateful filtering, NAT, QoS, logging, performance, and management tools, with real configuration examples throughout. By the end you will know exactly which firewall fits your use case.

Quick Verdict

PF is the right choice for most FreeBSD users. Its syntax is readable, its stateful tracking is on by default, and NAT configuration is straightforward. If you are setting up a PF firewall for a web server, VPN gateway, or small office router, PF will get you there faster with fewer surprises.

IPFW wins when you need advanced traffic shaping. The dummynet pipe system gives you fine-grained QoS controls -- bandwidth limits, delay injection, packet loss simulation -- that PF's ALTQ cannot match in flexibility. IPFW also appeals to administrators who prefer explicit rule-number ordering and a command-line-driven workflow where rules can be inserted, deleted, and reordered on the fly without reloading an entire ruleset.

If you are still unsure, start with PF. You can always migrate later.

History

PF: The OpenBSD Import

PF originated in OpenBSD 3.0 (2001) as a replacement for the IPFilter firewall after a licensing dispute. It was designed from the ground up with security, simplicity, and correctness as priorities. FreeBSD imported PF shortly after, and it has been available in the base system since FreeBSD 5.3.

The FreeBSD port of PF has diverged somewhat from the OpenBSD version. FreeBSD's PF uses its own SMP-friendly architecture and supports features like route-to for policy-based routing. Configuration syntax remains close to OpenBSD's, so most PF tutorials and examples translate with minimal changes.

IPFW: The Native Firewall

IPFW is native to FreeBSD. It has been part of the kernel since the early 4.4BSD-derived releases in the 1990s. Over the decades it has grown from a simple packet filter into a full-featured firewall with in-kernel NAT, the dummynet traffic shaper, and support for dynamic rules and lookup tables.

Because IPFW is FreeBSD-native, it tends to receive kernel-level optimizations and new features faster than PF. It is also the firewall used internally by FreeBSD jails networking and by some embedded FreeBSD distributions.

Syntax Comparison

Syntax is where PF and IPFW diverge most visibly. PF uses a declarative configuration file. IPFW uses imperative, numbered commands. Below are side-by-side examples for common rules.

Allow Inbound SSH

PF (/etc/pf.conf):

shell
pass in on egress proto tcp from any to (egress) port 22

IPFW (command line or /etc/rc.firewall):

shell
ipfw add 100 allow tcp from any to me 22 in via em0

PF uses pass / block keywords and interface macros like egress. IPFW uses allow / deny with explicit rule numbers. The me keyword in IPFW matches any address assigned to the host.

Block All Traffic by Default

PF:

shell
block all

IPFW:

shell
ipfw add 65000 deny all from any to any

PF applies a default-deny policy with two words. IPFW achieves the same by adding a deny rule at a high rule number (the default rule 65535 can also be set to deny via net.inet.ip.fw.default_to_accept=0).

NAT (Outbound Masquerading)

PF:

shell
nat on egress from 10.0.0.0/24 to any -> (egress)

IPFW (in-kernel NAT):

shell
ipfw nat 1 config if em0 reset ipfw add 50 nat 1 ip from 10.0.0.0/24 to any out via em0 ipfw add 60 nat 1 ip from any to any in via em0

PF handles NAT in a single line. IPFW requires configuring a NAT instance and then directing traffic through it with separate rules. Both produce the same result.

Port Forward (DNAT)

PF:

shell
rdr on egress proto tcp from any to (egress) port 8080 -> 10.0.0.5 port 80

IPFW:

shell
ipfw nat 1 config if em0 redirect_port tcp 10.0.0.5:80 8080

PF uses the rdr keyword. IPFW configures port redirects as part of a NAT instance. Both are effective; PF's syntax reads more naturally.

Rate Limiting

PF (using max-src-conn-rate):

shell
pass in on egress proto tcp to port 80 keep state \ (max-src-conn-rate 15/5, overload <bruteforce> flush global)

IPFW (using dummynet):

shell
ipfw pipe 1 config bw 1Mbit/s ipfw add 100 pipe 1 tcp from any to me 80 in

PF limits connection rates per source and can add offenders to a table for blocking. IPFW's dummynet limits raw bandwidth, which is a different kind of rate limiting. Each approach has its strengths depending on the threat model.

Stateful Filtering

Both firewalls support stateful packet inspection, but they handle it differently.

PF is stateful by default. Every pass rule automatically creates a state entry that tracks the connection. Return traffic is permitted without an explicit rule. You can override this with no state, but you almost never need to.

IPFW requires the keep-state keyword to enable stateful tracking on a per-rule basis. Without it, you must write separate rules for return traffic. Adding check-state early in the ruleset tells IPFW to match against existing state entries before processing further rules.

shell
# IPFW stateful SSH example ipfw add 10 check-state ipfw add 100 allow tcp from any to me 22 in setup keep-state

PF's approach is simpler and less error-prone. Forgetting keep-state in an IPFW ruleset is a common source of connectivity problems for newcomers.

NAT

PF NAT

PF integrates NAT directly into the ruleset using nat, rdr, and binat rules. NAT rules are evaluated before filter rules. The syntax is compact, and round-robin or sticky-address options are available for load balancing.

shell
nat on em0 from <internal> to any -> (em0) round-robin

IPFW NAT

IPFW offers two NAT mechanisms. The legacy approach uses natd, a userspace daemon that handles NAT via the divert socket. The modern approach uses in-kernel NAT (ipfw nat), which is faster and avoids the overhead of copying packets to userspace.

In-kernel NAT is the recommended option for all new deployments. It supports redirect rules, address pools, and connection tracking. However, the configuration is more verbose than PF's.

For a deeper dive into firewall NAT on FreeBSD, see our best firewall guide.

QoS and Traffic Shaping

This is where IPFW has a clear advantage.

IPFW Dummynet

Dummynet is a powerful traffic shaping system built into IPFW. It lets you create pipes and queues with configurable bandwidth, delay, and packet loss. Use cases include:

  • Bandwidth throttling per service or per client
  • WAN emulation for testing (adding latency and jitter)
  • Fair queuing across multiple users on a shared connection
  • Priority-based scheduling
shell
# Limit HTTP to 10 Mbit/s with 20ms delay ipfw pipe 1 config bw 10Mbit/s delay 20 # Apply to HTTP traffic ipfw add 200 pipe 1 tcp from any to me 80 in

Dummynet supports weighted fair queuing (WF2Q+), multiple queues within a single pipe, and dynamic pipes that create per-flow instances automatically. For network engineers and anyone doing traffic engineering, this is the most capable tool available in the FreeBSD base system.

PF ALTQ

PF's traffic shaping relies on ALTQ (Alternate Queuing), which supports schedulers like CBQ, HFSC, and PRIQ. ALTQ is functional but has significant limitations on FreeBSD:

  • ALTQ does not work on all network drivers
  • It is single-threaded, which can become a bottleneck on multi-core systems
  • Configuration is more complex than dummynet for common scenarios
  • Some ALTQ schedulers have seen limited maintenance
shell
# PF ALTQ example (HFSC scheduler) altq on em0 hfsc bandwidth 100Mb queue { std, http, ssh } queue std bandwidth 40% default queue http bandwidth 50% queue ssh bandwidth 10% priority 7 pass out on em0 proto tcp to port 80 queue http pass out on em0 proto tcp to port 22 queue ssh

If QoS is your primary concern, IPFW with dummynet is the stronger choice. If you only need basic prioritization, PF's ALTQ can work but expect some rough edges.

Tables and Dynamic Rules

PF Tables

PF tables are in-memory address sets optimized for fast lookups. They are ideal for managing large blocklists, whitelists, or overload tables:

shell
table <bruteforce> persist block in from <bruteforce> # Add addresses dynamically pfctl -t bruteforce -T add 192.168.1.50 pfctl -t bruteforce -T show

Tables can be loaded from files (table persist file "/etc/pf.blocklist") and updated at runtime without reloading the ruleset.

IPFW Tables and Dynamic Rules

IPFW also supports lookup tables (called table objects) for storing addresses, networks, and even interface names:

shell
ipfw table 1 add 192.168.1.50 ipfw add 100 deny all from table(1) to any

IPFW's dynamic rules are state entries created by keep-state and limit keywords. The limit keyword can restrict the number of connections per source, which functions similarly to PF's max-src-conn:

shell
ipfw add 100 allow tcp from any to me 22 in setup limit src-addr 3

Both firewalls handle large address sets efficiently. PF's table syntax is slightly more intuitive, but IPFW tables are equally capable.

Logging

PF Logging (pflog)

PF logs packets to a virtual interface called pflog0. The logs are in pcap format, which means you can inspect them with tcpdump:

shell
# Real-time monitoring tcpdump -n -e -ttt -i pflog0 # Read saved log file tcpdump -n -e -ttt -r /var/log/pflog

The pcap format is a significant advantage. You can filter, replay, and analyze firewall logs with standard network tools. Rules that should log packets use the log keyword:

shell
block log all pass log (all) in on egress proto tcp to port 22

IPFW Logging (syslog)

IPFW logs to syslog by default. Adding the log keyword to a rule sends matching packets to the system log:

shell
ipfw add 100 deny log all from any to any

Log entries appear in /var/log/security and can be filtered and rotated with standard syslog tools. IPFW also supports log logamount N to limit the number of logged packets per rule, preventing log floods.

The syslog approach integrates well with centralized logging systems (syslog-ng, rsyslog, ELK), while PF's pcap approach is better for packet-level forensics. Choose based on your monitoring infrastructure.

Performance

Both PF and IPFW are fast enough for the vast majority of workloads. Performance differences are measurable but rarely the deciding factor.

PF on FreeBSD has received SMP improvements over the years. Rulesets are evaluated efficiently, and state table lookups are O(1) with hash tables. However, ALTQ's single-threaded nature can limit throughput on multi-core systems when traffic shaping is enabled.

IPFW benefits from being FreeBSD-native. It uses a linear rule scan by default (rules are evaluated in order until a match), but lookup tables provide O(1) matching for address-based rules. Dummynet is SMP-aware and handles traffic shaping without the single-thread bottleneck.

For raw packet filtering without QoS, both firewalls can saturate 10 Gbps links on modern hardware. At very high packet rates (millions of packets per second), IPFW's linear rule scan can become a factor if the ruleset is large. Using skipto rules and tables mitigates this.

In practice, the choice between PF and IPFW should be driven by features and workflow, not performance. Optimize your ruleset structure rather than switching firewalls for speed.

Anchors (PF) vs Sets (IPFW)

PF Anchors

PF anchors are sub-rulesets that can be loaded, modified, and flushed independently of the main ruleset. They are useful for modular firewall configurations:

shell
# Main ruleset loads an anchor anchor "jails/*" # Load rules into an anchor pfctl -a jails/web -f /etc/pf.jails.web.conf # Flush a specific anchor without touching main rules pfctl -a jails/web -F rules

Anchors are widely used for managing per-jail or per-VM firewall rules on FreeBSD hosts. They enable dynamic rule management without the risk of disrupting the main policy.

IPFW Sets

IPFW organizes rules into 32 sets (numbered 0-31). Sets can be enabled, disabled, and swapped atomically:

shell
# Add a rule to set 5 ipfw set 5 add 300 allow tcp from any to me 443 # Disable set 5 (rules stop matching) ipfw set disable 5 # Swap sets 5 and 6 atomically ipfw set swap 5 6

Set swapping is IPFW's mechanism for atomic ruleset updates. You load a new ruleset into a disabled set, then swap it with the active set. This avoids any window where the firewall is in an intermediate state.

Both approaches solve the same problem -- modular, dynamic rule management -- but PF anchors are more flexible for hierarchical configurations, while IPFW sets are simpler for atomic ruleset swaps.

Management Tools

pfctl (PF)

pfctl is the primary management tool for PF. Common operations:

shell
pfctl -e # Enable PF pfctl -d # Disable PF pfctl -f /etc/pf.conf # Load ruleset pfctl -sr # Show rules pfctl -ss # Show state table pfctl -si # Show statistics pfctl -t blocklist -T show # Show table contents pfctl -k host 192.168.1.50 # Kill states for a host

PF is configured through a single file (/etc/pf.conf), which is parsed, optimized, and loaded atomically. This means you edit the file, run pfctl -f /etc/pf.conf, and the entire ruleset is replaced in one operation.

ipfw (IPFW)

The ipfw command manipulates rules directly:

shell
ipfw list # Show all rules ipfw show # Show rules with counters ipfw add 100 allow ... # Add a rule ipfw delete 100 # Delete a rule ipfw zero # Reset counters ipfw flush # Delete all rules (dangerous) ipfw table 1 list # Show table contents

IPFW rules can be added and removed individually without affecting the rest of the ruleset. This is convenient for quick changes but makes it harder to maintain a consistent, version-controlled configuration. Most administrators write their IPFW rules in a script that is loaded at boot via rc.conf.

For a comprehensive FreeBSD hardening workflow that includes firewall setup, see our dedicated guide.

Running Both Simultaneously

It is technically possible to run PF and IPFW at the same time on FreeBSD. Both can be loaded as kernel modules and both can process packets. However, this is not recommended.

When both are active, packets pass through both firewalls in sequence. The order depends on which modules are loaded first and how sysctl variables are configured. This creates a confusing situation where a packet allowed by PF might be blocked by IPFW, and troubleshooting requires checking two rulesets instead of one.

There are rare cases where running both makes sense -- for example, using IPFW solely for dummynet traffic shaping while PF handles filtering and NAT. If you go this route, keep one firewall's ruleset minimal and document the interaction clearly.

For most deployments, pick one firewall and use it exclusively.

Comparison Table

| Feature | PF | IPFW |

|---|---|---|

| Origin | OpenBSD (ported to FreeBSD) | Native to FreeBSD |

| Config style | Declarative file (pf.conf) | Imperative commands / script |

| Stateful by default | Yes | No (requires keep-state) |

| NAT | Built-in (nat, rdr, binat) | In-kernel NAT or natd |

| Traffic shaping | ALTQ (limited SMP) | Dummynet (SMP-aware) |

| Logging format | pcap (pflog0) | syslog |

| Address tables | Tables (file or dynamic) | Tables (numbered) |

| Modular rules | Anchors (hierarchical) | Sets (numbered 0-31) |

| Atomic reload | Yes (pfctl -f) | Set swap (ipfw set swap) |

| Rule ordering | Last match wins (default) | First match wins |

| Management tool | pfctl | ipfw |

| IPv6 support | Full | Full |

| SMP scaling | Good (no ALTQ) | Good |

| Learning curve | Moderate | Moderate-High |

| Best for | General-purpose firewalling | QoS, traffic shaping, testing |

Migration Guide: IPFW to PF

If you are moving from IPFW to PF, here is a translation pattern for common rules.

Step 1: Default Policy

IPFW:

shell
ipfw add 65000 deny all from any to any

PF:

shell
block all

Step 2: Loopback

IPFW:

shell
ipfw add 10 allow all from any to any via lo0

PF:

shell
set skip on lo0

Step 3: Stateful Rules

IPFW:

shell
ipfw add 20 check-state ipfw add 100 allow tcp from any to me 22 in setup keep-state ipfw add 200 allow tcp from any to me 80,443 in setup keep-state

PF:

shell
pass in on egress proto tcp to port { 22, 80, 443 }

PF is stateful by default, so there is no need for check-state or keep-state equivalents.

Step 4: NAT

IPFW:

shell
ipfw nat 1 config if em0 reset ipfw add 50 nat 1 ip from 10.0.0.0/24 to any out via em0 ipfw add 60 nat 1 ip from any to any in via em0

PF:

shell
nat on em0 from 10.0.0.0/24 to any -> (em0)

Step 5: Rate Limiting

IPFW:

shell
ipfw add 100 allow tcp from any to me 22 in setup limit src-addr 5

PF:

shell
pass in on egress proto tcp to port 22 keep state \ (max-src-conn 5, overload <blocked> flush global) block in from <blocked>

Step 6: Enable PF

Add to /etc/rc.conf:

shell
pf_enable="YES" pflog_enable="YES"

Remove IPFW lines:

shell
# Remove or comment out: # firewall_enable="YES" # firewall_type="..."

Reload and test:

shell
pfctl -nf /etc/pf.conf # Syntax check (dry run) pfctl -f /etc/pf.conf # Load ruleset pfctl -sr # Verify rules

Always test over a console session or with a cron job that disables PF after a few minutes, so you do not lock yourself out of a remote machine.

Frequently Asked Questions

Which firewall should a beginner use?

PF. Its syntax is more readable, stateful filtering is on by default, and there are more beginner-friendly tutorials available. The PF firewall guide on this site is a good starting point.

Can I use PF inside a FreeBSD jail?

Not directly. PF operates at the host level. You manage per-jail rules using PF anchors on the host. IPFW has similar limitations, though there is some support for IPFW within VNET jails.

Is one firewall more secure than the other?

Both are mature and well-audited. PF benefits from OpenBSD's security-focused development culture. IPFW benefits from decades of FreeBSD kernel development. Neither has a history of significant security vulnerabilities. Choose based on features, not perceived security differences.

Does pfSense use PF or IPFW?

pfSense uses PF as its firewall engine. OPNsense also uses PF. This means PF has a large deployed base and benefits from ongoing testing in these widely used firewall distributions.

How do I check which firewall is running?

shell
# Check PF status pfctl -si # Check IPFW status ipfw list

If PF is not loaded, pfctl will return an error about the /dev/pf device. If IPFW is not loaded, the ipfw command will fail with a sysctl error.

Can I switch from PF to IPFW without downtime?

On a local machine, yes -- load the IPFW module, add your rules, then disable PF. On a remote server, plan carefully. Load IPFW with a permissive ruleset first, verify connectivity, then disable PF and tighten the IPFW rules. Always have out-of-band console access available.

Which firewall is better for a router with multiple interfaces?

Both handle multi-interface routing well. PF's route-to and reply-to directives make policy-based routing straightforward. IPFW's fwd (forward) action achieves similar results. For a general-purpose firewall router, PF is typically easier to configure.

Conclusion

PF and IPFW are both excellent firewalls. PF is the better default choice for most FreeBSD administrators -- its readable syntax, built-in statefulness, and clean NAT handling make it productive from day one. IPFW is the better choice when you need dummynet's traffic shaping capabilities, prefer imperative rule management, or are working in environments where IPFW is already established.

Pick one, learn it deeply, and build your server hardening practices around it. Either firewall, properly configured, will protect your FreeBSD systems reliably.

Get more FreeBSD guides

Weekly tutorials, security advisories, and package updates. No spam.