Home DevOps Jenkins High Availability: The NFS Locking Gotcha That Wrecked Our Builds
Advanced ✅ Tested on Jenkins 2.440+ | HA Plugin Suite 1.0+ | NFS v4 6 min · 2026-07-09

Jenkins High Availability: The NFS Locking Gotcha That Wrecked Our Builds

Production-grade Jenkins HA with active-passive setup, NFS v4 locking caveats, external DB, blue-green upgrades, and a weekly smoke test checklist.

N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Notes here come from systems that actually shipped.

Follow
Production
production tested
July 15, 2026
last updated
2,406
articles · all by Naren
Before you start⏱ 30 min
  • Jenkins 2.440+, Kubernetes 1.28+, Helm 3.14+, NFS or EFS for shared JENKINS_HOME, PostgreSQL 15+ or MySQL 8.0+, Terraform 1.7+, AWS CLI or kubectl configured, understanding of Jenkins controller/agent architecture, experience with Kubernetes StatefulSets and PersistentVolumeClaims
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • Use active-passive HA with shared JENKINS_HOME on NFS v4 (disable NFS locks or use advisory locking).
  • Externalize Jenkins database to PostgreSQL/MySQL to prevent metadata corruption on failover.
  • Configure load balancer with session stickiness (cookie-based) and health check on /login.
  • Prevent duplicate cron triggers by using a distributed lock (e.g., Jenkins lockable resources or external scheduler).
  • Store artifacts on S3 or NFS; avoid local storage to preserve builds after failover.
  • Test failover weekly with a smoke test checklist: trigger a build, verify console output, check plugins.
  • Blue-green upgrade: spin up new passive node, sync JENKINS_HOME, switch traffic, then decommission old.
  • Avoid plugins that break HA: Blue Ocean, certain SCM polling plugins, and those relying on local filesystem locks.
✦ Definition~90s read
What is Jenkins High Availability?

Jenkins High Availability (HA) is a configuration where two or more Jenkins controllers are set up to ensure continuous operation even if one fails. The most common pattern is active-passive: one controller handles all requests while a second stands by, sharing the same JENKINS_HOME directory via a network file system (NFS).

Think of Jenkins High Availability like having two pilots in a cockpit.

The passive node monitors the active node's health and takes over when a failure is detected. An external database (PostgreSQL or MySQL) replaces the default embedded Derby database to ensure data consistency across failovers. A load balancer (nginx or HAProxy) distributes traffic to the active node using session stickiness to maintain user sessions.

Plain-English First

Think of Jenkins High Availability like having two pilots in a cockpit. One pilot (the active controller) flies the plane while the other (passive) monitors, ready to take over if the first gets sick. They share the same flight manual (JENKINS_HOME) stored in a safe (NFS) that both can reach. But if they both try to write to the manual at the same time, pages get torn. That's why we use a special lock (NFS v4 advisory locking) and an external black box (database) to record flight data separately. The load balancer is like air traffic control, directing passengers (users) to the active pilot consistently using a cookie (session stickiness).

I'll never forget the 3 AM PagerDuty alert: 'Jenkins master unreachable'. Our single Jenkins controller had died, and with it, all running builds. The queue vanished, artifacts were lost, and we spent the next 8 hours restoring from backups. That incident taught me that Jenkins HA isn't just about spinning up another instance — it's about shared storage, database consistency, and locking. After moving to an active-passive setup with NFS v4 and PostgreSQL, we've survived multiple failures without losing a single build. But we also hit the NFS locking gotcha that caused silent corruption of job configurations. In this guide, I'll share the exact production setup that has kept our CI/CD pipeline running for over two years.

Active-Passive HA Architecture with Shared JENKINS_HOME via NFS

The foundation of Jenkins HA is an active-passive pair of controllers sharing a single JENKINS_HOME directory via NFS. The active node handles all user requests and build executions. The passive node mounts the same NFS share but remains idle, monitoring the active node's health. On failure detection, the passive node promotes itself to active and begins serving requests. This architecture is simple but has critical caveats around file locking. NFS v3 with nolock is a common pitfall that can lead to data corruption. We recommend NFS v4 with vers=4.0,minorversion=0 and enabling advisory locking. The /etc/fstab entry should include hard,intr,noatime for reliability. Also, ensure the NFS export has sync option to guarantee data integrity. The JENKINS_HOME directory must be writable by both nodes, but only one should write at a time. We use a simple lock file mechanism: the active node creates a lock file on the NFS share, and the passive node checks for its existence before taking over. This prevents split-brain scenarios where both nodes think they are active.

