Documents
PfSense Config.xml Structural Reference
PfSense Config.xml Structural Reference
Type
Topic
Status
Published
Created
Mar 22, 2026
Updated
Mar 22, 2026
Created by
Dosu Bot
Updated by
Dosu Bot

pfSense Config.xml Structural Reference#

Overview#

The pfSense config.xml file is the primary configuration storage format for pfSense, a free and open-source firewall and router platform based on FreeBSD. This XML-based configuration file serves as the single source of truth for all system settings, including network interfaces, firewall rules, NAT configurations, VPN tunnels, and service definitions. The configuration format uses <pfsense> as its root XML element, distinguishing it from its close relative OPNsense, which uses <opnsense> as the root element.

pfSense and OPNsense share a common heritage—OPNsense forked from pfSense in 2015—resulting in similar but distinct configuration formats. Both platforms maintain XML-based configuration files with overlapping structure for 35+ top-level sections including system settings, network interfaces, firewall rules, NAT configurations, and service definitions. However, key differences exist in field naming conventions, data type representations, and organizational patterns that make cross-platform configuration parsing a complex engineering challenge.

Understanding the pfSense config.xml structure is critical for multi-vendor firewall management tools such as the opnDossier project, which provides unified configuration analysis and compliance checking across multiple firewall platforms. pfSense support is implemented in opnDossier with a dedicated parser (pkg/parser/pfsense/), schema definitions (pkg/schema/pfsense/), and validator (internal/validator/pfsense.go) that handle the XML schema structure, listtags parsing rules, and structural divergences from the OPNsense format to enable accurate parsing and transformation into platform-agnostic data models.

Root Element and Device Detection#

The root XML element serves as the primary identifier for distinguishing between pfSense and OPNsense configuration files. pfSense uses <pfsense> while OPNsense uses <opnsense> as the document root. This simple but critical difference enables automatic device type detection in multi-vendor parsing systems.

Parser registry patterns in modern implementations register device-specific parsers by root element string (e.g., "pfsense", "opnsense"), allowing a factory pattern to instantiate the appropriate parser based on quick inspection of the XML document's root tag. This approach enables efficient auto-detection without requiring full document parsing or external metadata.

PlatformRoot ElementDefault UserPassword Field
pfSense<pfsense>admin<bcrypt-hash>
OPNsense<opnsense>root<passwd>

Configuration Version Mapping#

pfSense embeds version information within the config.xml file using a <version> element, which tracks the configuration schema version independently from the pfSense software version. This allows the system to detect and migrate configurations from older schema versions when upgrading the firewall software.

Target versions for opnDossier development are pfSense 2.6.x and 2.7.x, which represent the current stable and latest release branches as of 2024. Version-specific variations between these releases require documentation to ensure parsers can handle schema evolution correctly.

According to the background context, the following version mappings apply to pfSense Community Edition:

  • CE 2.5.x: Config versions 21.4 through 21.7
  • CE 2.6.0: Config version 22.2
  • CE 2.7.0: Config version 22.9
  • CE 2.8.0: Config version 24.0

Both pfSense Community Edition (CE) and pfSense Plus share the same configuration format, ensuring compatibility between the free and commercial editions of the platform.

Top-Level XML Sections#

The pfSense config.xml structure contains 35+ documented top-level sections that organize configuration data into logical groupings. These sections are largely shared between pfSense and OPNsense due to their common heritage, though individual field structures may differ.

Core Configuration Sections#

SectionXML TagDescription
System Settings<system>Hostname, domain, DNS, users, groups, SSH, WebGUI
Network Interfaces<interfaces>Dynamic-keyed interface configurations (<wan>, <lan>, <opt0>)
Firewall Rules<filter>Packet filter rules with source/destination matching
NAT Configuration<nat>Port forwarding (inbound) and source NAT (outbound)
Gateways<gateways>Gateway definitions and gateway groups
Static Routes<staticroutes>Static routing table entries
DHCP Server<dhcpd>DHCP scopes, map keyed by interface name
DNS Resolver<unbound>Unbound DNS resolver configuration
VLANs<vlans>VLAN interface definitions
Bridges<bridges>Bridge interface configurations
Link Aggregation<laggs>LAGG (Link Aggregation) groups
GIF Tunnels<gifs>Generic tunnel interfaces
GRE Tunnels<gres>GRE tunnel interfaces
OpenVPN<openvpn>OpenVPN servers, clients, and CSCs
IPsec VPN<ipsec>IPsec phase1 and phase2 configurations
Certificates<ca>, <cert>Certificate authorities and certificates
High Availability<hasync>HA/CARP synchronization settings
Interface Groups<ifgroups>Logical interface groupings

