FreeBSD.software
Home/Guides/How to Configure IPFW Firewall on FreeBSD
tutorial·2026-04-09·12 min read

How to Configure IPFW Firewall on FreeBSD

Complete guide to configuring IPFW firewall on FreeBSD: enabling IPFW, rule syntax, stateful filtering, NAT with natd and in-kernel NAT, dummynet traffic shaping, and logging.

How to Configure IPFW Firewall on FreeBSD

IPFW (IP Firewall) is FreeBSD's native firewall, included in the base system since 1994. It provides packet filtering, stateful inspection, NAT, traffic shaping via dummynet, and logging. Unlike pf (which was ported from OpenBSD), IPFW is developed as part of FreeBSD and has deep integration with the kernel. This guide covers enabling IPFW, rule syntax, stateful filtering, NAT configuration, dummynet traffic shaping, logging, and practical firewall configurations.

Prerequisites

  • FreeBSD 14.0 or later
  • Root access
  • Console access (in case you lock yourself out with a firewall misconfiguration)

The most important safety note: when configuring a firewall remotely over SSH, a mistake can lock you out permanently. Always have console access (physical, IPMI, serial, or VM console) available before modifying firewall rules.

Step 1: Enable IPFW

IPFW can be loaded as a kernel module or compiled into the kernel. The kernel module approach is simpler and sufficient for most use cases.

Enable via rc.conf

sh
sysrc firewall_enable="YES" sysrc firewall_type="workstation" sysrc firewall_myservices="22/tcp 80/tcp 443/tcp" sysrc firewall_allowservices="any" sysrc firewall_logdeny="YES"

This uses FreeBSD's built-in workstation firewall profile, which provides a reasonable default: allow outbound traffic, allow inbound on specified ports, deny everything else.

Start IPFW

sh
service ipfw start

Verify IPFW is Running

sh
ipfw list

You should see a numbered list of rules. The last rule is always 65535 deny ip from any to any (the default deny rule).

Load the Kernel Module Manually

If you need IPFW without the rc.conf integration:

sh
kldload ipfw

To load at boot, add to /boot/loader.conf:

sh
ipfw_load="YES"

Safety: Open Firewall on Module Load

When the IPFW kernel module loads, the default policy is deny all. To prevent locking yourself out, set the default to allow while configuring:

sh
sysctl net.inet.ip.fw.default_to_accept=1

Add to /etc/sysctl.conf during initial configuration. Remove it once your rules are finalized and tested.

Step 2: Understand Rule Syntax

IPFW rules follow this general format:

shell
ipfw add [rule_number] [prob probability] action [log [logamount number]] proto from src to dst [options]

Components

| Component | Description |

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

| rule_number | 1-65534 (rules processed in order, first match wins) |

| action | allow, deny, reject, count, skipto, check-state, etc. |

| proto | ip, tcp, udp, icmp, or any |

| src/dst | IP address, network, or "any" with optional port |

| options | in/out, via interface, keep-state, established, etc. |

Basic Rule Examples

sh
# Allow all traffic from localhost ipfw add 100 allow ip from any to any via lo0 # Allow established TCP connections ipfw add 200 allow tcp from any to any established # Allow SSH from any source ipfw add 300 allow tcp from any to me 22 # Allow HTTP and HTTPS ipfw add 400 allow tcp from any to me 80,443 # Allow ICMP (ping) ipfw add 500 allow icmp from any to any # Allow DNS lookups ipfw add 600 allow udp from me to any 53 ipfw add 610 allow udp from any 53 to me # Deny everything else (implicit, but explicit is clearer) ipfw add 65000 deny ip from any to any

Rule Processing Order

IPFW processes rules in numerical order. The first matching rule determines the action. This is different from pf, where the last matching rule wins (unless quick is used).

Rule numbering strategy:

  • 100-999: Loopback and basic infrastructure
  • 1000-9999: Stateful rules and service-specific rules
  • 10000-49999: Application-specific rules
  • 50000-64999: Catch-all and logging rules
  • 65535: Default deny (always present)