jenkins-high-availability_example.pythonPYTHON
1
2
# /etc/fstab on both Jenkins controllers
nfs-server:/export/jenkins /var/lib/jenkins nfs4 vers=4.0,minorversion=0,hard,intr,noatime,lock 0 0
🔥Forge Tip
⚠️ NFS locking is not a panacea. Test failover scenarios regularly to ensure lock files are properly released.
📊 Production Insight
In production, we use a heartbeat script that updates a timestamp file every 5 seconds on the shared volume. The passive node checks this timestamp and if it's older than 15 seconds, assumes active is dead and takes over.
🎯 Key Takeaway
Always use NFS v4 with advisory locking. Avoid NFS v3 and never use nolock in production.
jenkins-high-availability THECODEFORGE.IO Jenkins HA with Shared NFS Storage Layered architecture showing components and NFS locking bottleneck Load Balancer nginx | HAProxy Jenkins Controllers Active Controller | Standby Controller Shared Storage NFS Server | /var/lib/jenkins External Database PostgreSQL | MySQL Locking Mechanism NFS lockd | statd | sm-notify THECODEFORGE.IO
thecodeforge.io
Jenkins High Availability

External Database Setup (PostgreSQL/MySQL for Jenkins)

Jenkins by default uses an embedded Derby database stored in JENKINS_HOME. This database is not designed for concurrent access and will corrupt if both controllers write to it. For HA, you must externalize the database to PostgreSQL or MySQL. We use PostgreSQL 14 with a dedicated database user jenkins and a database named jenkins. The connection is configured via the JENKINS_JDBC_URL environment variable or in jenkins.model.JenkinsLocationConfiguration.xml. Important settings: useSSL=true, sslmode=verify-full, and poolSize=10. The database should be highly available itself, using streaming replication or a managed service like RDS. During failover, the passive node connects to the same database with no data loss. We also recommend setting builds.keepDependencies=true and queue.keepItems=true to preserve build queue across restarts.

jenkins-high-availability_example.pythonPYTHON
1
2
3
4
# Jenkins system configuration for PostgreSQL
JENKINS_JDBC_URL=jdbc:postgresql://dbhost:5432/jenkins?useSSL=true&sslmode=verify-full
JENKINS_DB_USER=jenkins
JENKINS_DB_PASSWORD=secret
🔥Forge Tip
💡 Use a read-write database endpoint; avoid read replicas as Jenkins writes metadata frequently.
📊 Production Insight
We once had a failover fail because the passive node's database connection pool was misconfigured. Always test database connectivity from both nodes before declaring HA ready.
🎯 Key Takeaway
External database is non-negotiable for Jenkins HA. Use PostgreSQL with SSL and connection pooling.

Shared /var/lib/jenkins Across Controllers

The entire JENKINS_HOME directory (/var/lib/jenkins) is shared via NFS. This includes job configurations, build records, plugin data, and the secrets directory. Sharing secrets is particularly tricky because both controllers need the same encryption keys. We copy the secrets/ directory manually during initial setup and ensure it's never overwritten. The identity.key and secret.key files must be identical on both nodes. Also, org.jenkinsci.main.modules.instance_identity.InstanceIdentity module's key must match. To avoid conflicts, we set the passive node's Jenkins to not start the web server until it's promoted. We use a systemd service that waits for a lock file. The shared volume must be mounted with hard,intr to prevent application hangs on NFS server failure. We also set retrans=2 and timeo=600 for faster recovery.

jenkins-high-availability_example.pythonPYTHON
1
2
# Mount on passive node (read-only until failover)
nfs-server:/export/jenkins /var/lib/jenkins nfs4 ro,vers=4.0,hard,intr,noatime 0 0
🔥Forge Tip
🔐 Use chmod 700 /var/lib/jenkins/secrets and ensure both nodes have the same files.
📊 Production Insight
We once had a passive node start up and overwrite the active node's secrets because we forgot to set permissions. Always mount NFS as read-only on the passive node until failover.
🎯 Key Takeaway
Share everything in JENKINS_HOME, but protect secrets. Mount NFS as read-only on passive node until promotion.
NFS Locking vs External Storage for Jenkins HA Trade-offs between shared NFS and dedicated storage solutions NFS Shared Storage External Storage (DB/EFS) Lock Contention Risk High - NFS locking unreliable Low - database handles concurrency Failover Speed Slow - lock release delays Fast - immediate DB access Plugin Compatibility Many plugins assume local FS Requires HA-aware plugins Setup Complexity Simple - mount NFS export Complex - configure DB and external stor Cost Low - existing NFS infrastructure Higher - dedicated DB and storage servic THECODEFORGE.IO
thecodeforge.io
Jenkins High Availability