Parser Implementation Note#

Top-level repeating elements like <ca> and <cert> require manual append logic in the parser switch because Go's encoding/xml package cannot automatically populate slices from sibling root-level elements without a container wrapper. This is a critical implementation detail for correct XML deserialization.

Structural Differences from OPNsense#

While pfSense and OPNsense share substantial configuration structure due to their common heritage, several important differences exist that impact parser implementation.

Summary Comparison#

AspectpfSenseOPNsense
Root element<pfsense><opnsense>
Default admin useradminroot
Password field<bcrypt-hash><passwd>
Rule identifier<tracker> (integer from microtime())uuid attribute on <rule> elements
MVC frameworkNone (legacy format only)<OPNsense> subtree for new-style configs
Legacy rule formatCanonicalMaintained for backward compatibility
DNS servers<dnsserver>[] (repeating, array)Single <dnsserver> string
User privileges<priv>[] per-user arraySingle <priv> string
NAT redirect field<target><internalip>
Syslog structureMinimal (filterdescriptions)Different field set

NAT Port Forwards#

Both platforms store NAT inbound port forward rules as direct <nat><rule> children in the legacy format, though OPNsense's Go schema may map this using the path xml:"inbound>rule" as a logical convention. The actual XML structure in both systems uses <nat><rule> for port forwarding rules.

NAT Redirect IP Field#

A CRITICAL difference for InboundNATRule structures: pfSense uses <target> to specify the internal destination IP for NAT port forwards, while OPNsense uses <internalip>. This field name divergence requires platform-specific struct definitions in pkg/schema/pfsense/security.go.

User Password Storage#

pfSense stores user passwords in <bcrypt-hash> elements, while OPNsense uses <passwd>. Both platforms use bcrypt hashing for password security, but the XML element names differ.

DNS Server Configuration#

pfSense uses repeating <dnsserver> elements that must be parsed as an array ([]string in the schema at pkg/schema/pfsense/system.go), defined in the listtags configuration:

<system>
  <dnsserver>8.8.8.8</dnsserver>
  <dnsserver>8.8.4.4</dnsserver>
</system>

OPNsense schema currently models DNS servers as a single string, which is a known schema gap. Using string instead of []string drops all but the last DNS server, breaking name resolution for systems with multiple DNS servers.

Firewall Rule Identifiers#

pfSense uses a <tracker> child element containing an integer auto-generated from microtime() to uniquely identify firewall rules. OPNsense uses a uuid XML attribute on the <rule> element itself (e.g., <rule uuid="...">). Both identifiers may be present in configurations that have been migrated between platforms.

Aliases#

pfSense implements aliases using a flat <aliases><alias[]> container structure, where each <alias> element is a direct child of the <aliases> container. This follows the standard container pattern used throughout the configuration. The UUID vs Tracker distinction applies to firewall rules, not alias definitions. Aliases are currently unimplemented in the OPNsense schema, representing a gap in parser coverage.

Additional pfSense-Specific Fields#

pfSense filter rules include additional fields not present in the base OPNsense legacy format: <id>, <tag>, <tagged>, <os>, and <associated-rule-id>. These fields support advanced filtering and rule association features.

Captive Portal and Traffic Shaping#

pfSense implements captive portal as a zone-keyed map with a different internal structure than OPNsense. Traffic shaping in pfSense uses ALTQ (Alternate Queueing) combined with dummynet for queue management, while OPNsense provides a TrafficShaperConfig optional feature with its own implementation approach.

XML Schema Patterns#

Boolean Representation#

pfSense config.xml uses two distinct boolean patterns that require different parsing strategies:

Presence-Based Booleans#

Element presence indicates true, absence indicates false. Content is irrelevant. The PHP codebase uses isset($rule['field']) to check these flags.