Step 3: Stateful Filtering

Stateful rules track connections and allow return traffic automatically. This is the recommended approach for most firewalls.

Enable Stateful Filtering

sh
# Check state of existing connections first ipfw add 100 check-state # Allow outbound traffic and create state entries ipfw add 1000 allow tcp from me to any setup keep-state ipfw add 1010 allow udp from me to any keep-state ipfw add 1020 allow icmp from me to any keep-state # Allow inbound SSH with state tracking ipfw add 2000 allow tcp from any to me 22 setup keep-state # Allow inbound HTTP/HTTPS with state tracking ipfw add 2100 allow tcp from any to me 80,443 setup keep-state

The keep-state keyword creates a dynamic rule entry for matching packets. The check-state rule at the top matches return traffic against existing dynamic rules. The setup keyword matches only TCP SYN packets (connection initiation).

View Dynamic State Table

sh
ipfw -d list

This shows active dynamic rules created by keep-state.

State Table Tuning

For busy servers, increase the state table size:

sh
sysctl net.inet.ip.fw.dyn_max=65536 sysctl net.inet.ip.fw.dyn_ack_lifetime=300 sysctl net.inet.ip.fw.dyn_syn_lifetime=20 sysctl net.inet.ip.fw.dyn_fin_lifetime=1

Add to /etc/sysctl.conf for persistence.

Step 4: Build a Complete Firewall Script

Rather than adding rules one at a time, create a firewall script that loads all rules atomically. Create /usr/local/etc/ipfw.rules:

sh
#!/bin/sh # Flush existing rules ipfw -q flush # Define variables cmd="ipfw -q add" lan="em0" wan="igb0" myip="203.0.113.10" mynet="10.0.0.0/24" # Loopback ${cmd} 100 allow ip from any to any via lo0 ${cmd} 110 deny ip from any to 127.0.0.0/8 ${cmd} 120 deny ip from 127.0.0.0/8 to any # Check dynamic rules first ${cmd} 200 check-state # Anti-spoofing ${cmd} 300 deny ip from ${mynet} to any in via ${wan} ${cmd} 310 deny ip from 10.0.0.0/8 to any in via ${wan} ${cmd} 320 deny ip from 172.16.0.0/12 to any in via ${wan} ${cmd} 330 deny ip from 192.168.0.0/16 to any in via ${wan} # Allow all outbound with state ${cmd} 1000 allow tcp from me to any setup keep-state ${cmd} 1010 allow udp from me to any keep-state ${cmd} 1020 allow icmp from me to any keep-state # Allow outbound from LAN (if this is a gateway) ${cmd} 1100 allow tcp from ${mynet} to any setup keep-state via ${wan} ${cmd} 1110 allow udp from ${mynet} to any keep-state via ${wan} ${cmd} 1120 allow icmp from ${mynet} to any keep-state via ${wan} # Inbound services ${cmd} 2000 allow tcp from any to ${myip} 22 setup keep-state ${cmd} 2100 allow tcp from any to ${myip} 80 setup keep-state ${cmd} 2110 allow tcp from any to ${myip} 443 setup keep-state # Allow ICMP (ping) ${cmd} 3000 allow icmp from any to me icmptypes 0,3,8,11 # Log and deny everything else ${cmd} 60000 deny log ip from any to any

Configure rc.conf to Use the Script

sh
sysrc firewall_enable="YES" sysrc firewall_script="/usr/local/etc/ipfw.rules"

Load the Rules

sh
sh /usr/local/etc/ipfw.rules

Verify:

sh
ipfw list

Step 5: NAT Configuration

IPFW supports NAT for masquerading internal network traffic behind a public IP address.

In-Kernel NAT

FreeBSD's IPFW includes in-kernel NAT, which is faster than the userspace natd.

sh
# Load the NAT kernel module kldload ipfw_nat

Add to /boot/loader.conf:

sh
ipfw_nat_load="YES"

Configure NAT Rules

Add to your firewall script:

sh
# Define NAT instance ipfw nat 1 config if igb0 same_ports unreg_only reset # NAT outbound traffic from LAN ipfw add 500 nat 1 ip from 10.0.0.0/24 to any out via igb0 # NAT inbound traffic (reverse translation) ipfw add 510 nat 1 ip from any to me in via igb0

The nat 1 config options:

  • if igb0: Use the IP address of igb0
  • same_ports: Try to preserve source ports
  • unreg_only: Only NAT RFC1918 addresses
  • reset: Reset the NAT instance when the interface address changes

Port Forwarding

Forward specific ports to internal hosts:

sh
# Forward port 8080 to internal web server ipfw nat 1 config if igb0 same_ports unreg_only \ redirect_port tcp 10.0.0.50:80 8080 # Forward a range of ports ipfw nat 1 config if igb0 same_ports unreg_only \ redirect_port tcp 10.0.0.50:3000-3010 3000-3010

Enable IP Forwarding

NAT requires IP forwarding:

sh
sysrc gateway_enable="YES" sysctl net.inet.ip.forwarding=1

Step 6: Dummynet Traffic Shaping

Dummynet is IPFW's traffic shaping subsystem. It creates virtual "pipes" with configurable bandwidth, delay, and packet loss.

Load Dummynet

sh
kldload dummynet

Add to /boot/loader.conf:

sh
dummynet_load="YES"

Create Bandwidth Pipes

sh
# Create a 10 Mbps pipe ipfw pipe 1 config bw 10Mbit/s # Apply the pipe to traffic from a specific subnet ipfw add 5000 pipe 1 ip from 10.0.0.0/24 to any out via igb0

Per-Client Bandwidth Limits

Use masks to create individual per-IP pipes:

sh
# Create a pipe with per-source-IP queuing ipfw pipe 1 config bw 5Mbit/s mask src-ip 0xffffffff # Apply to outbound traffic ipfw add 5000 pipe 1 ip from 10.0.0.0/24 to any out via igb0

Each unique source IP gets its own 5 Mbps pipe.

Simulate Network Conditions

Dummynet can simulate latency and packet loss for testing:

sh
# Add 50ms latency ipfw pipe 2 config delay 50ms # Add 50ms latency and 1% packet loss ipfw pipe 3 config delay 50ms plr 0.01 # Apply to specific traffic ipfw add 5100 pipe 2 tcp from any to me 80 in via igb0

Queue-Based Shaping

For more granular control, use queues within pipes:

sh
# Create a shared pipe ipfw pipe 1 config bw 100Mbit/s # Create weighted queues ipfw queue 1 config pipe 1 weight 70 # High priority (70%) ipfw queue 2 config pipe 1 weight 30 # Low priority (30%) # Assign traffic to queues ipfw add 5000 queue 1 tcp from any to me 80,443 in ipfw add 5010 queue 2 ip from any to any in

View Pipe and Queue Statistics

sh
ipfw pipe show ipfw queue show

Step 7: Logging

Enable IPFW Logging

IPFW sends log messages to syslog. Enable logging in the kernel:

sh
sysctl net.inet.ip.fw.verbose=1 sysctl net.inet.ip.fw.verbose_limit=100

The verbose_limit prevents log flooding by limiting messages per rule to 100. Set to 0 for unlimited (not recommended on production).

Add to /etc/sysctl.conf:

sh
net.inet.ip.fw.verbose=1 net.inet.ip.fw.verbose_limit=100

Log Specific Rules

Add log to any rule to log matching packets:

sh
# Log and deny ipfw add 60000 deny log ip from any to any # Log and allow (for debugging) ipfw add 2000 allow log tcp from any to me 22 setup keep-state # Log with log amount limit per rule ipfw add 60000 deny log logamount 50 ip from any to any

Direct Logs to a Separate File

Configure syslog to separate IPFW logs. Add to /etc/syslog.conf:

sh
!ipfw *.* /var/log/ipfw.log

Create the log file and restart syslog:

sh
touch /var/log/ipfw.log service syslogd restart

Log Rotation

Add to /etc/newsyslog.conf:

