pfSense Firewall Best Practices - Rules and Migration

A well-designed firewall policy is the foundation of network security. pfSense provides flexible filtering capabilities built on pf, but the platform alone does not guarantee security - the outcome depends on how the administrator organizes rules, maintains their relevance, and adapts the policy to evolving requirements. This guide covers the principles of building a robust firewall policy, common mistakes to avoid, rule auditing procedures, and detailed migration instructions for Cisco ASA, FortiGate, and MikroTik RouterOS.

Rule Organization

Rule ordering in pfSense directly determines filtering results: the first match wins principle means the first matching rule terminates packet processing. Improper ordering causes traffic to be blocked or permitted contrary to the administrator’s intent. A detailed description of the processing mechanism is available in the pfSense firewall rules section.

Recommended Rule Order Per Interface

For each interface (LAN, DMZ, OPT, etc.), maintain a consistent structure:

  1. Anti-lockout - pfSense creates this rule automatically on the LAN interface. If disabled manually, create an explicit allow rule for administrative access (HTTPS/SSH to the interface IP) and place it first.

  2. Explicit blocks for known threats - block traffic from blacklist sources (bogon networks, GeoIP lists, known malicious address lists). Use URL Table aliases for these rules, as they update automatically.

  3. Allow rules for legitimate traffic - specific rules permitting required protocols and destinations. Each rule should be as specific as possible: define the protocol, destination port, source address, and destination address.

  4. Implicit deny (default deny) - pfSense automatically drops all traffic that does not match any rule. Creating an explicit block rule at the bottom of the list is not required but recommended for two purposes: logging denied traffic and providing visual confirmation that the default deny policy is in effect.

Using Separators

pfSense supports colored separators between rules in the web interface. Separators have no effect on traffic processing but significantly improve navigation when managing large rule sets.

Recommended separator naming scheme:

SeparatorPurpose
--- ADMIN ACCESS ---Management rules
--- BLOCK LISTS ---Blacklist-based block rules
--- SERVERS ---Server access (grouped by function)
--- USER TRAFFIC ---Workstation rules
--- DEFAULT DENY ---Final deny rule

Rule Descriptions

Every firewall rule must contain a meaningful description in the Description field. The description is the only way to understand a rule’s purpose without analyzing its parameters. Recommended format:

[TICKET/CR] Purpose - expiration (if temporary)

Examples:

  • CR-2024-031 VPN access for partner ExampleCorp - until 2024-12-31
  • Allow DNS to internal resolvers
  • Block outbound SMTP from workstations (relay prevention)

Rules without descriptions turn the rule set into an unreadable list that becomes impossible to maintain when administrators change.

Minimizing Rule Count Through Aliases

A large number of rules complicates auditing and increases the probability of errors. Instead of creating a separate rule for each server or port, use aliases :

Suboptimal - three separate rules:

ActionSourceDestination PortDescription
PassLAN net80HTTP to web server 1
PassLAN net443HTTPS to web server 1
PassLAN net8443Alt HTTPS to web server 2

Optimal - a single rule with aliases:

ActionSourceDestinationPortDescription
PassLAN netAlias: WebServersAlias: WebPortsAccess to web servers

The WebServers alias contains server IP addresses, and the WebPorts alias contains ports 80, 443, 8443. When a new server or port is added, only the alias needs updating - no new rule is required.

Common Mistakes

Overly Permissive Rules (any/any)

A rule Pass * * * * * (allow everything from all sources to all destinations on all protocols) is the most prevalent and dangerous mistake. These rules are created for quick debugging and left in the production configuration. pfSense creates a default allow rule on LAN - after initial setup, this rule must be replaced with a set of specific permits.

Warning:

An any/any rule on the LAN interface permits any device on the network to reach arbitrary addresses over any protocol, including direct internet access bypassing proxy servers and security controls.

No Egress Filtering

Administrators often focus exclusively on inbound traffic and completely ignore outbound filtering. Lack of egress filtering allows compromised hosts to freely establish reverse shells, exfiltrate data, and communicate with command-and-control (C2) servers. Minimum egress restrictions:

  • Allow DNS only to internal resolvers or specific public resolvers
  • Allow HTTP/HTTPS through a proxy server (when available)
  • Allow SMTP only from the mail server
  • Block direct outbound connections on non-standard ports (IRC, Tor, P2P)

