Build a highly available Linux cluster with Pacemaker and Corosync

Advanced 75 min Nov 07, 2026 11 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Build a two-node active-passive cluster with Pacemaker, Corosync and pcs, including STONITH fencing, resource constraints and automatic failover of a floating virtual IP.

Prerequisites

  • At least two Linux nodes with root or sudo access
  • A dedicated network path between nodes for cluster communication
  • A working fencing mechanism such as IPMI, iLO, or a cloud fencing agent
  • Synchronized system clocks across all nodes
  • Basic familiarity with systemd and Linux networking

What this solves

Pacemaker and Corosync form the standard open source stack for building highly available Linux clusters. This tutorial builds a two-node active-passive cluster with quorum, STONITH fencing and a floating virtual IP, then walks through testing failover and troubleshooting the most common quorum and fencing failures.

This is an advanced setup. You need at least two nodes with a dedicated network for cluster communication, ideally a third quorum device or a third node, and a fencing mechanism such as IPMI, a cloud API agent or a shared power switch.

Warning: running a cluster without working STONITH fencing is worse than running no cluster at all. A split-brain scenario where both nodes believe they are active can corrupt shared data. Do not skip the fencing section.

Step-by-step installation

Prepare both nodes

Set a unique hostname on each node and make sure both can resolve each other by name. Add entries to /etc/hosts on both nodes.

203.0.113.10 node1 node1.example.com
203.0.113.11 node2 node2.example.com

Confirm time is in sync between nodes, since clock drift causes false quorum timeouts. See configuring NTP with chrony if your nodes are not already synced.

Install Corosync, Pacemaker and pcs

Install the cluster stack on both nodes. Ubuntu and Debian ship pcs in the universe/backports repos; AlmaLinux and Rocky include it in the HighAvailability repo, which must be enabled first.

sudo apt update
sudo apt install -y pacemaker corosync pcs fence-agents resource-agents
sudo dnf install -y epel-release
sudo dnf config-manager --set-enabled highavailability
sudo dnf install -y pacemaker corosync pcs fence-agents-all resource-agents

Run these commands on both nodes.

Set the cluster user password and enable pcsd

pcs uses a system account called hacluster to authenticate between nodes. Set a strong password for it on both nodes, then enable the pcsd daemon.

sudo passwd hacluster
sudo systemctl enable --now pcsd

Use a strong, unique password here, not something guessable. It is only used for inter-node cluster authentication, never for interactive login.

Open the required firewall ports

Corosync uses UDP 5405 and pcsd uses TCP 2224. Open these explicitly rather than disabling the firewall.

sudo ufw allow 2224/tcp
sudo ufw allow 5405/udp
sudo ufw allow 3121/tcp
sudo ufw allow 21064/tcp
sudo firewall-cmd --permanent --add-service=high-availability
sudo firewall-cmd --reload

Authenticate the nodes and create the cluster

Run this from one node only. It authenticates both hosts using the hacluster credentials and generates the Corosync configuration automatically.

sudo pcs host auth node1 node2 -u hacluster -p 'YourStrongClusterPass94!'
sudo pcs cluster setup mycluster node1 node2 --force
sudo pcs cluster start --all
sudo pcs cluster enable --all

Review and tune corosync.conf

pcs generates /etc/corosync/corosync.conf automatically, but understanding it matters for troubleshooting. Each node is a ring member and quorum is calculated from the total number of configured votes.

totem {
    version: 2
    cluster_name: mycluster
    transport: knet
    crypto_cipher: aes256
    crypto_hash: sha256
}

nodelist {
    node {
        ring0_addr: 203.0.113.10
        name: node1
        nodeid: 1
    }
    node {
        ring0_addr: 203.0.113.11
        name: node2
        nodeid: 2
    }
}

quorum {
    provider: corosync_votequorum
    two_node: 1
}

logging {
    to_logfile: yes
    logfile: /var/log/cluster/corosync.log
    to_syslog: yes
}

The two_node: 1 setting is critical in a two-node cluster. It tells Corosync to allow quorum with only one node reachable, since a strict majority of two nodes requires both to be online, which defeats the purpose of HA. In production with three or more nodes, remove this setting and let standard majority quorum apply.

Note: for a two-node cluster, add a third lightweight quorum device (qdevice) if you want proper split-brain protection instead of relying on the two_node override. This is beyond this tutorial's scope but worth planning for before go-live.

Disable the default STONITH-less behavior and configure fencing

Pacemaker refuses to run resources safely without a working fencing device. Never set stonith-enabled=false in production. Configure a real fencing agent instead, for example IPMI based fencing (fence_ipmilan) for bare metal, or a cloud-specific agent for virtualized environments.

sudo pcs stonith create node1-fence fence_ipmilan \
  pcmk_host_list="node1" \
  ipaddr="203.0.113.50" \
  login="admin" \
  passwd="StrongIpmiPass77!" \
  lanplus=1 \
  op monitor interval=60s