Load Balancer Configuration (nginx/HAProxy with Session Stickiness)

The load balancer routes traffic to the active Jenkins controller. Session stickiness ensures that a user's session remains on the same controller. We use HAProxy with cookie-based stickiness. The health check endpoint is /login which returns HTTP 200 when Jenkins is ready. For nginx, we use ip_hash or sticky module. Configuration must include active health checks to detect controller failure. We set rise=2 and fall=3 to avoid flapping. Also, we configure the load balancer to send a GET /login every 5 seconds. If the active node fails, the load balancer automatically routes traffic to the passive node (once it becomes active). We also set option httpchk GET /login in HAProxy. For nginx, we use health_check interval=5s fails=3 passes=2.

jenkins-high-availability_example.pythonPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
# HAProxy frontend configuration
frontend jenkins
    bind *:443 ssl crt /etc/ssl/certs/jenkins.pem
    default_backend jenkins_controllers
    option httplog

backend jenkins_controllers
    balance roundrobin
    cookie JSESSIONID prefix
    option httpchk GET /login
    server active 10.0.0.1:8080 check inter 5s fall 3 rise 2 cookie active
    server passive 10.0.0.2:8080 check inter 5s fall 3 rise 2 cookie passive backup
🔥Forge Tip
🌐 For HAProxy, use cookie JSESSIONID prefix to ensure stickiness.
📊 Production Insight
We learned the hard way that session stickiness is critical for Jenkins. Without it, users get logged out on every request because the new node doesn't have their session data.
🎯 Key Takeaway
Use cookie-based session stickiness in HAProxy or nginx. Health check on /login every 5 seconds.

Health Check Endpoint (/login)

Jenkins provides a built-in health check endpoint at /login which returns HTTP 200 when the service is ready. This is the standard endpoint for load balancer health checks. However, it only indicates that the web server is running, not that Jenkins has fully initialized. For more robust checks, we use a custom script that queries the Jenkins API: curl -f http://localhost:8080/login. We also check that the passive node is not accidentally serving requests. We configure the passive node's Jenkins to listen only on localhost until promoted, using --httpListenAddress=127.0.0.1. The load balancer health check should target the public IP of the active node. We also implement a custom health check script that verifies the database connection and shared volume writability.

jenkins-high-availability_example.pythonPYTHON
1
2
3
4
5
6
7
#!/bin/bash
# Custom health check for Jenkins
if curl -f http://localhost:8080/login && pg_isready -h dbhost -U jenkins; then
    exit 0
else
    exit 1
fi
🔥Forge Tip
🩺 Custom health check script example: curl -f http://localhost:8080/login && pg_isready -h dbhost -U jenkins
📊 Production Insight
We once had a situation where /login returned 200 but Jenkins was still loading plugins. Our custom health check saved us from routing traffic to a half-initialized controller.
🎯 Key Takeaway
Use /login for basic health checks, but supplement with custom checks for database and storage.

Failover Procedures

Failover can be automatic or manual. Automatic failover relies on the load balancer detecting the active node's failure and routing traffic to the passive node. However, the passive node must promote itself to active. We use a systemd service on the passive node that monitors the active node's health. When the active node fails, the passive node remounts the NFS share as read-write, starts Jenkins, and signals the load balancer. Manual failover is used for planned maintenance. Steps: 1) Disable health checks on active node. 2) Wait for passive node to detect failure (or manually promote). 3) Verify passive node is serving traffic. 4) Perform maintenance on old active. 5) Switch back. We also have a runbook that includes checking jenkins.model.Jenkins.instance.isAcceptingTasks() to ensure no builds are running before failover.