Missing Logging on Deny Rules

pfSense logs traffic matching the implicit deny rule by default. However, when adding explicit block rules (for example, blacklist rules), logging must be enabled manually - it is disabled by default for explicit rules. Without logs, the administrator cannot see blocked attacks or assess policy effectiveness.

Shadowed Rules

A shadowed rule is a rule that never matches traffic because a broader rule placed above it intercepts all qualifying traffic. Example:

#ActionSourceDestinationPort
1PassLAN net**
2BlockLAN net10.0.0.522

Rule 2 never fires because Rule 1 permits all traffic from LAN, including SSH to 10.0.0.5. To fix this, move the block rule above the allow rule:

#ActionSourceDestinationPort
1BlockLAN net10.0.0.522
2PassLAN net**

Not Using Aliases

Hardcoding IP addresses and ports directly in rules leads to data duplication: when a server address changes, every rule referencing it must be updated individually. Aliases solve this problem - a detailed description of their capabilities is available in the pfSense aliases section.

Forgotten Temporary Rules

Temporary rules created for diagnostics or short-term tasks remain in the configuration indefinitely. Recommendations:

  • Include the expiration date in the rule description (e.g., until 2024-06-30)
  • Enable logging on temporary rules to monitor their usage
  • Conduct monthly reviews for the presence of temporary rules

Migration from Cisco ASA

Migrating from Cisco ASA to pfSense requires understanding fundamental differences in architecture and terminology. Both firewalls operate on stateful inspection and first match wins, but their approaches to rule organization, objects, and NAT differ substantially.

Terminology Mapping

Cisco ASApfSenseNotes
Access Control List (ACL)Firewall Rules (per interface)In pfSense, rules are bound to interfaces directly; no separate ACL-to-interface binding step
Object Group (network)Alias (Host / Network)Functionally identical - named address groups
Object Group (service)Alias (Port)Port groups
Security Level (0-100)No equivalentpfSense does not use security levels; everything is determined by rules
Security ZoneInterfaceEach pfSense interface is effectively a separate zone; zone-based firewall is not supported
Global ACLFloating RulesFloating rules apply to multiple interfaces
NAT exemption (identity NAT)Manual Outbound NAT (no NAT rule)Configured in Firewall > NAT > Outbound
Twice NATManual Outbound NAT + Port ForwardpfSense separates inbound and outbound NAT
Packet TracerPacket Capture + StatesDiagnostics via Diagnostics > Packet Capture

Converting ACLs to pfSense Rules

ACLs on Cisco ASA are ordered lists of ACEs (Access Control Entries) bound to an interface via the access-group command. In pfSense, each rule is already bound to an interface - the intermediate binding step does not exist.

Cisco ASA:

object-group network WEBSERVERS
 network-object host 10.0.1.10
 network-object host 10.0.1.11

object-group service WEBPORTS tcp
 port-object eq www
 port-object eq https

access-list INSIDE_IN extended permit tcp any object-group WEBSERVERS object-group WEBPORTS
access-group INSIDE_IN in interface inside

pfSense - equivalent configuration:

  1. Create alias WEBSERVERS (type Host): 10.0.1.10, 10.0.1.11
  2. Create alias WEBPORTS (type Port): 80, 443
  3. Create a rule on the LAN tab:
ActionProtocolSourceDestinationPortDescription
PassTCPLAN netWEBSERVERSWEBPORTSHTTP/HTTPS to web servers

NAT Processing Differences

On Cisco ASA, NAT and filtering rules (ACLs) are processed as related but separate constructs. Processing order depends on the ASA version and NAT type (auto NAT, manual NAT, twice NAT). In pfSense, NAT and firewall rules are processed sequentially:

  1. Inbound packet passes through NAT rules (Destination NAT / Port Forward)
  2. After address translation, the packet is evaluated by firewall rules using the already-translated addresses