Examples: <disabled/>, <log/>, <quick/>, <not/>, <any/>, <staticnatport/>, <nonat/>, <nordr/>, <nosync/>, <nopfsync/>, <allowopts/>, <disablereplyto/>, <tcpflags_any/>, <interfacenot/>, <nottagged/>

In Go implementations, these require a custom BoolFlag type with specialized UnmarshalXML and MarshalXML methods that set the boolean to true on any element presence regardless of content, and omit the element entirely when false.

Historical Note: pfSense Bug #6893 documents that prior to version 2.3.3, some code produced self-closing tags like <tag/> while other code produced empty element pairs like <tag></tag>. Both are semantically equivalent in XML and must be handled correctly by parsers.

Value-Based Booleans#

Element contains a specific value (typically "1" or "yes") to indicate true. PHP codebase uses $config['field'] == "1" checks.

Examples: <enable>1</enable>, <blockpriv>1</blockpriv>, <blockbogons>1</blockbogons>, <ipv6allow>1</ipv6allow>, <dnsallowoverride>1</dnsallowoverride>

These must be parsed as string types, not BoolFlag. Using BoolFlag for value-based fields would cause <enabled>0</enabled> to incorrectly evaluate to true due to element presence.

Container Pattern#

Collections of similar elements use a container-and-child pattern where the parent element wraps a slice of child elements:

type VLANs struct {
    XMLName xml.Name `xml:"vlans"`
    VLAN []VLAN `xml:"vlan,omitempty"`
}

type VLAN struct {
    XMLName xml.Name `xml:"vlan"`
    If string `xml:"if,omitempty"`
    Tag string `xml:"tag,omitempty"`
    Descr string `xml:"descr,omitempty"`
}

This pattern is used throughout the configuration for: VLANs/VLAN, Bridges/Bridge, GIFInterfaces/GIF, GREInterfaces/GRE, LAGGInterfaces/LAGG, Gateways/Gateway, StaticRoutes/StaticRoute, and many others.

Critical Implementation Detail: XMLName must always be the first field in the struct definition. Placing it in any other position breaks XML marshaling in Go.

Dynamic Interface Keys#

The <interfaces> section uses dynamic element names (<wan>, <lan>, <opt0>, <opt1>, etc.) rather than a repeated <interface name="wan"> pattern. This requires custom UnmarshalXML and MarshalXML implementations in Go to handle the map-based structure where element names serve as keys.

The same dynamic key pattern applies to the <dhcpd> section, where each interface that has DHCP enabled gets its own named element (e.g., <dhcpd><lan>...</lan></dhcpd>).

Listtags: Elements Always Parsed as Arrays#

pfSense's XML parser uses a listtags mechanism to identify XML elements that should always be parsed as arrays, even when only a single instance appears in the configuration. This is defined in the listtags() function in xmlparse.inc.

Complete Listtags Inventory#

The listtags array contains 99 XML tag names:

acls, alias, aliasurl, allowedip, allowedhostname, authserver, bridged, build_port_path, ca, cacert, cert, crl, clone, config, container, columnitem, checkipservice, depends_on_package, disk, dnsserver, dnsupdate, domainoverrides, dyndns, earlyshellcmd, element, encryption-algorithm-option, field, fieldname, gateway_item, gateway_group, gif, gre, group, hash-algorithm-option, hosts, ifgroupentry, igmpentry, interface_array, item, key, lagg, laggroup, lbaction, lbpool, l7rules, lbprotocol, member, menu, tab, mobilekey, mobilegroup, monitor_type, mount, npt, ntpserver, onetoone, openvpn-server, openvpn-client, openvpn-csc, option, package, passthrumac, phase1, phase2, ppp, pppoe, priv, proxyarpnet, pool, qinqentry, queue, pages, pipe, radnsserver, roll, route, row, rrddatafile, rule, schedule, service, servernat, servers, sshkeyfile, serversdisabled, shellcmd, staticmap, subqueue, switch, swport, timerange, tunnel, user, vip, virtual_server, vlan, vlangroup, voucherdbfile, vxlan, wgpeer, winsserver, wolentry, widget, xmldatafile

Implementation Details#

