Power Your Software Testing with AI Agents and Cloud

The Native AI-Agentic Cloud Platform to Supercharge Quality Engineering. Test Intelligently and Ship Faster.

CI/CDDevOps

Jenkins Best Practices: 11 Rules for Security and Pipelines

Jenkins best practices tested on Jenkins 2.580.1: lock down security, build on agents, keep secrets in credentials, back up master.key, and cap parallel tests.

Last Updated on:

Jenkins best practices come down to three habits: lock down the controller, define every pipeline in source control, and restore your backups at least once before you need them. Each rule below names the setting or pipeline step that enforces it, checked on Jenkins 2.580.1, the current long-term support (LTS) release, with the test stages running on the TestMu AI cloud grid.

A default Jenkins install starts locked, but only because the setup wizard locks it. When I started a fresh 2.580.1 controller with -Djenkins.install.runSetupWizard=false, the system property that skips that wizard, an anonymous request to the script console ran Groovy on the controller.

If Jenkins itself is new to you, read what Jenkins is and work through the Jenkins tutorial first, because the rules here assume a running controller.

Overview

The most important Jenkins best practices are to keep security enabled with matrix-based authorization, run every build on an agent instead of the built-in node, and store secrets in the credentials store. For pipelines, define every job as a declarative Jenkinsfile in source control, and test that your JENKINS_HOME backup actually restores.

Which Jenkins Settings Should You Change First?

  • Matrix-based security: Use matrix-based security instead of "Logged-in users can do anything", which gives every account full control. Matrix-based security grants permissions such as Overall/Read or Job/Build per user or group, and it requires the Matrix Authorization Strategy plugin.
  • Built-in node executors: Set the built-in node to 0 executors so every build runs on an agent. Fresh Jenkins 2.580.1 installs ship with 2 executors on the built-in node, where a build has the same file access as the Jenkins process.
  • Credentials store: Keep every secret in the Jenkins credentials store and bind it only to the step that needs it with withCredentials. Jenkins masks bound values in the console log, which reduces accidental exposure but does not make a secret safe to print.
  • Setup wizard: Never skip the Jenkins setup wizard unless security is configured as code. A Jenkins 2.580.1 controller started with the wizard skipped had no security realm, and an anonymous request to its script console ran Groovy.
  • JENKINS_HOME backups: Back up master.key separately from the rest of JENKINS_HOME, and test every restore. A copy restored without its secrets folder rejected the admin password with HTTP 401.
  • Parallel test limits: Cap concurrent test sessions with the Lockable Resources plugin so parallel stages queue inside Jenkins instead of colliding. With two slots, a third TestMu AI grid session waited about 16 seconds for a free slot.

Jenkins Security Best Practices

I started two clean Jenkins 2.580.1 controllers on Java 21 and probed each one as an anonymous visitor and as an administrator: one launched normally, which shows the setup wizard, and one launched with the wizard skipped. I probed the default start while the wizard waited for its initial admin password, then again after finishing the wizard, and the authorization strategy and executor count did not change.

SettingDefault startWizard skippedSet it to
Security realmJenkins' own user database, with an admin account and a generated 32-character passwordNoneOwn user database, LDAP, or single sign-on
AuthorizationLogged-in users can do anything, with no anonymous readAnyone can do anythingMatrix-based security
Anonymous POST to /scriptTextHTTP 403HTTP 200, Groovy executedAdministrators only
CSRF crumb issuerDefault Crumb IssuerDefault Crumb IssuerLeave it on
Built-in node executors220
Inbound agent TCP portDisabledRandom portDisabled, with agents on WebSocket

1. Keep Security On With Matrix-Based Authorization

Finish the setup wizard on every manual install and configure security as code on every scripted one, because the wizard is what switches security on. The wizard still leaves Logged-in users can do anything in place, which gives every account full control, so switch to Matrix-based security or Project-based Matrix Authorization Strategy from the Matrix Authorization Strategy plugin as soon as a second person signs in.

Jenkins 2.580.1 still offers Anyone can do anything and Legacy mode; use neither outside a throwaway local install. On the demo controller, the Configuration as Code (JCasC) plugin applied the whole security setup from one file at startup and read the admin password from an environment variable, so no secret sits in the YAML:

# jenkins.yaml, part 1 of 3: who can sign in and what they can do
jenkins:
  securityRealm:
    local:
      allowsSignup: false
      users:
        - id: admin
          password: "${JENKINS_ADMIN_PASSWORD}"
  authorizationStrategy:
    globalMatrix:
      entries:
        - user:
            name: admin
            permissions: ["Overall/Administer"]
        - group:
            name: authenticated
            permissions: ["Overall/Read", "Job/Read", "Job/Build"]