This means that pfSense firewall rules must reference the translated (internal) destination addresses, not the public ones. This difference frequently causes errors during migration.

Example - port forward with filtering:

NAT:   WAN:443 -> 10.0.1.10:443
Rule:  Pass TCP * -> 10.0.1.10:443 (on WAN interface)

On Cisco ASA, the ACL rule references the public address (unless nat control is used); in pfSense, it references the internal server address.

Security Levels vs. Interface Rules

Cisco ASA assigns each interface a security level (0–100). Traffic from a higher security level interface to a lower one is permitted by default. Traffic in the reverse direction is denied without an explicit ACL.

pfSense does not support the security level concept. Each interface is processed independently: only traffic explicitly permitted by rules is allowed (except for the default allow rule on LAN, which should be replaced after initial setup). This simplifies the security model but requires creating explicit rules for every traffic direction.

Step-by-Step ASA Migration Plan

  1. Inventory objects - export all object-groups using show running-config object-group and create corresponding pfSense aliases
  2. Export ACLs - export ACLs using show access-list and convert each ACE to a pfSense rule on the appropriate interface
  3. Verify ordering - confirm that rule order is preserved (first match wins on both platforms)
  4. NAT - convert the NAT configuration, accounting for processing order differences
  5. Testing - verify critical traffic flows using Diagnostics > Packet Capture and firewall logs
  6. Remove the default allow rule on LAN - replace it with a set of specific permits

Migration from FortiGate

FortiGate uses a zone-based firewall with security policies linking source and destination zones. pfSense uses a per-interface model without zones. Migration requires redesigning the rule structure to account for this difference.

Terminology Mapping

FortiGatepfSenseNotes
Security PolicyFirewall RuleFortiGate policies contain more parameters (UTM profiles, schedules)
Address ObjectAlias (Host / Network)Functionally identical
Address GroupAlias (containing nested aliases)pfSense supports nested aliases
Service ObjectAlias (Port)Port groups
ZoneInterface / Interface GrouppfSense does not support zones; interfaces are used instead
VDOMSeparate pfSense instanceVDOMs have no equivalent - isolation requires a separate installation
Security Profile (AV, IPS, Web Filter)Packages: Suricata/Snort, pfBlockerNG, SquidGuardImplemented via additional packages
Central NATFirewall > NATSimilar structure
FortiViewStatus > Dashboard + pfSense logsLimited analytics without additional tools

Converting Policies to Rules

A FortiGate policy defines: source zone, destination zone, addresses, services, action, UTM profiles, and schedule. The pfSense equivalent is a rule on the interface tab specifying protocol, addresses, and ports.

FortiGate:

config firewall policy
    edit 10
        set name "LAN to WAN Web"
        set srcintf "lan"
        set dstintf "wan1"
        set srcaddr "LAN_SUBNET"
        set dstaddr "all"
        set action accept
        set schedule "always"
        set service "HTTP" "HTTPS" "DNS"
        set utm-status enable
        set av-profile "default"
        set ips-sensor "default"
        set ssl-ssh-profile "certificate-inspection"
        set logtraffic all
    next
end

pfSense - equivalent configuration:

  1. Create alias WebAndDNS_Ports (type Port): 80, 443, 53
  2. Create a rule on the LAN tab:
ActionProtocolSourceDestinationPortGatewayDescription
PassTCP/UDPLAN net*WebAndDNS_Ports*LAN to WAN Web + DNS
  1. Install Suricata or Snort packages to replace UTM profiles (AV, IPS)
  2. Install SquidGuard or pfBlockerNG for web filtering

FortiGate Zones vs. pfSense Interfaces

FortiGate allows combining multiple interfaces into a zone and creating policies between zones. This abstraction does not exist in pfSense. Available alternatives:

  • Interface Groups - pfSense allows grouping interfaces and creating shared rules for the group. This is the closest equivalent to zones, but with limitations: group rules are processed before individual interface rules and cannot be overridden at the interface level.
  • Floating Rules - allow applying a single rule to multiple interfaces. Suitable for global policies (e.g., blocking specific protocols on all interfaces).

