Running Two pfSense Firewalls as One: A Practical Look at CARP Failover
How pfSense CARP, XMLRPC sync, and pfsync combine to give you a high-availability firewall pair — with a concrete two-node setup, verification steps, and the gotchas that cause split-brain.
29 Sept 2025, 03:23 UTC

Your firewall is the single device standing between your network and everything else. If it dies at 2 a.m., everything behind it goes dark until someone drives in, swaps hardware, and restores a config backup. pfSense's answer to this is CARP — the Common Address Redundancy Protocol — which lets two (or more) firewalls share one virtual IP address and hand it off automatically when the active node fails. This post walks through how CARP actually works, a concrete two-node setup, and the trade-offs you should understand before trusting it with production traffic.
What CARP actually does
CARP is a protocol originally from OpenBSD. Multiple machines on the same broadcast domain advertise a shared virtual IP (VIP). One node is elected MASTER and answers traffic for that VIP; the others stay in BACKUP state and listen. Each group is identified by a Virtual Host ID (VHID), and a shared password authenticates the advertisements so a rogue device can't steal the address.
The election hinges on two parameters: advbase (how often, in seconds, a node advertises itself) and advskew (a 0–254 modifier; lower means more preferred). The node with the lowest effective advertisement interval becomes MASTER. In practice you set advskew to 0 on your intended primary and 100 on the backup. If the MASTER stops advertising — crash, power loss, link failure — the backup notices the silence within a few seconds and promotes itself.
CARP alone only moves IP addresses. pfSense layers two more pieces on top: XMLRPC sync, which pushes firewall rules, NAT, aliases, and VPN settings from the primary to the secondary so configs don't drift, and pfsync, which replicates live firewall state tables so existing TCP sessions survive a failover instead of being reset.
A concrete two-node setup
Assume two pfSense appliances (version 2.7.x or Plus 24.x; the menu layout is similar in both) with LAN 192.168.1.2/24 (primary) and 192.168.1.3/24 (secondary), and a dedicated crossover or VLAN interface for sync, 10.0.0.1 and 10.0.0.2.
- Create the CARP VIPs. On the primary, go to Firewall > Virtual IPs > Add. Choose type CARP, interface LAN, address 192.168.1.1/24, a VHID that is unique on that broadcast domain (e.g., 10), and a strong password. Repeat for WAN with a VIP from your ISP block and a different VHID.
- Enable pfsync. Under System > High Availability Sync, set the Synchronize Interface to your dedicated sync interface and enter the peer IP (10.0.0.2). Enable pfsync.
- Enable XMLRPC sync. On the same page, tick the sections you want replicated (firewall rules, NAT, aliases, virtual IPs, etc.) and set the remote system to the secondary's sync IP with admin credentials.
- Configure the secondary. On the backup node, only set the sync interface IP and enable System > High Availability Sync in the receiving direction. Do not manually duplicate rules — XMLRPC pushes them.
- Point outbound NAT at the WAN CARP VIP (Firewall > NAT > Outbound) so return traffic flows to whichever node is MASTER. Likewise, internal hosts should use 192.168.1.1 (the LAN VIP) as their gateway, never a node-specific address.
Verifying it works before you need it
Verification is where most HA pairs quietly fail — everything looks fine until the first real outage. Do these checks deliberately:
- Status > CARP (failover) should show MASTER on the primary and BACKUP on the secondary for every VIP. If both say MASTER, the nodes can't hear each other's advertisements — usually a switch/VLAN issue (see below).
- From the console or SSH (Diagnostics > Command Prompt works too), run
ifconfigand look for the carp interfaces. The master's carp entries should showstatus: masterwith the expected VHID and advskew; the backup showsstatus: backup. - Perform a controlled failover: on the primary, use Status > CARP > Enter Persistent Maintenance Mode, or temporarily disable an interface. Watch the secondary promote to MASTER, then run a continuous ping through the firewall from a LAN client. Expect a brief blip of a few seconds; with pfsync working, established sessions (e.g., an SSH connection through the firewall) should survive rather than drop.
Never claim the pair is "tested" until you've actually pulled the plug once during a maintenance window.
Trade-offs and gotchas
- Broadcast domain requirement. CARP advertisements are multicast within a L2 segment. Both nodes' CARP interfaces must sit in the same untagged or identically tagged VLAN. Misconfigured switch ports (VLAN pruning, multicast filtering, port security) are the classic cause of the dreaded split-brain where both nodes think they're MASTER.
- Passwords in backups. CARP passwords are stored in plain text in pfSense config backups. Treat backup files as sensitive and restrict who can download them.
- Sync is one-way and not everything syncs. XMLRPC sync goes primary → secondary only; changes made on the secondary get overwritten. Some packages don't sync cleanly. After any package change, verify both nodes.
- Stateful failover isn't perfect. pfsync keeps state tables aligned, but a failover mid-burst can still drop a packet or two. Plan for a few seconds of disruption, not zero.
Closing
CARP turns a single point of failure into a maintenance-friendly pair: you can patch or reboot one node while the other carries traffic. The setup cost is modest — a second appliance, a sync link, and an hour of configuration — but the payoff only materializes if you verify failover behavior before the night you actually need it. Build the pair, fail it over on purpose, and check the state table survival yourself.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.