sudo pcs stonith create node2-fence fence_ipmilan \
  pcmk_host_list="node2" \
  ipaddr="203.0.113.51" \
  login="admin" \
  passwd="StrongIpmiPass77!" \
  lanplus=1 \
  op monitor interval=60s

Each fencing device must target the opposite node, not itself. node1-fence powers off node2's IPMI interface and vice versa, so a healthy node can forcibly remove a failed peer from the cluster.

Never disable STONITH to make a cluster work. If pcs reports resources will not start because stonith is misconfigured, fix the fencing device, do not run pcs property set stonith-enabled=false in production.

Create a floating virtual IP resource

This IP address moves to whichever node is currently active, giving clients a stable endpoint regardless of which physical node is serving traffic.

sudo pcs resource create cluster_vip ocf:heartbeat:IPaddr2 \
  ip=203.0.113.100 cidr_netmask=24 \
  op monitor interval=10s

Add a managed service and group it with the VIP

Assume you are protecting an Nginx instance. Install it on both nodes first, then wrap it in a Pacemaker resource so the cluster controls start and stop instead of systemd directly.

sudo pcs resource create web_server ocf:heartbeat:nginx \
  configfile=/etc/nginx/nginx.conf \
  op monitor interval=15s

sudo pcs resource group add web_group cluster_vip web_server

Grouping resources ensures they always start, stop and move together in the order listed. For more granular control than groups allow, use explicit constraints as shown next.

If you need a reverse proxy in front of this stack, see configuring an NGINX reverse proxy for the base config this resource expects.

Configure colocation and ordering constraints

Groups cover simple cases, but explicit constraints give finer control for more complex topologies, such as when a database must start before an application server on the same node.

sudo pcs constraint colocation add web_server with cluster_vip INFINITY
sudo pcs constraint order cluster_vip then web_server

The colocation constraint forces web_server to always run on the same node as cluster_vip. The order constraint ensures the VIP comes up first, so the service is reachable the moment it starts. You can also set location preferences to favor a specific node during normal operation.

sudo pcs constraint location web_group prefers node1=100

Review the full cluster configuration

Before testing failover, review the complete resource and constraint set to catch mistakes early.

sudo pcs config show

Verify your setup

Check overall cluster status, quorum state and resource placement.

sudo pcs status
sudo corosync-quorumtool -s
sudo pcs resource status

Confirm the virtual IP is reachable and bound to the active node.

ip addr show | grep 203.0.113.100
curl -I http://203.0.113.100

Test failover by moving the active node out of the cluster and watching resources migrate.

sudo pcs node standby node1
sudo pcs status
sudo pcs node unstandby node1

During this test, the VIP and web_server should relocate to node2 within a few seconds, and curl against 203.0.113.100 should keep responding throughout. To simulate a hard failure instead of a graceful standby, power off node1 entirely and confirm node2's fencing agent successfully fences it before taking over resources.

Common issues

SymptomCauseFix
pcs status shows "partition WITHOUT quorum"Fewer than required votes are reachable, often from a network split or a node downCheck connectivity with corosync-cfgtool -s and confirm two_node or a qdevice is correctly configured
Resources stuck in "Stopped" with stonith errorsNo working fencing device configured, or fencing agent cannot reach the target's management interfaceRun sudo pcs stonith status and test manually with sudo stonith_admin --reboot node2
Cluster forms but VIP never comes upIPaddr2 resource cidr_netmask or interface mismatchCheck sudo pcs resource debug-start cluster_vip for detailed errors
Node rejoins but resources do not fail backNo location preference set, or stickiness keeps resources on the current nodeSet sudo pcs resource defaults resource-stickiness=0 or adjust location constraints explicitly
Fencing loops: nodes repeatedly reboot each otherFencing device misconfigured to target the wrong host, or network partition causing both nodes to fence each otherVerify each stonith resource's pcmk_host_list matches the correct target and add a fencing delay with pcmk_delay_max
pcs cluster auth fails between nodesFirewall blocking TCP 2224, or hacluster password mismatchRe-run sudo passwd hacluster on both nodes with the same value and confirm port 2224 is open

Next steps

Running this in production?

Want this handled for you? Running this at scale adds a second layer of work: fencing drills, quorum device placement, failover testing after every patch cycle, and being on-call when a node actually dies at 3am. Talk to an engineer if you'd rather hand the ops side off.

Automated install script

Run this to automate the entire setup

#pacemaker #corosync #high availability #stonith #fencing #pcs #cluster #failover #linux cluster

Don't want to manage this yourself?

We handle infrastructure for businesses that depend on uptime. Fully managed, with one fixed contact who knows your setup.

You get one fixed contact who knows your setup

Rotterdam 17:48 · reachable in a message, no ticket form

Learn more: managed cloud infrastructure