Anonymous visitors get no permissions, signed-in users can read and build jobs, and only the admin account administers the controller, as the Security page shows.

Jenkins 2.580.1 Security page using Jenkins' own user database and matrix-based security, where admin administers, Anonymous has no permissions, and authenticated users can read and build jobs

2. Run Builds on Agents, Not the Built-In Node

The Jenkins controller isolation guide states the risk directly: "Any builds running on the built-in node have the same level of access to the controller file system as the Jenkins process."

Both fresh installs above gave the built-in node 2 executors, so any job allowed to run there could read JENKINS_HOME, secrets included. Set the built-in node to 0 executors and attach agents. The second part of the JCasC file does that, disables the inbound TCP port, and connects the agent over WebSocket so no extra port has to be open on the controller:

# jenkins.yaml, part 2 of 3: builds run on agents, never the controller
  numExecutors: 0
  slaveAgentPort: -1
  nodes:
    - permanent:
        name: win-agent
        labelString: selenium
        numExecutors: 3
        remoteFS: "${AGENT_WORKDIR}"
        launcher:
          inbound:
            webSocket: true

The same guide notes that agent-to-controller access control is "always enabled since Jenkins 2.326," so on current releases there is nothing to switch on. On the demo controller, the executor panel lists only the agent:

Jenkins multibranch job selenium-checks with the main branch passing, and a Build Executor Status panel that lists only win-agent with 0 of 3 executors busy

3. Keep Secrets in the Credentials Store

Grid keys, cloud tokens, and signing passwords belong in the Jenkins credentials store, not in the Jenkinsfile or in plain job environment variables. The last part of the JCasC file stores the TestMu AI grid key as a username-and-password credential, again read from the environment at startup, and declares the two grid slots that the pipeline uses later:

# jenkins.yaml, part 3 of 3: one stored secret, two grid slots
credentials:
  system:
    domainCredentials:
      - credentials:
          - usernamePassword:
              scope: GLOBAL
              id: testmu-grid
              username: "${LT_USERNAME}"
              password: "${LT_ACCESS_KEY}"
unclassified:
  lockableResourcesManager:
    declaredResources:
      - name: testmu-slot-1
        labels: testmu-parallel
      - name: testmu-slot-2
        labels: testmu-parallel

Bind the credential only around the step that needs it with withCredentials, as part 2 of the Jenkinsfile below does. During the run, the console printed Masking supported pattern matches of %LT_ACCESS_KEY% for each stage that bound it (the %VAR% form appears because the agent ran on Windows), and the key itself never appeared.

The Jenkins Pipeline credentials documentation says masking "only reduces the risk of accidental exposure" and warns that "Groovy string interpolation should never be used with credentials," so pass secrets to sh inside single-quoted strings and let the shell expand them.

For values that are not secret, set Jenkins pipeline environment variables in an environment block instead.

4. Stay on the LTS Line and Prune Plugins

In January 2024 the Jenkins project rated CVE-2024-23897, an arbitrary file read through the built-in command-line interface that can lead to remote code execution, as critical and fixed it in Jenkins 2.442 and LTS 2.426.3, according to its January 24, 2024 security advisory.

CISA added CVE-2024-23897 to its Known Exploited Vulnerabilities catalog on August 19, 2024, based on evidence of active exploitation.

Upgrades involve the Java runtime too. The Jenkins Java support policy lists Java 21 and Java 25 as the supported versions and 2.541.3 as the final LTS release for Java 17.

Every plugin runs inside the controller, so install fewer and check each one's status before you rely on it. The Jenkins plugins site now labels three plugins that an earlier version of this guide recommended as deprecated: Cobertura, Code Coverage API, and Blue Ocean.

The Cobertura plugin page says all of its functionality has been integrated into the Coverage plugin, which is the maintained replacement.

Note

Note: Run your Jenkins test stages on 3,000+ browser/OS combinations with the TestMu AI cloud grid, and keep the grid key in Jenkins credentials. Start testing for free

Jenkins Pipeline Best Practices

One Multibranch job, selenium-checks, exercised every rule in this section: a 47-line Jenkinsfile that runs a Selenium test on the TestMu AI grid in Chrome, Firefox, and Edge as parallel stages. The test opens the Selenium Playground Simple Form Demo, types a message, and asserts that the page echoes it; this Jenkins and Selenium integration guide covers a first Selenium job in more depth.