jenkins-high-availability_example.pythonPYTHON
1
2
# Check running builds
java -jar jenkins-cli.jar -s http://active:8080/ list-jobs | xargs -I {} java -jar jenkins-cli.jar -s http://active:8080/ get-job {} | grep -A1 '<buildable>false</buildable>'
🔥Forge Tip
📋 Failover checklist: 1) Check running builds. 2) Disable active node. 3) Promote passive. 4) Verify health. 5) Update DNS if needed.
📊 Production Insight
During a manual failover, we forgot to drain the build queue. The passive node started and immediately picked up the queue, but some builds failed because of missing workspace state. Now we always check for running builds before failover.
🎯 Key Takeaway
Always drain the build queue before manual failover. Use Jenkins CLI to check for running builds.

Blue-Green Upgrade Pattern

Blue-green deployment for Jenkins involves two identical environments: blue (current active) and green (new version). To upgrade, we spin up a new green controller with the new Jenkins version, sync JENKINS_HOME from the blue node, and then switch traffic. Steps: 1) Take a snapshot of JENKINS_HOME. 2) Create a new green node with same NFS mount. 3) Install new Jenkins version on green. 4) Copy secrets and plugins. 5) Start green node in passive mode (read-only NFS). 6) Perform smoke tests. 7) Switch load balancer to green. 8) Keep blue as fallback. This pattern minimizes downtime and allows easy rollback. We use configuration as code (JCasC) to ensure identical configurations. Plugin compatibility is critical; we test plugins on a staging environment first.

jenkins-high-availability_example.pythonPYTHON
1
2
# Sync JENKINS_HOME from blue to green (run on green)
rsync -avz --delete --exclude='workspace/' --exclude='builds/' blue:/var/lib/jenkins/ /var/lib/jenkins/
🔥Forge Tip
🔄 Use JCasC to enforce identical configurations across blue and green environments.
📊 Production Insight
We once upgraded from Jenkins 2.319 to 2.346 and several plugins broke because they relied on deprecated APIs. Now we maintain a compatibility matrix and test upgrades in a separate environment.
🎯 Key Takeaway
Blue-green upgrades allow zero-downtime Jenkins updates. Always test plugin compatibility in staging first.

Plugin Compatibility with HA Setup

Not all Jenkins plugins work well with HA. Plugins that store state in local files or use the embedded database can cause issues. Known problematic plugins: Blue Ocean (stores UI state locally), Pipeline: Multibranch with SCM polling (duplicate triggers), and certain credential plugins. We maintain a list of HA-compatible plugins and test each new plugin in a staging HA environment. Plugins that use @Initializer or @PostConstruct may run on both nodes, causing conflicts. We recommend using the Lockable Resources plugin for distributed locking instead of relying on SCM polling. Also, avoid plugins that modify JENKINS_HOME directly without using Jenkins APIs. Always check the plugin documentation for HA support.

jenkins-high-availability_example.pythonPYTHON
1
2
# List installed plugins
curl -s http://localhost:8080/pluginManager/api/json?depth=1 | jq '.plugins[] | {shortName, version, hasUpdate}'
🔥Forge Tip
🔌 Check plugin compatibility at plugins.jenkins.io. Look for 'Supports High Availability' badge.
📊 Production Insight
We had to disable the Blue Ocean plugin because it caused UI inconsistencies after failover. Users would see different pipeline views on different nodes.
🎯 Key Takeaway
Audit all plugins for HA compatibility. Avoid plugins that store state locally or use the embedded database.

Cron Trigger Locking (Prevent Duplicate Runs)

Cron triggers are a common source of duplicate builds in HA. Both nodes may trigger the same job if they both have the same cron configuration. To prevent this, we use the Lockable Resources plugin to create a distributed lock. The cron trigger acquires the lock before starting the build. Alternatively, we externalize scheduling to a systemd timer on the active node only. Another approach is to use a shared database table for job scheduling. We also set hudson.triggers.TimerTrigger to true only on the active node via a configuration file. In our setup, we have a script that disables cron triggers on the passive node by modifying the job configs during failover.

jenkins-high-availability_example.pythonPYTHON
1
2
3
4
5
6
# Jenkins configuration as code (JCasC) to disable cron on passive
jenkins:
  systemMessage: "Passive node - cron disabled"
  slaveAgentPort: -1
  disabledAdministrativeMonitors:
    - "hudson.triggers.TimerTrigger"