sh
/var/log/ipfw.log 600 7 1000 * JC

View Firewall Logs

sh
tail -f /var/log/ipfw.log

IPFW log entries show the rule number, action, protocol, source, destination, and interface. Example:

shell
ipfw: 60000 Deny TCP 198.51.100.50:54321 203.0.113.10:3389 in via igb0

Step 8: Advanced Features

Rate Limiting with Dummynet

Limit the rate of new SSH connections to prevent brute force:

sh
# Create a pipe that limits to 3 connections per second per source IP ipfw pipe 10 config bw 0 mask src-ip 0xffffffff ipfw queue 10 config pipe 10 weight 1 slots 3 ipfw add 1900 queue 10 tcp from any to me 22 setup in via igb0 ipfw add 2000 allow tcp from any to me 22 setup keep-state

Table-Based Rules

IPFW tables allow dynamic rule sets without reloading the entire configuration:

sh
# Create a table for blocked IPs ipfw table 1 create type addr # Add entries ipfw table 1 add 198.51.100.0/24 ipfw table 1 add 203.0.113.50 # Use the table in a rule ipfw add 400 deny ip from 'table(1)' to any # Remove an entry ipfw table 1 delete 203.0.113.50 # List table contents ipfw table 1 list

Tables are useful for blocklists that change frequently without requiring a full rule reload.

Common Firewall Configurations

Web Server

sh
#!/bin/sh ipfw -q flush cmd="ipfw -q add" ${cmd} 100 allow ip from any to any via lo0 ${cmd} 200 check-state ${cmd} 1000 allow tcp from me to any setup keep-state ${cmd} 1010 allow udp from me to any keep-state ${cmd} 2000 allow tcp from any to me 22 setup keep-state ${cmd} 2100 allow tcp from any to me 80,443 setup keep-state ${cmd} 3000 allow icmp from any to me icmptypes 0,3,8,11 ${cmd} 60000 deny log ip from any to any

Frequently Asked Questions

What is the difference between IPFW and pf?

IPFW is FreeBSD-native, processes rules first-match-wins, supports dummynet traffic shaping, and uses numbered rules. pf was ported from OpenBSD, processes rules last-match-wins (unless quick is used), has a cleaner syntax for complex rule sets, and is used by pfSense and OPNsense. Both are capable firewalls. IPFW is simpler for basic setups; pf is often preferred for complex configurations.

Can I use IPFW and pf at the same time?

Technically yes, but it is strongly discouraged. Running two firewalls creates confusion about which rules apply and in what order. Choose one and use it exclusively.

How do I prevent locking myself out when configuring remotely?

Use the default_to_accept sysctl during configuration:

sh
sysctl net.inet.ip.fw.default_to_accept=1

Or schedule a script to flush rules after a timeout:

sh
# Schedule a rule flush in 5 minutes (safety net) echo "ipfw -q flush" | at now + 5 minutes

If your configuration works, cancel the at job. If not, rules are flushed automatically.

How do I see which rule matched a specific packet?

Add count rules before your actual rules to see hit counts:

sh
ipfw show

The output shows the rule number, packet count, byte count, and the rule itself. High counts on deny rules indicate blocked traffic.

Can IPFW handle IPv6?

Yes. IPFW supports IPv6 natively. Use ip6 as the protocol:

sh
ipfw add 2000 allow tcp from any to me6 22 setup keep-state ipfw add 2100 allow tcp from any to me6 80,443 setup keep-state

Does IPFW support connection rate limiting?

Yes, through dummynet pipes and queues. You can limit connections per second per source IP using pipe masks. For simpler rate limiting, use the limit keyword:

sh
# Limit each source IP to 5 concurrent SSH connections ipfw add 2000 allow tcp from any to me 22 setup limit src-addr 5

How do I debug IPFW rule matching?

Enable verbose logging and add log to the rules you want to trace. Then watch the log:

sh
tail -f /var/log/ipfw.log

For real-time packet analysis, use tcpdump on the relevant interface to see what traffic is arriving before IPFW processes it.

Get more FreeBSD guides

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