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 09, 2026
last updated
230
articles · all by Naren
Before you start⏱ 30 min
  • Deep devops experience in a production environment
  • Familiarity with debugging, profiling, and performance analysis
  • Understanding of distributed systems and failure modes
 ● 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.

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 diagram 1 HA Architecture Active-Passive with shared NFS and external DB Active Controller Serving traffic Passive Controller Standby Load Balancer (nginx / HAProxy) ip_hash | health check /login NFS Share JENKINS_HOME, NFS v4 External DB PostgreSQL / MySQL Build Agents SSH / K8s / Docker Failover Procedure Stop → LB switch → Start 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.

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.

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.
jenkins-high-availability diagram 2 Failover Sequence Timeline: primary failure → recovery T+0s: Primary Healthy Active controller serving T+30s: Health Check Fails LB detects 5xx T+60s: Primary Marked Down LB removes from pool T+90s: Standby Takes Over LB routes to standby T+120s: Standby Active Shared JENKINS_HOME mounted T+180s: Primary Repaired New standby role RTO: ~2min | RPO: ~0 (NFS) SLA targets 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.

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.

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.

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.

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.

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.

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.

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.

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.

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.
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

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.

Common mistakes to avoid

6 patterns
×

Using NFS v3 with `nolock` option

Symptom
Corrupted job configurations after failover
Fix
Switch to NFS v4 with lock option and enable advisory locking.
×

Not externalizing the database

Symptom
Metadata loss or corruption on failover
Fix
Migrate to PostgreSQL or MySQL and configure Jenkins to use it.
×

Configuring identical cron triggers on both nodes

Symptom
Duplicate builds triggered after failover
Fix
Disable cron on passive node or use distributed locks.
×

Storing artifacts only on local disk

Symptom
Artifacts missing after failover
Fix
Use shared NFS volume or S3 for artifact storage.
×

Not testing failover regularly

Symptom
Failover fails when needed
Fix
Schedule monthly failover drills and document results.
×

Assuming all plugins are HA-compatible

Symptom
Unexpected errors after failover
Fix
Maintain a list of compatible plugins and test each new plugin in staging.
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 09, 2026
last updated
230
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 / 39 · Jenkins
Next
Jenkins Pipeline Best Practices