For a guided start on jobs and pipelines, the TestMu AI Jenkins tutorial runs just under three hours:

Youtube thumbnail

5. Keep Every Job in a Jenkinsfile in Source Control

A job clicked together in the UI has no version history and skips code review, while a Jenkinsfile in the repository gets both.

The Jenkins best practices guide recommends Organization Folders, with Multibranch Pipelines as the alternative, so that Jenkins creates and deletes jobs as repositories and branches are added or removed.

Pointing a Multibranch job at the demo repository was the only job configuration I did. Branch indexing found the Jenkinsfile on main and scheduled the first build with no further setup; this Jenkins multibranch pipeline guide walks through the configuration screens.

Commit a .gitattributes file containing * text=auto eol=lf when Windows agents check out shell scripts. Git warned that it would convert the demo's script to CRLF because core.autocrlf was on, and in a quick check a CRLF copy of a similar script failed in Git Bash with command not found, because a backslash before a carriage return no longer continues the line.

6. Write Declarative Pipelines and Keep Groovy as Glue

Declarative syntax puts the guard rails where reviewers can see them. Part 1 of the demo Jenkinsfile pins the agent label, keeps the last 30 builds, stops any run after 20 minutes, refuses to run two builds of the same branch at once, and timestamps every log line:

// Jenkinsfile, part 1 of 3: where it runs and the guard rails
pipeline {
  agent { label 'selenium' }
  options {
    buildDiscarder(logRotator(numToKeepStr: '30'))
    timeout(time: 20, unit: 'MINUTES')
    disableConcurrentBuilds()
    timestamps()
  }
  stages {
    stage('Install') {
      steps { sh 'npm ci' }
    }

Keep the Groovy itself thin. The Jenkins Pipeline best practices page warns that "Groovy code always executes on controller," so parsing JSON with JsonSlurper or calling an API with HttpRequest spends controller memory and CPU. Do the work in a shell step on the agent and hand back only the result:

node('selenium') {
  writeFile file: 'package.json', text: '{"version": "2.4.1"}'
  // The agent parses the file; the controller gets one short string
  def version = sh(returnStdout: true,
      script: 'node -p "require(\'./package.json\').version"').trim()
  echo "Testing version ${version}"
}

That job ran in the Groovy sandbox, parsed the file on the agent, and printed Testing version 2.4.1. Use jq instead of node -p on agents that have it, and browse these Jenkins declarative pipeline examples for more stage patterns.

7. Share Pipeline Code Through Small Shared Libraries

When several Jenkinsfiles repeat the same steps, move those steps into a shared library in its own repository and load it with @Library. In 2.580.1, global libraries live under Manage Jenkins > System > Global Trusted Pipeline Libraries, and folder-level libraries suit a single team. Guard the global library repository as closely as the admin account.

The Jenkins shared libraries documentation warns that "anyone able to push commits to this SCM repository could obtain unlimited access to Jenkins," while folder-scoped libraries are always untrusted and run in the Groovy sandbox.

The Pipeline best practices page also says not to override built-in steps such as sh or timeout, to avoid large global variable files that load into every run, and to keep libraries small because each one is checked out before the pipeline starts. This walkthrough of Jenkins shared libraries shows the repository layout.

8. Prevent Collisions Between Parallel Builds

Parallel stages fail in confusing ways when they compete for one database, port, or account. Part 2 of the Jenkinsfile runs one Selenium stage per browser through a matrix and makes each stage take one of two lockable slots before it opens a grid session:

// Jenkinsfile, part 2 of 3: one grid slot per browser, secrets bound late
    stage('Cross-browser') {
      matrix {
        axes {
          axis {
            name 'BROWSER'
            values 'chrome', 'firefox', 'MicrosoftEdge'
          }
        }
        stages {
          stage('Selenium') {
            steps {
              lock(label: 'testmu-parallel', quantity: 1, resource: null) {
                withCredentials([usernamePassword(
                    credentialsId: 'testmu-grid',
                    usernameVariable: 'LT_USERNAME',
                    passwordVariable: 'LT_ACCESS_KEY')]) {
                  sh 'sh ci/run-tests.sh'

The slots are the two testmu-parallel resources declared in part 3 of the JCasC file, so no more than two browser sessions run at once across all jobs that take this lock, however wide the matrix gets. Match the slot count to the parallel sessions on your plan.

In declarative syntax, a label lock also needs resource: null. My first run without it failed before any stage started, with Missing required parameter: "resource" @ line 26.

The declarative example on the Lockable Resources plugin page uses the same form, lock(label: 'printer', quantity: 1, resource : null).

With two slots and three browsers, the third stage waits. These lock lines, filtered from the final run's console, show the Edge stage queuing until Chrome released its slot:

[2026-10-12T08:09:22.869Z] Lock acquired on [Label: testmu-parallel, Quantity: 1]
[2026-10-12T08:09:22.899Z] Lock acquired on [Label: testmu-parallel, Quantity: 1]
[2026-10-12T08:09:22.926Z] Found 0 available resource(s). Waiting for correct amount: 1.
[2026-10-12T08:09:22.927Z] Found 0 available resource(s). Waiting for correct amount: 1.
[2026-10-12T08:09:22.927Z] [Label: testmu-parallel, Quantity: 1] is not free, waiting for execution ...
[2026-10-12T08:09:22.928Z] [Required label: testmu-parallel, Number of resources: 1] added into queue at position 0
[2026-10-12T08:09:38.886Z] ✔ simple form echoes the message on chrome (12514.3656ms)
[2026-10-12T08:09:39.022Z] Lock acquired on [Label: testmu-parallel, Quantity: 1]

The Edge stage got its slot 16.1 seconds after it asked. Across four runs the wait ranged from 15.6 to 18.8 seconds, and all four builds passed. During the wait, the Lockable Resources page showed both slots locked and one build queued:

Jenkins Lockable Resources overview during a run, with both testmu-parallel slots locked, none free, and one build of selenium-checks main waiting in the queue

For whole jobs, the best practices guide recommends the Throttle Concurrent Builds plugin, and the H symbol in cron schedules, such as H 2 * * *, so scheduled jobs spread across the hour instead of all starting at minute 0. The Pipeline best practices page also advises against sharing one workspace between pipeline runs.

9. Run Tests in the Pipeline and Publish the Results

Run the test suite inside the pipeline so that a failure blocks the build. Each matrix stage calls a three-line script that runs Node's built-in test runner with two reporters, one for the console and one that writes a JUnit XML file per browser:

mkdir -p reports
node --test --test-reporter=spec --test-reporter-destination=stdout \
  --test-reporter=junit --test-reporter-destination="reports/$BROWSER.xml"

Part 3 of the Jenkinsfile publishes those files whatever the result, so a failed stage still leaves a report:

// Jenkinsfile, part 3 of 3: publish results and fingerprint them
                }
              }
            }
          }
        }
      }
    }
  }
  post {
    always {
      junit 'reports/*.xml'
      archiveArtifacts artifacts: 'reports/*.xml', fingerprint: true
    }
  }
}