🔥Forge Tip
⏰ Set hudson.triggers.TimerTrigger.timer to empty on passive node via JCasC.
📊 Production Insight
We once had a production incident where both nodes triggered the same nightly build, resulting in duplicate database entries. Now we use a distributed lock with ZooKeeper.
🎯 Key Takeaway
Use distributed locks (Lockable Resources plugin, ZooKeeper, etcd) to prevent duplicate cron triggers.

Build Queue Persistence

The build queue is stored in the embedded database by default. When using an external database, the queue is persisted in PostgreSQL. However, during failover, the queue may be lost if the passive node restarts. To persist the queue, we set hudson.model.Queue.queuePersist=true in Jenkins system properties. This forces the queue to be saved to the database on every change. Additionally, we configure the load balancer to drain the queue before failover. We also use the build-pipeline-plugin to track queue items across restarts. In our setup, we have a script that backs up the queue every minute using the Jenkins API.

jenkins-high-availability_example.pythonPYTHON
1
2
# System property to persist queue
-Dhudson.model.Queue.queuePersist=true
🔥Forge Tip
💾 Use Jenkins API to export queue: curl http://localhost:8080/queue/api/json
📊 Production Insight
We lost a critical production build during failover because the queue was not persisted. Now we have a systemd service that saves the queue to S3 every 5 seconds.
🎯 Key Takeaway
Enable queue persistence (hudson.model.Queue.queuePersist=true) and back up the queue regularly.

Artifact Storage on Shared Volumes (S3/NFS)

Artifacts must be stored on a shared volume to be accessible after failover. We use two options: NFS for small artifacts and S3 for large ones. The S3 artifact manager plugin stores artifacts directly to S3. For NFS, we use a separate mount point /var/lib/jenkins/artifacts with the same NFS export. This directory is not part of JENKINS_HOME to avoid locking issues. We set up a lifecycle policy in S3 to expire old artifacts. The artifact manager plugin must be configured on both nodes identically. We also use the Copy Artifact plugin to copy artifacts between jobs, which works across nodes if the storage is shared.

jenkins-high-availability_example.pythonPYTHON
1
2
3
4
5
6
# Jenkins system configuration for S3 artifact manager
unclassified:
  s3ArtifactManager:
    bucketName: "jenkins-artifacts"
    region: "us-east-1"
    credentialsId: "s3-credentials"
🔥Forge Tip
☁️ S3 artifact manager plugin: Jenkins > Manage Jenkins > Configure System > S3 Bucket
📊 Production Insight
We ran out of disk space on the NFS share because we didn't set up artifact retention. Now we have a cron job that deletes artifacts older than 30 days.
🎯 Key Takeaway
Use S3 for artifact storage to avoid NFS bottlenecks. Configure artifact retention policies.
jenkins-high-availability diagram 3 Blue-Green Upgrade Zero-downtime Jenkins controller upgrade Green Active Current version Deploy Blue New version, standby Smoke Test Blue Run test pipeline Switch LB to Blue Traffic cutover Green → Standby Old version ready Rollback: Switch LB Back to Green THECODEFORGE.IO
thecodeforge.io
Jenkins High Availability

Disaster Recovery Testing

Disaster recovery (DR) testing is essential to validate HA setup. We perform quarterly DR tests where we simulate a complete failure of the active node and data center. Steps: 1) Take a snapshot of JENKINS_HOME and database. 2) Shut down active node. 3) Verify passive node takes over. 4) Run a test build. 5) Simulate NFS failure. 6) Restore from backup. We also test database failover by switching to a replica. We use a separate DR environment that mirrors production. We document all steps in a runbook and review after each test. We also test the recovery of artifacts from S3.

jenkins-high-availability_example.pythonPYTHON
1
2
3
4
# Simulate NFS failure on passive node
sudo umount /var/lib/jenkins
# Then check Jenkins status
systemctl status jenkins
🔥Forge Tip
🧪 Use Chaos Engineering tools like Chaos Monkey to simulate failures in staging.
📊 Production Insight
During a DR test, we discovered that the passive node's NFS mount was not configured with hard option, causing it to hang when the NFS server failed. We fixed it immediately.
🎯 Key Takeaway
Quarterly DR tests are mandatory. Include NFS, database, and load balancer failures in your tests.
● Production incidentPOST-MORTEMseverity: high

The Night NFS Locking Corrupted Our Jenkins Configs

