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
shsysrc 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
shservice ipfw start
Verify IPFW is Running
shipfw 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:
shkldload ipfw
To load at boot, add to /boot/loader.conf:
shipfw_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:
shsysctl 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:
shellipfw 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
shipfw -d list
This shows active dynamic rules created by keep-state.
State Table Tuning
For busy servers, increase the state table size:
shsysctl 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
shsysrc firewall_enable="YES" sysrc firewall_script="/usr/local/etc/ipfw.rules"
Load the Rules
shsh /usr/local/etc/ipfw.rules
Verify:
shipfw 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:
shipfw_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 igb0same_ports: Try to preserve source portsunreg_only: Only NAT RFC1918 addressesreset: 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:
shsysrc 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
shkldload dummynet
Add to /boot/loader.conf:
shdummynet_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
shipfw pipe show ipfw queue show
Step 7: Logging
Enable IPFW Logging
IPFW sends log messages to syslog. Enable logging in the kernel:
shsysctl 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:
shnet.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:
shtouch /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
shtail -f /var/log/ipfw.log
IPFW log entries show the rule number, action, protocol, source, destination, and interface. Example:
shellipfw: 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:
shsysctl 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:
shipfw 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:
shipfw 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:
shtail -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.