In the final run, the JUnit plugin recorded 3 passing tests, and the TestMu AI grid filed the three sessions (Chrome 154, Firefox 156, and Edge 154 on Windows 11) under a build named jenkins-selenium-checks-main-1, the BUILD_TAG that Jenkins set, so the grid build and the Jenkins build share a name. The test follows the patterns in the TestMu AI Selenium skill: explicit waits, credentials from the environment, and a lambda-status call that marks each session passed or failed.

Jenkins 2.580.1 stages view of the selenium-checks main build, with three parallel matrix branches for the Selenium stage and post actions that archive the JUnit reports and record fingerprints

Those sessions ran on TestMu AI's Automation Cloud, which covers 3,000+ browser/OS combinations. Each session in this run came back with a video and a command log, and the session log API reported that network and console logs need network: true and console: true in LT:Options.

For Freestyle jobs, the TestMu AI Jenkins plugin stores the credentials in the job, starts and stops the tunnel for locally hosted apps, and embeds video, network logs, and screenshots in the Jenkins results.

When a suite outgrows a three-browser matrix, HyperExecute runs it from the same pipeline through a single sh step that calls its CLI. TestMu AI puts it at up to 70% faster than traditional grids, because each test runs on a just-in-time VM alongside its browser instead of crossing a hub; the HyperExecute Jenkins guide lists the steps.

When a build fails, tell the people most likely to fix it: the best practices guide suggests notifying the most recent committers when new test failures appear, instead of messaging the whole team about every red build.

Austin Siewert

Austin Siewert

Co-Founder, Steadfast Systems

Discovered @TestMu AI yesterday. Best browser testing tool I've found for my use case. Great pricing model for the limited testing I do 👏

2M+ Devs and QAs rely on TestMu AI

Deliver immersive digital experiences with Next-Generation Mobile Apps and Cross Browser Testing Cloud