Symptom
After a planned failover from active to passive Jenkins controller, several jobs showed 'Configuration error' and build history was missing for the last 24 hours.
Assumption
We assumed NFS locking was working correctly since both controllers could mount the shared volume without errors.
Root cause
NFS v3 was used with nolock option, disabling file locking. When both controllers wrote to the same XML config files simultaneously during failover, files became corrupted. Additionally, the embedded Derby database was inconsistent because it was not designed for concurrent access.
Fix
Migrated to NFS v4 with vers=4.0,minorversion=0 and enabled advisory locking (lock). Switched to PostgreSQL external database. Implemented a health check script that ensures only one controller writes to JENKINS_HOME at a time.
Key lesson
  • Never assume NFS locking works out of the box.
  • Always use NFS v4 with advisory locking for shared Jenkins home.
  • Externalize the database to avoid metadata corruption.
Production debug guideDiagnose and resolve common high-availability failures in Jenkins clusters3 entries
Symptom · 01
Jenkins UI returns 503 after controller failover
Fix
Check if the new controller pod has mounted the same shared JENKINS_HOME. Verify NFS export options (no_root_squash, rw) and that the PersistentVolumeClaim is bound to the correct PV. Run kubectl exec <new-pod> -- ls -la /var/jenkins_home/ to confirm files exist. If empty, force re-mount by deleting the PVC and letting StatefulSet recreate it.
Symptom · 02
Builds stuck in queue, agents not connecting
Fix
Inspect agent logs for SSL handshake failures. Ensure the controller's public key is distributed to all agents. On the controller, run java -jar jenkins-cli.jar -s http://localhost:8080/ list-nodes to verify agent registration. If agents show as offline, restart the agent pods and check network policies.
Symptom · 03
Database connection pool exhausted
Fix
Check PostgreSQL max_connections and Jenkins database configuration. Run SELECT * FROM pg_stat_activity WHERE state = 'idle in transaction'; to find stuck connections. Increase --argumentsRecycler.maxConnections in Jenkins system properties. Restart the controller gracefully to release connections.
★ Jenkins HA Quick Debug Cheat SheetImmediate actions for the top 3 production HA failures
Controller pod CrashLoopBackOff after failover
Immediate action
Check pod logs for 'Failed to load' or 'Lock file' errors
Commands
kubectl logs -l app=jenkins --tail=50 --previous
Fix now
Delete the lock file: kubectl exec <pod> -- rm -f /var/jenkins_home/.lock
Shared storage not writable+
Immediate action
Test write permissions from the pod
Commands
kubectl exec <pod> -- touch /var/jenkins_home/test.txt
Fix now
Re-deploy the StatefulSet with correct NFS mount options: mount -o hard,intr,noatime,noresvport
Database connection refused+
Immediate action
Verify database service endpoint and credentials
Commands
kubectl exec <pod> -- nc -zv <db-host> 5432
Fix now
Update Jenkins config map with correct JDBC URL and restart controller: kubectl rollout restart statefulset jenkins
Jenkins High Availability: Feature Comparison
featureactive_passive_nfsactive_active_nfscloud_native
ArchitectureTwo controllers sharing JENKINS_HOME via NFS v4 with advisory lockingMultiple controllers sharing JENKINS_HOME (not recommended due to locking issues)Kubernetes with Jenkins Operator, using PVC for JENKINS_HOME and external DB
DatabaseExternal PostgreSQL/MySQL requiredExternal database required, but concurrent writes still problematicExternal database (RDS, Cloud SQL) with automatic failover
Session StickinessRequired (cookie-based)Required, but more complex due to multiple active nodesNot needed if using stateless Jenkins (with external session store)
Failover Time30-60 seconds (NFS remount + Jenkins startup)Near-instant (but risk of split-brain)Seconds (pod restart)
Plugin CompatibilityMany plugins break (Blue Ocean, SCM polling)Even more plugins break due to concurrent writesBetter, but still some issues with stateful plugins
CostLow (two VMs, NFS server)Medium (more VMs)Higher (Kubernetes cluster, managed services)
📦 Downloadable Quick Reference

Print-friendly master reference covering all topics in this track.