The PHP implementation uses array_flip() to convert the array into a hash map, enabling O(1) lookup performance via isset($listtags[strtolower($name)]). This approach efficiently determines whether an encountered XML element should be treated as an array during parsing.

Custom listtags can be added via the $GLOBALS['custom_listtags'] extension mechanism, allowing packages and plugins to register additional element names that require array parsing. A separate listtags_pkg() function defines listtags specific to package XML parsing.

Multi-Value String Fields#

Some fields pack multiple values into a single element using delimiters, which requires custom unmarshaling logic:

Field TypeExample XMLDelimiter
Floating rule interfaces<interface>wan,lan,opt1</interface>Comma
ICMP type list<icmptype>3,11,0</icmptype>Comma
Bridge members<members>em0,em1</members>Comma
Interface group members<members>em0 em1</members>Space
NTP servers<timeservers>0.ntp.org 1.ntp.org</timeservers>Space

The OPNsense implementation uses a custom InterfaceList type to handle comma-separated interface lists in floating rules.

Firewall Filter Rules#

Firewall filter rules in pfSense are stored in <filter><rule> elements and contain 22+ documented fields for comprehensive packet matching and action control.

Core Rule Fields#

XML ElementTypeDescription
<type>stringAction: pass, block, or reject
<interface>InterfaceListComma-separated interface list for floating rules
<ipprotocol>stringIP version: inet (IPv4) or inet6 (IPv6)
<protocol>stringProtocol: tcp, udp, tcp/udp, icmp, igmp, etc.
<source>Source structSource address/network matching criteria
<destination>Destination structDestination address/network matching criteria
<descr>stringHuman-readable rule description
<disabled/>BoolFlagRule is disabled (presence-based)
<log/>BoolFlagEnable packet logging (presence-based)
<quick/>BoolFlagExit on first match (OPNsense-specific)
<floating>stringFloating rule marker
<direction>stringTraffic direction: in, out, or any
<gateway>stringPolicy routing gateway name
<tracker>stringRule identifier (pfSense: integer)
<statetype>stringState tracking type

Source and Destination Structure#

Source and destination blocks represent the most complex sub-structures in firewall rules:

type Source struct {
    Any *string `xml:"any,omitempty"` // Pointer: nil=absent, non-nil=present
    Network string `xml:"network,omitempty"` // Interface names: lan, wan, opt1, (self)
    Address string `xml:"address,omitempty"` // IP/CIDR or alias name
    Port string `xml:"port,omitempty"` // Port or range (hyphen: "80-443")
    Not BoolFlag `xml:"not,omitempty"` // Negate match
}

Type Note: The Any field must be *string rather than string because Go's XML unmarshaler produces "" for both <any/> and absent elements when using plain string. Using a pointer type correctly distinguishes: non-nil = element present, nil = element absent.

Address Resolution Priority#

The three address fields are mutually exclusive with defined priority:

  1. <network> (highest priority) — Interface names (lan, wan, opt1), interface IPs (lanip, wanip), special value (self), interface groups, VIP names
  2. <address> — IP/CIDR notation (192.168.1.0/24) or alias names
  3. <any> — Match any address (implicit if none specified)

Port ranges use hyphen format in XML (80-443) but must be converted to colon format (80:443) for pf ruleset generation.

Advanced Rule Fields#

CategoryFields
Rate Limiting<max-src-nodes>, <max-src-conn>, <max-src-conn-rate>, <max-src-conn-rates>, <max-src-states>
TCP Matching<tcpflags1>, <tcpflags2>, <tcpflags_any/>
ICMP Matching<icmptype>, <icmp6-type>
State Control<statetimeout>, <nopfsync/>, <nosync/>
IP Options<allowopts/>, <disablereplyto/>
QoS/DSCP<tag>, <tagged>, <dscp>, <vlanprio>, <set-prio>
Traffic Shaping<dnpipe>, <pdnpipe>, <defaultqueue>, <ackqueue>

NAT Configuration#

NAT (Network Address Translation) configuration is stored in the <nat> section with separate structures for inbound (port forwarding) and outbound (source NAT) rules.

NAT Outbound Modes#

Four modes control outbound NAT behavior:

ModeValueDescription
AutomaticautomaticAutomatic outbound NAT rule generation (default)
HybridhybridAutomatic rules plus manual custom rules
ManualadvancedManual outbound NAT rules only
DisableddisabledDisable outbound NAT entirely