VDOMs and Multi-Tenancy

FortiGate supports Virtual Domains (VDOMs) - virtual firewall instances within a single device. Each VDOM has its own interfaces, routing table, policies, and administrative access.

pfSense does not support a VDOM equivalent. For multi-tenant scenarios, the options are:

  • Separate pfSense instances (physical or virtual)
  • VLAN separation with distinct rule sets for each VLAN interface (limited isolation, shared routing table)

Security Profiles and pfSense Equivalents

FortiGate offers integrated security profiles (UTM) applied directly within a policy. pfSense achieves similar functionality through separate packages:

FortiGate FunctionpfSense PackageNotes
Antivirus ProfileClamAV (via Squid)HTTP traffic scanning through proxy
IPS SensorSuricata / SnortFull IDS/IPS; requires rule configuration
Web FilterSquidGuard / pfBlockerNG-develCategory and list-based filtering
Application ControlSuricata (Application Layer rules)Limited support compared to FortiGate
SSL InspectionSquid (SSL Bump)Requires root certificate deployment to clients
DNS FilterpfBlockerNG-devel (DNSBL)DNS-based blocklist filtering

Warning:

FortiGate UTM functions are deeply integrated into policy processing and operate at the hardware level (ASIC). pfSense packages run in software and require additional CPU/RAM resources. When migrating high-throughput installations, load testing is mandatory.

Step-by-Step FortiGate Migration Plan

  1. Export configuration - export the full configuration via execute backup full-config
  2. Map zones to interfaces - determine which pfSense interfaces correspond to FortiGate zones; use Interface Groups where appropriate
  3. Create aliases - convert Address Objects and Address Groups to pfSense aliases
  4. Create rules - convert each Security Policy to a pfSense rule on the appropriate interface
  5. Install security packages - install Suricata/Snort, pfBlockerNG, and other packages to replace UTM profiles
  6. Migrate NAT - convert VIPs and Central NAT to Port Forward and Outbound NAT
  7. Testing - verify all traffic flows, especially cross-zone traffic that in pfSense is implemented through rules on multiple interfaces

Migration from MikroTik RouterOS

MikroTik RouterOS uses chains to organize filtering, NAT, and packet manipulation rules. The model differs fundamentally from pfSense: in RouterOS, the administrator works with filter, NAT, and mangle tables, each containing chains (input, forward, output). In pfSense, all rules are bound to interfaces and processed on ingress.

Terminology Mapping

MikroTik RouterOSpfSenseNotes
/ip firewall filterFirewall > RulesPrimary filtering rules
Chain: inputInterface rules (traffic to pfSense itself)Filtering traffic destined for the device
Chain: forwardInterface rules (transit traffic)pfSense does not separate input/forward - all rules are on the interface tab
Chain: outputFloating Rules (outbound)Outbound traffic from pfSense is filtered via floating rules
Address ListAliasFunctionally identical
/ip firewall natFirewall > NATSplit into Port Forward and Outbound NAT
/ip firewall mangleNo direct equivalentPartially replaced by floating rules with DSCP/Queue
Connection TrackingState Tablepf uses a state table analogous to conntrack
Action: jump (custom chain)No equivalentpfSense does not support custom chains
Default policy: acceptDefault policy: denyCritical difference during migration

Critical Difference: Default Policy

Warning:

MikroTik RouterOS permits all traffic by default (default accept). pfSense denies all traffic by default (default deny) on all interfaces except LAN (where an initial allow-all rule is created). During migration, ensure that all required permits are created explicitly, or traffic will be blocked.

Converting Chains to Interface Rules

In MikroTik, rules are distributed across chains (input, forward, output). In pfSense, all rules reside on interface tabs. The mapping principle:

MikroTik - filter/forward:

/ip firewall filter
add chain=forward src-address=192.168.1.0/24 dst-address=10.0.0.0/24 \
    dst-port=80,443 protocol=tcp action=accept comment="LAN to DMZ web"
add chain=forward src-address=192.168.1.0/24 dst-address=0.0.0.0/0 \
    dst-port=53 protocol=udp action=accept comment="LAN DNS"