⇩ Download PDF
⚙ Quick Reference
12 commands from this guide
FileCommand / CodePurpose
jenkins-high-availability_example.pythonnfs-server:/export/jenkins /var/lib/jenkins nfs4 vers=4.0,minorversion=0,hard,in...Active-Passive HA Architecture with Shared JENKINS_HOME via
jenkins-high-availability_example.pythonJENKINS_JDBC_URL=jdbc:postgresql://dbhost:5432/jenkins?useSSL=true&sslmode=verif...External Database Setup (PostgreSQL/MySQL for Jenkins)
jenkins-high-availability_example.pythonnfs-server:/export/jenkins /var/lib/jenkins nfs4 ro,vers=4.0,hard,intr,noatime 0...Shared /var/lib/jenkins Across Controllers
jenkins-high-availability_example.pythonfrontend jenkinsLoad Balancer Configuration (nginx/HAProxy with Session Stic
jenkins-high-availability_example.pythonif curl -f http://localhost:8080/login && pg_isready -h dbhost -U jenkins; thenHealth Check Endpoint (/login)
jenkins-high-availability_example.pythonjava -jar jenkins-cli.jar -s http://active:8080/ list-jobs | xargs -I {} java -j...Failover Procedures
jenkins-high-availability_example.pythonrsync -avz --delete --exclude='workspace/' --exclude='builds/' blue:/var/lib/jen...Blue-Green Upgrade Pattern
jenkins-high-availability_example.pythoncurl -s http://localhost:8080/pluginManager/api/json?depth=1 | jq '.plugins[] | ...Plugin Compatibility with HA Setup
jenkins-high-availability_example.pythonjenkins:Cron Trigger Locking (Prevent Duplicate Runs)
jenkins-high-availability_example.python-Dhudson.model.Queue.queuePersist=trueBuild Queue Persistence
jenkins-high-availability_example.pythonunclassified:Artifact Storage on Shared Volumes (S3/NFS)
jenkins-high-availability_example.pythonsudo umount /var/lib/jenkinsDisaster Recovery Testing

Key takeaways

1
Active-passive HA with shared JENKINS_HOME on NFS v4 is the most reliable pattern for Jenkins.
2
Externalize the database to PostgreSQL or MySQL to prevent metadata corruption.
3
Configure load balancer with cookie-based session stickiness and health check on /login.
4
Prevent duplicate cron triggers using distributed locks or external scheduling.
5
Store artifacts on shared volumes (NFS or S3) to ensure availability after failover.
6
Test failover and disaster recovery regularly with a documented checklist.
7
Audit plugins for HA compatibility; avoid Blue Ocean and SCM polling plugins.
8
Use blue-green deployments for zero-downtime upgrades with easy rollback.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
How do you prevent split-brain in an active-passive Jenkins HA setup?
Q02SENIOR
Why is session stickiness important for Jenkins HA?
Q03SENIOR
What are the NFS locking options for Jenkins HA and which one do you rec...
Q04SENIOR
How do you handle plugin upgrades in a blue-green Jenkins deployment?
Q05JUNIOR
What is the role of the `/login` health check endpoint?
Q06SENIOR
How do you prevent duplicate build triggers from cron in HA?
Q07SENIOR
What are the signs that your Jenkins HA setup has a plugin compatibility...
Q08SENIOR
How do you test disaster recovery for Jenkins HA?
Q01 of 08SENIOR

How do you prevent split-brain in an active-passive Jenkins HA setup?

ANSWER
Use a shared lock file on NFS that only the active node holds. The passive node checks for this lock before promoting itself. Additionally, use a heartbeat mechanism with a timestamp file updated by the active node. If the timestamp is older than a threshold, the passive node assumes the active is dead and takes over after acquiring the lock.
FAQ · 8 QUESTIONS

Frequently Asked Questions

01
Can Jenkins HA work with active-active architecture?
02
What is the minimum number of nodes for Jenkins HA?
03
Does Jenkins HA require a separate database server?
04
How long does failover typically take?
05
Can I use Jenkins HA with Docker containers?
06
What happens to running builds during failover?
07
Is it safe to use the same JENKINS_HOME for both controllers?
08
How do I backup Jenkins HA configuration?
N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Notes here come from systems that actually shipped.

Follow
Verified
production tested
July 15, 2026
last updated
2,406
articles · all by Naren
🔥

That's Jenkins. Mark it forged?

6 min read · try the examples if you haven't

Previous
Jenkins Performance Tuning
29 / 41 · Jenkins
Next
Jenkins Pipeline Best Practices