Outbound NAT Rule Fields#

XML ElementTypeDescription
<staticnatport/>BoolFlagPreserve source port (no port translation)
<nonat/>BoolFlagDisable NAT (policy NAT exclusion)
<natport>stringTranslated source port or range
<poolopts_sourcehashkey>stringSource hash key for load balancing

Inbound NAT Rule Fields#

XML ElementTypeDescription
<target>stringpfSense: Internal destination IP
<local-port>stringInternal destination port
<natreflection>stringNAT reflection mode
<associated-rule-id>stringLinked firewall rule identifier
<nordr/>BoolFlagNo redirect (policy NAT)
<nosync/>BoolFlagDisable HA synchronization

Implementation Architecture#

The pfSense parser implementation follows a four-phase architecture designed for maintainability and extensibility, with all components implemented in the opnDossier codebase.

Four-Phase Architecture#

  1. Device Document Interface — Abstract DeviceParser interface in pkg/model/device.go for platform-agnostic configuration representation
  2. Parser Abstraction — Factory pattern and parser registry in pkg/parser/ enable dynamic parser selection based on root element detection
  3. pfSense Implementation — pfSense-specific XML DTOs in pkg/schema/pfsense/ and converter logic in pkg/parser/pfsense/ transform raw XML into the common device model
  4. CLI Integration — Automatic device type detection via root element inspection with optional --device-type flag for manual override

pfSense Schema Package Structure#

The pkg/schema/pfsense/ package contains five primary struct definition files that define the pfSense configuration data model:

FilePurpose
document.goRoot Document struct with <pfsense> XML root and top-level sections
system.goSystem configuration: System, User, Group, WebGUI structs with pfSense-specific fields
network.goNetwork services: DHCPv6 and DHCPv6Interface structs
security.goSecurity structures: Nat, Filter, InboundRule, FilterRule with pfSense-specific divergences
services.goService configurations: SyslogConfig, UnboundConfig, Cron, Widgets, Diag structs

The schema follows a copy-on-write pattern: it reuses 25+ OPNsense types where XML structures are identical (VLANs, Gateways, OpenVPN, DHCP, etc.) and forks locally at first divergence (NAT inbound, FilterRule, User/bcrypt-hash, DHCPv6). See pkg/schema/pfsense/README.md for complete structural documentation.

Shared XML Security Utilities#

Both OPNsense and pfSense parsers delegate XML security hardening to shared utilities in pkg/parser/xmlutil.go:

FunctionPurpose
NewSecureXMLDecoder(r io.Reader, maxSize int64)Returns configured *xml.Decoder with entity expansion disabled, size limit enforcement, and charset reader
CharsetReader(charset string, input io.Reader)Handles UTF-8, US-ASCII, ISO-8859-1, and Windows-1252 encoding

This shared implementation eliminates duplication between OPNsense and pfSense parsers while providing consistent XML bomb protection, XXE prevention, and charset handling across both platforms.

Security Requirements#