add chain=forward src-address=192.168.1.0/24 action=drop \
    comment="Block all other LAN forward"

pfSense - rules on the LAN tab:

#ActionProtocolSourceDestinationPortDescription
1PassTCP192.168.1.0/24DMZ_SERVERS80, 443LAN to DMZ web
2PassUDP192.168.1.0/24*53LAN DNS
3Block*192.168.1.0/24**Block all other LAN

Address Lists vs. Aliases

MikroTik Address Lists and pfSense aliases serve the same purpose - grouping addresses for use in rules. Key differences:

FeatureMikroTik Address ListpfSense Alias
Dynamic additionYes (action=add-src-to-address-list)No (URL Table only)
Timeout (entry TTL)Yes (timeout=1h)No
Nested listsNoYes (aliases can reference other aliases)
URL sourcesNo (scripting only)Yes (URL Table, scheduled updates)
FQDNNo (scripting only)Yes (automatic DNS resolution)

Dynamic address addition via action=add-src-to-address-list (e.g., for automatically blocking scanners) has no direct equivalent in pfSense. Similar functionality is achieved through packages:

  • pfBlockerNG - automatic blocking via IP lists and GeoIP
  • Suricata/Snort - automatic blocking upon attack detection

NAT: RouterOS vs. pfSense

MikroTik combines all NAT types in a single /ip firewall nat table with srcnat and dstnat chains. pfSense separates NAT into three sections:

MikroTik NATpfSense NATDescription
chain=srcnat action=masqueradeOutbound NAT (Automatic)Masquerading for outbound traffic
chain=srcnat action=src-natOutbound NAT (Manual)Static Source NAT
chain=dstnat action=dst-natPort ForwardInbound connection redirection
chain=dstnat action=redirectPort Forward (redirect)Redirect to localhost

In MikroTik, NAT and filter rules are separate tables processed independently. In pfSense, creating a Port Forward automatically generates an associated firewall rule (unless disabled). During migration, verify that each dstnat rule has a corresponding allow rule in pfSense.

Mangle and Advanced Packet Processing

The MikroTik mangle table is used for marking packets, connections, and routes, as well as modifying header fields (TTL, DSCP, TOS). pfSense has no direct mangle equivalent. Partial replacements:

Mangle FunctionpfSense Solution
Connection mark + policy routingFloating rules with Gateway specified
DSCP markingFloating rules with limiter (traffic shaping)
Packet mark for QoSTraffic Shaper (ALTQ or Limiters)
Change MSSSystem > Advanced > Firewall/NAT > MSS Clamping
Route mark (PBR)Policy Routing via multiple gateways

Connection Tracking

MikroTik RouterOS uses the connection tracking module (conntrack) with explicit state matchers:

/ip firewall filter
add chain=forward connection-state=established,related action=accept
add chain=forward connection-state=invalid action=drop

In pfSense, the state table is managed automatically: every allow rule creates a state table entry, and return traffic is passed without explicit rules. An explicit rule for established/related is not required. However, enabling the Scrub option (System > Advanced > Firewall/NAT) is recommended to drop fragmented and malformed packets.

Step-by-Step MikroTik Migration Plan

  1. Export configuration - execute /export to obtain the text configuration
  2. Inventory Address Lists - convert to pfSense aliases
  3. Map chains:
    • chain=input - rules on interfaces for access to pfSense (WebGUI, SSH, DNS)
    • chain=forward - rules on interfaces for transit traffic
    • chain=output - floating rules (if needed)
  4. Remove conntrack rules - in pfSense, established/related handling is automatic
  5. Convert NAT - split srcnat and dstnat into Outbound NAT and Port Forward
  6. Replace mangle - identify which mangle functions are required and implement via floating rules, traffic shaper, or system settings
  7. Verify default policy - ensure all permits are created explicitly (pfSense = default deny)
  8. Testing - verify all traffic flows; pay special attention to rules that in MikroTik used custom chains (jump), as pfSense does not support this construct

Rule Auditing

Regular firewall rule auditing is a mandatory practice for maintaining security and regulatory compliance. Without auditing, rule sets degrade over time: stale permits accumulate, organizational logic deteriorates, and the attack surface expands.