Controller Maintenance Best Practices

These practices protect the controller's own data: the JENKINS_HOME directory and the build history Jenkins keeps for every job.

10. Back Up JENKINS_HOME and Rehearse the Restore

The Jenkins backup guide says to back up master.key separately and store it somewhere more secure, because "If you need to restore a system from a backup, you will need this file," and to validate every backup.

ThinBackup, the maintained scheduled-backup plugin, states that it backs up credentials.xml "but will not back up the server keys," so a restore onto a fresh server sees the credentials but cannot decrypt them unless you add the secrets through its additional-files option.

I checked what a missing secrets folder does by copying the demo controller's JENKINS_HOME twice, once with secrets and once without, and starting each copy on its own port:

Restored copyAdmin sign-inStored grid credentialWhat the log showed
With secretsWorkedDecrypted and matched the original keyA normal startup
Without secretsRejected with HTTP 401Not reachable without a sign-inidentity.key.enc reported as corrupted, and the admin's user folder ignored

Skip workspaces and tool caches, which the backup guide says a clean checkout or a fresh download restores, and keep the JCasC file in Git so the configuration can be rebuilt even if a backup fails its test. Avoid the original Backup plugin: the Jenkins plugins site lists it as up for adoption, with no release since 2011.

11. Fingerprint Artifacts and Discard Old Builds

Fingerprints let Jenkins trace which build produced a file and which builds used it later. The archiveArtifacts step in part 3 of the Jenkinsfile records an MD5 fingerprint for each JUnit report, and the console confirms it with Recording fingerprints.

Old builds consume controller resources, and the Pipeline best practices page lists cleaning them up as a controller-health rule. The buildDiscarder(logRotator(numToKeepStr: '30')) option in part 1 keeps the last 30 builds of each branch and deletes older ones automatically.

Conclusion

Change two defaults first: set the built-in node to 0 executors, replace "Logged-in users can do anything" with matrix-based authorization, and commit both settings to a JCasC file. Next, move your grid key into the credentials store and point the test stage at the TestMu AI cloud grid with the Jenkins pipeline integration guide.

Author

...

Himanshu Sheth

Blogs: 142

  • Twitter
  • Linkedin

Himanshu Sheth is the Director of Marketing (Technical Content) at TestMu AI, with over 8 years of hands-on experience in Selenium, Cypress, and other test automation frameworks. He has authored more than 130 technical blogs for TestMu AI, covering software testing, automation strategy, and CI/CD. At TestMu AI, he leads the technical content efforts across blogs, YouTube, and social media, while closely collaborating with contributors to enhance content quality and product feedback loops. He has done his graduation with a B.E. in Computer Engineering from Mumbai University. Before TestMu AI, Himanshu led engineering teams in embedded software domains at companies like Samsung Research, Motorola, and NXP Semiconductors. He is a core member of DZone and has been a speaker at several unconferences focused on technical writing and software quality.

Reviewer

...

Japneet Singh Chawla

Reviewer

  • Linkedin

Japneet Singh Chawla is an Engineering Manager at TestMu AI, where he leads a team driving HyperExecute, the AI-native Test Orchestration Cloud Platform, and integrations with Cypress, Provar, Tosca, and Selenium, improving test execution efficiency and driving adoption across 500+ enterprise clients. He also spearheaded zero-downtime deployments that cut release-related downtime by 90%, and mentors new engineers into productive contributors. He brings 9+ years of experience building and scaling distributed systems, SaaS platforms, and developer tools, with deep hands-on backend engineering across Golang, Python, Node.js, Kafka, and Redis. Earlier at Sumo Logic he built award-winning developer tools, including a VS Code Parser Linter, and at Indus Valley Partners he was a founding member of the Sentiment Analyzer team, building ML-powered solutions for financial clients. Japneet holds an MCA in Computer Science from GGSIPU.

Add to Google preferred sources

Summarise with AI

Copied to Clipboard!
...

3000+ Browsers. One Platform.

See exactly how your site performs everywhere.

Try it free
...

Write Tests in Plain English with KaneAI

Create, debug, and evolve tests using natural language.

Try for free

Jenkins Best Practices FAQs

Did you find this page helpful?

More Related Blogs

TestMu AI forEnterprise

Get access to solutions built on Enterprise
grade security, privacy, & compliance

  • Advanced access controls
  • Advanced data retention rules
  • Advanced Local Testing
  • Premium Support options
  • Early access to beta features
  • Private Slack Channel
  • Unlimited Manual Accessibility DevTools Tests