XML parsers implement comprehensive security measures via pkg/parser/xmlutil.go:

  • XML Bomb Protection — Limit entity expansion depth and count, enforce 10MB document size limit via io.LimitReader
  • XXE Prevention — Disable external entity resolution (Go's xml.Decoder provides this by default, explicitly enforced)
  • Charset Handling — Normalize ISO-8859-1 and Windows-1252 encoded configurations before parsing
  • Streaming Parsing — Use token-based XML decoding (xml.Decoder) to avoid loading entire documents into memory

Parser Registration Pattern#

The v2.0+ registry pattern enables extensible parser registration:

func init() {
    parser.DefaultFactory.Register("pfsense", func() parser.DeviceParser {
        return NewParser()
    })
}

This allows external packages to register custom device parsers for additional platforms beyond OPNsense and pfSense.

Relevant Code Files#

FilePurpose
pkg/schema/pfsense/README.mdComplete pfSense config.xml structural reference with 50+ top-level sections, listtags, version mapping
pkg/schema/pfsense/document.goRoot Document struct with <pfsense> XML root
pkg/schema/pfsense/system.goSystem configuration: System, User, Group, WebGUI
pkg/schema/pfsense/network.goNetwork services: DHCPv6, DHCPv6Interface
pkg/schema/pfsense/security.goSecurity: Nat, Filter, InboundRule, FilterRule
pkg/schema/pfsense/services.goServices: SyslogConfig, UnboundConfig, Cron
pkg/parser/pfsense/parser.gopfSense parser with auto-registration for <pfsense> root
pkg/parser/pfsense/converter.goSchema to CommonDevice converter with subsystem pipelines
pkg/parser/xmlutil.goShared XML security hardening (NewSecureXMLDecoder, CharsetReader)
internal/validator/pfsense.gopfSense configuration validator (system, network, security, users, groups)
pkg/schema/opnsense/opnsense.goRoot OpnSenseDocument XML DTO structure, reused types for pfSense
pkg/schema/opnsense/security.goFilter/NAT rule structures, Source/Destination types, InterfaceList
pkg/schema/opnsense/interfaces.goVLAN, Bridge, GIF, GRE, LAGG container patterns
pkg/schema/opnsense/network.goGateways and static routes structures
internal/cfgparser/xml.goXML parser with switch-based dispatch and security hardening
pkg/parser/opnsense/converter.goOPNsense schema to CommonDevice converter
pkg/model/device.goPlatform-agnostic CommonDevice model interface
pkg/model/firewall.goFirewallRule, NATRule, RuleEndpoint types with typed enums
internal/schema/common.goBoolFlag type definition with XML marshaling logic
internal/schema/security_test.goXML round-trip test examples demonstrating correct parsing
pfsense/xmlparse.incpfSense listtags definition and XML parsing functions
  • OPNsense Configuration Format — Sister platform sharing common heritage and similar XML structure
  • XML Data Transfer Object (DTO) Patterns — Design patterns for mapping XML to strongly-typed language structures
  • Go XML Encoding — Go standard library encoding/xml package capabilities and limitations
  • Firewall Rule Modeling — Abstract representation of firewall rules across multiple vendor platforms
  • Configuration Compliance Checking — Automated security and policy validation for firewall configurations
  • Multi-Vendor Network Device Management — Unified management approaches for heterogeneous network infrastructure

Implementation Status#

pfSense support is implemented in opnDossier with comprehensive parser, schema, and validator components. The implementation includes:

  • Parserpkg/parser/pfsense/ with automatic <pfsense> root element detection and registration
  • Schemapkg/schema/pfsense/ with 5 struct files (document, system, network, security, services) following copy-on-write pattern
  • Validatorinternal/validator/pfsense.go with system, network, security, user, and group validation
  • Converter — Multi-stage pipeline transforming pfsense.DocumentCommonDevice with subsystem converters for system, network, security, and services
  • Security — Shared XML hardening via pkg/parser/xmlutil.go (XXE prevention, size limits, charset handling)
  • Test Coverage — 1200+ lines of parser/converter tests plus pfSense 2.6.x and 2.7.x test fixtures

The implementation supports pfSense CE 2.6.x and 2.7.x (config versions 22.2 and 22.9), with the schema designed to handle version evolution. Both pfSense Community Edition and pfSense Plus share the same configuration format.

Documented Schema Divergences#

The following pfSense-specific divergences from OPNsense are implemented in the schema:

AreapfSense ImplementationLocation
User privilegesGroup.Priv as []string (multiple elements)pkg/schema/pfsense/system.go
DNS serversSystem.DNSServers as []string (repeating <dnsserver>)pkg/schema/pfsense/system.go
NAT inbound redirect IPInboundRule.Target field (vs OPNsense InternalIP)pkg/schema/pfsense/security.go
User passwordsUser.BcryptHash field (vs OPNsense Password)pkg/schema/pfsense/system.go
Syslog configurationSyslogConfig with minimal fields (FilterDescriptions)pkg/schema/pfsense/services.go
DHCPv6 serverDHCPv6 with RAMode and RAPriority fieldspkg/schema/pfsense/network.go
WebGUI configurationWebGUI struct with pfSense-specific dashboard and CSS fieldspkg/schema/pfsense/system.go

This documentation serves as the specification and design reference for the pfSense implementation, compiling information from pfSense source code, vendor documentation, and structural analysis of the XML format.

References#