Hit Counters

pfSense displays hit counters for each rule in the interface rule list. Counters enable:

  • Identifying rules with zero counts - potential candidates for removal
  • Determining the most heavily used rules for position optimization (moving them higher to accelerate processing)
  • Detecting anomalies - a sudden increase in hit counts may indicate an attack or a change in network infrastructure

Warning:

Counters reset on pfSense reboot and when firewall rule changes are applied. For long-term analysis, use an external log collection system.

Identifying Unused Rules

Procedure for identifying unused rules:

  1. Reset all rule counters: Diagnostics > pfTop > Reset States
  2. Wait for a full business cycle (minimum 30 days for a typical organization to account for monthly processes)
  3. Check counters: rules with zero values are candidates for removal
  4. Before removing, change the rule action to Block with logging and wait an additional period
  5. If the block did not cause any incidents - remove the rule

Audit Frequency

Network TypeRecommended Frequency
Stable infrastructure, minimal changesEvery 6 months
Dynamic environment, frequent changesMonthly
Network undergoing migrationWeekly
PCI DSS compliance (Requirement 1.1.7)Every 6 months (minimum)

Regulatory Compliance

PCI DSS

PCI DSS Requirement 1 mandates:

  • Documenting all allowed services, protocols, and ports with business justification (1.1.6)
  • Reviewing firewall and router rules at least every six months (1.1.7)
  • Prohibiting direct public access to the cardholder data environment (1.3)
  • Installing personal firewalls on all mobile and employee-owned devices (1.4)

To satisfy Requirement 1.1.7:

  • Maintain a rule registry with a documented purpose for each rule
  • Record the date of the last review
  • Store audit results as a documented report

CIS Benchmarks

CIS Critical Security Controls (v8) include:

  • Control 4.4: Implement and Manage a Firewall on Servers - mandatory server-level filtering
  • Control 4.5: Implement and Manage a Firewall on End-User Devices - endpoint filtering
  • Control 13.4: Perform Traffic Filtering Between Network Segments - segmentation with traffic control

pfSense supports these controls through VLAN segmentation and firewall rules on each interface.

Audit Checklist

[ ] All rules have descriptions
[ ] No any/any rules (except documented exceptions)
[ ] No rules with zero hit count older than 90 days
[ ] Temporary rules removed or renewed with justification
[ ] Aliases are current (no addresses of decommissioned servers)
[ ] Logging enabled on block rules
[ ] Egress filtering configured
[ ] Default deny rule present explicitly (with logging)
[ ] Separators up to date
[ ] Audit results documented

IDS/IPS Integration

pfSense supports intrusion detection and prevention systems (IDS/IPS) through additional packages - Suricata and Snort. These systems analyze traffic content at the application layer and complement firewall rules, which operate at the network and transport layers.

IDS/IPS and Firewall Interaction

Firewall rules and IDS/IPS operate at different stages of packet processing:

  1. Firewall (pf) - makes Pass/Block decisions based on packet headers (IP, port, protocol)
  2. IDS/IPS (Suricata/Snort) - analyzes the payload of traffic that passed through the firewall

If the firewall blocked a packet, IDS/IPS never sees it. This means IDS/IPS protects only against threats in permitted traffic - for example, exploits in HTTP requests, SQL injections, malicious DNS queries.

Automatic Blocking

Suricata and Snort in IPS mode can automatically block traffic upon threat detection. The blocking mechanism:

  • Suricata/Snort adds the attacker’s IP address to the snort2c table
  • pf checks the snort2c table before processing firewall rules
  • All packets from the blocked IP are dropped until the timeout expires

The administrator controls blocking parameters: ban duration, pass lists (whitelists), and rule categories.

Wazuh Integration

For centralized monitoring of firewall and IDS/IPS events, integrating pfSense with Wazuh is recommended. Wazuh collects pfSense logs (filterlog, Suricata/Snort alerts), correlates events, and generates notifications. Detailed setup instructions are available in the pfSense and Wazuh integration section.

Related Sections

Last updated on