Jenkins Installation & Configuration: From Zero to First Pipeline
Getting Jenkins running is the easy part. Getting it running well -- secured, backed up, and ready for teams -- takes a bit more thought. This guide walks through both: the quick install and the production-grade setup you actually want. By the end, you will have a fully functional Jenkins instance with proper security, essential plugins, your first pipeline job, and a Configuration as Code setup you can version-control alongside your application code.
System Requirements and Capacity Planning
Before you install anything, make sure your machine can handle it. Jenkins is a Java application, and it can be surprisingly hungry -- especially once you start running builds on the controller (which, as we will discuss, you should avoid in production).
| Resource | Minimum (Single User) | Recommended (Small Team) | Production (Large Team) |
|---|---|---|---|
| CPU | 1 core | 4+ cores | 8+ cores |
| RAM | 256 MB | 4 GB | 8-16 GB |
| Disk | 1 GB | 50 GB SSD | 200+ GB SSD |
| Java | JDK 17 | JDK 17 or 21 | JDK 21 |
| OS | Any with JDK support | Ubuntu 22.04/24.04 LTS | Ubuntu 24.04 LTS or RHEL 9 |
| Network | Any | 100 Mbps | 1 Gbps+ |
A few notes on capacity:
Disk space grows faster than you expect. Every build produces logs, artifacts, and workspace files. A busy Jenkins instance with 50 active jobs can easily consume 100 GB in a few months. Set up log rotation from day one and configure build discarders to automatically delete old builds.
RAM matters more than CPU for the controller. Jenkins holds a lot of state in memory -- job configurations, build history, plugin data. If the controller starts swapping, everything slows down. Allocate at least 2 GB of heap space for a team instance.
The controller should not run builds itself in production. Keep the controller lightweight and offload builds to agents. We will cover Docker-based agents in a separate article. For now, understand that setting the number of executors on the controller to zero is a best practice, not a suggestion.
Choosing Your Java Version
Jenkins officially supports JDK 17 and JDK 21. Here is how to decide:
| JDK Version | Status | Recommendation |
|---|---|---|
| JDK 11 | Deprecated since Jenkins 2.357 | Do not use for new installations |
| JDK 17 | Fully supported, widely tested | Safe default for most environments |
| JDK 21 | Fully supported since Jenkins 2.426 | Preferred for new installations |
Stick with the LTS (Long-Term Support) builds from Eclipse Temurin or the distribution packages from your OS vendor. Avoid mixing JDK vendors between the controller and agents, as subtle differences in class loading and garbage collection behavior can cause hard-to-diagnose issues.
Installation on Ubuntu via APT
This is the most common approach for dedicated Jenkins servers. The APT installation gives you a systemd service, automatic startup on boot, and straightforward upgrade paths through the package manager.
Step 1: Install Java
# Update the package index
sudo apt update
# Install Java 17 (or 21 if you prefer)
sudo apt install -y fontconfig openjdk-17-jre
# Verify the installation
java -version
You should see output similar to:
openjdk version "17.0.10" 2024-01-16
OpenJDK Runtime Environment (build 17.0.10+7-Ubuntu-122.04.1)
OpenJDK 64-Bit Server VM (build 17.0.10+7-Ubuntu-122.04.1, mixed mode, sharing)
Step 2: Add the Jenkins Repository
# Download and install the Jenkins repository key
curl -fsSL https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key | sudo tee \
/usr/share/keyrings/jenkins-keyring.asc > /dev/null
# Add the Jenkins apt repository
echo "deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc] \
https://pkg.jenkins.io/debian-stable binary/" | sudo tee \
/etc/apt/sources.list.d/jenkins.list > /dev/null
Step 3: Install and Start Jenkins
# Update the package index to include the Jenkins repo
sudo apt update
# Install Jenkins
sudo apt install -y jenkins
# Enable Jenkins to start on boot and start it now
sudo systemctl enable jenkins
sudo systemctl start jenkins
Step 4: Verify the Installation
Jenkins runs on port 8080 by default. Verify it is alive:
# Check the service status
sudo systemctl status jenkins
# Verify the HTTP endpoint is responding
curl -s -o /dev/null -w "%{http_code}" http://localhost:8080
# Should return 200 (or 403 before initial setup)
If Jenkins fails to start, check the logs:
# View Jenkins service logs
sudo journalctl -u jenkins -f
# Or read the log file directly
sudo tail -100 /var/log/jenkins/jenkins.log
Changing the Default Port
To change the port, edit the systemd service file:
sudo systemctl edit jenkins
Add the following override:
[Service]
Environment="JENKINS_PORT=9090"
Then reload and restart:
sudo systemctl daemon-reload
sudo systemctl restart jenkins
Configuring JVM Options for APT Installations
For production, you want to tune the JVM. Edit the Jenkins defaults file:
sudo nano /etc/default/jenkins
Or use a systemd override:
sudo systemctl edit jenkins
Add these JVM options:
[Service]
Environment="JAVA_OPTS=-Xmx4g -Xms2g -XX:+UseG1GC -XX:+AlwaysPreTouch -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/jenkins/heapdump.hprof -Djava.awt.headless=true"
| JVM Flag | Purpose |
|---|---|
-Xmx4g | Maximum heap size (adjust based on available RAM) |
-Xms2g | Initial heap size (set equal to or half of Xmx) |
-XX:+UseG1GC | Use the G1 garbage collector (best for Jenkins workloads) |
-XX:+AlwaysPreTouch | Pre-allocate heap pages at startup for more predictable performance |
-XX:+HeapDumpOnOutOfMemoryError | Dump heap on OOM for post-mortem analysis |
-Djava.awt.headless=true | Disable GUI components (Jenkins is headless) |
Installation with Docker
Docker gives you a reproducible Jenkins setup you can tear down and rebuild in seconds. This is the preferred approach for most environments because it decouples Jenkins from the host OS and makes upgrades trivial.
Quick Start with Docker Run
docker run -d \
--name jenkins \
--restart unless-stopped \
-p 8080:8080 \
-p 50000:50000 \
-v jenkins_home:/var/jenkins_home \
jenkins/jenkins:lts-jdk17
Port 50000 is for agent connections via the JNLP protocol. The named volume jenkins_home persists all configuration, jobs, and plugins across container restarts. Even if you destroy and recreate the container, your data survives in the volume.
Production Setup with Docker Compose
For a more production-ready setup, use Docker Compose:
# docker-compose.yml
version: "3.9"
services:
jenkins:
image: jenkins/jenkins:lts-jdk17
container_name: jenkins
restart: unless-stopped
ports:
- "8080:8080"
- "50000:50000"
volumes:
- jenkins_home:/var/jenkins_home
- /var/run/docker.sock:/var/run/docker.sock
environment:
- JAVA_OPTS=-Xmx4g -Xms2g -XX:+UseG1GC -Djava.awt.headless=true
user: root # Needed for Docker socket access
volumes:
jenkins_home:
Running as root inside the container is a pragmatic choice when you need Docker socket access. For tighter security, create a custom image that adds the jenkins user to the docker group:
FROM jenkins/jenkins:lts-jdk17
USER root
# Install Docker CLI (not the daemon -- we use the host's daemon via socket)
RUN apt-get update && \
apt-get install -y docker.io && \
rm -rf /var/lib/apt/lists/*
# Add jenkins user to docker group
RUN usermod -aG docker jenkins
USER jenkins
Docker Compose with Nginx Reverse Proxy
In production, you almost always want Jenkins behind a reverse proxy with TLS:
# docker-compose.yml
version: "3.9"
services:
jenkins:
image: jenkins/jenkins:lts-jdk17
container_name: jenkins
restart: unless-stopped
expose:
- "8080"
- "50000"
volumes:
- jenkins_home:/var/jenkins_home
environment:
- JAVA_OPTS=-Xmx4g -Xms2g -Djava.awt.headless=true
- JENKINS_OPTS=--prefix=/
nginx:
image: nginx:alpine
container_name: jenkins-nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- ./certs:/etc/nginx/certs
depends_on:
- jenkins
volumes:
jenkins_home:
The corresponding Nginx configuration:
upstream jenkins {
keepalive 32;
server jenkins:8080;
}
server {
listen 80;
server_name jenkins.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name jenkins.example.com;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
location / {
proxy_pass http://jenkins;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 90;
# Required for WebSocket support (Jenkins CLI, etc.)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Initial Setup Wizard
When you first open Jenkins at http://your-server:8080, you will see the unlock screen. Grab the initial admin password:
# APT install
sudo cat /var/lib/jenkins/secrets/initialAdminPassword
# Docker install
docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
Paste it in and you will be asked to install plugins. Choose Install suggested plugins to start. You can always add or remove plugins later.
Create your first admin user when prompted. Do not skip this and keep using the initial password. The initial password is stored in plaintext on disk and is meant to be temporary.
After setup, configure the Jenkins URL under Manage Jenkins, then System, then Jenkins Location. Set it to the actual URL users will use (for example, https://jenkins.example.com/). This affects links in email notifications, webhook callbacks, and API responses.
Security Configuration
An unsecured Jenkins is an open door to your entire infrastructure. Jenkins builds typically have access to source code repositories, deployment credentials, production servers, and cloud provider APIs. Lock it down immediately after installation.
Enable CSRF Protection
Jenkins enables CSRF protection by default since version 2.222. Verify it is on:
Navigate to Manage Jenkins, then Security, then CSRF Protection. Ensure "Prevent Cross Site Request Forgery exploits" is checked and the crumb issuer is set to Default Crumb Issuer.
If you have API clients or webhook integrations that need to make POST requests, they must include the crumb token. Fetch it from:
# Get a crumb for API requests
CRUMB=$(curl -s -u admin:api-token \
'http://localhost:8080/crumbIssuer/api/json' | \
jq -r '.crumb')
# Use the crumb in subsequent requests
curl -X POST -u admin:api-token \
-H "Jenkins-Crumb: $CRUMB" \
'http://localhost:8080/job/my-job/build'
Configure Authentication
Go to Manage Jenkins, then Security and configure the security realm and authorization strategy:
Security Realm options:
| Realm | Best For | Notes |
|---|---|---|
| Jenkins' own user database | Small teams (under 20 users) | Simple, no external dependencies |
| LDAP | Enterprise environments | Integrates with Active Directory |
| SAML 2.0 | SSO with Okta, Azure AD, etc. | Requires the SAML plugin |
| GitHub Authentication | GitHub-centric teams | Uses GitHub OAuth, maps orgs to roles |
| Google Login | Google Workspace orgs | Quick SSO setup |
Authorization Strategy options:
| Strategy | Description | Recommended? |
|---|---|---|
| Anyone can do anything | No restrictions at all | Never use in production |
| Logged-in users can do anything | All authenticated users are admins | Avoid -- too permissive |
| Matrix-based security | Granular per-user/group permissions | Yes, for most teams |
| Project-based Matrix | Per-project permission overrides | Yes, for multi-team setups |
| Role-Based Strategy | Role-based access control (RBAC) | Yes, with the Role Strategy plugin |
A practical matrix setup:
| User/Group | Overall Read | Job Build | Job Configure | Job Create | Administer |
|---|---|---|---|---|---|
| admin | Yes | Yes | Yes | Yes | Yes |
| lead-devs | Yes | Yes | Yes | Yes | No |
| developers | Yes | Yes | No | No | No |
| viewers | Yes | No | No | No | No |
| Anonymous | No | No | No | No | No |
Agent-to-Controller Security
Under Manage Jenkins, then Security, set the agent-to-controller access control to Enabled. This prevents rogue agents from accessing files on the controller.
Without this setting, a compromised build agent could read credentials, modify job configurations, or install malicious plugins on the controller. This is one of the most overlooked security settings.
Disable CLI Over Remoting
The Jenkins CLI over remoting protocol has been a source of vulnerabilities. Disable it under Manage Jenkins, then Security by unchecking "Enable CLI over Remoting."
Use the SSH CLI or HTTP API instead for programmatic access.
Additional Security Hardening
// Script Console (Manage Jenkins -> Script Console)
// Disable the Script Console in production -- it allows arbitrary code execution
// If you must keep it, restrict access to administrators only
// Check for known security issues
println Jenkins.instance.pluginManager.plugins.collect {
"${it.shortName}: ${it.version}"
}.join('\n')
Content Security Policy: Jenkins sets a strict CSP by default. If you need to relax it for HTML reports:
# Add to JAVA_OPTS (be specific about what you allow)
-Dhudson.model.DirectoryBrowserSupport.CSP="sandbox allow-scripts; default-src 'self'; style-src 'self' 'unsafe-inline';"
API Token Best Practices:
- Use API tokens instead of passwords for automation
- Set expiration dates on tokens
- Create separate tokens for each integration
- Revoke tokens immediately when an integration is decommissioned
Plugin Management
Plugins are Jenkins' greatest strength and its biggest maintenance burden. Jenkins has over 1,800 plugins, and it is tempting to install dozens of them. Be deliberate about what you install -- every plugin is a potential security vulnerability, a source of compatibility issues, and something you need to keep updated.
Essential Plugins
These are the plugins you should install on every Jenkins instance:
| Plugin | Artifact ID | Purpose |
|---|---|---|
| Pipeline | workflow-aggregator | Declarative and scripted pipeline support |
| Git | git | Git SCM integration |
| Docker Pipeline | docker-workflow | Run stages in Docker containers |
| Credentials Binding | credentials-binding | Inject credentials into builds |
| Blue Ocean | blueocean | Modern pipeline visualization UI |
| Job DSL | job-dsl | Programmatic job creation |
| Configuration as Code | configuration-as-code | JCasC -- configure Jenkins via YAML |
| Timestamper | timestamper | Add timestamps to console output |
| Workspace Cleanup | ws-cleanup | Clean workspace before/after builds |
| Warnings Next Generation | warnings-ng | Static analysis result aggregation |
| Pipeline Utility Steps | pipeline-utility-steps | readJSON, readYaml, zip, unzip helpers |
| SSH Agent | ssh-agent | Forward SSH keys into build steps |
Plugins to Avoid
| Plugin | Reason |
|---|---|
| Swarm Plugin (client-side) | Deprecated in favor of inbound agents |
| Jenkins CLI over Remoting | Security risk, use SSH or HTTP API |
| Any plugin not updated in 12+ months | Likely unmaintained, potential vulnerabilities |
Managing Plugins via CLI
You can install plugins without the UI using the Jenkins CLI jar:
# Download the CLI
wget http://localhost:8080/jnlpJars/jenkins-cli.jar
# Install plugins
java -jar jenkins-cli.jar -s http://localhost:8080/ \
-auth admin:your-api-token \
install-plugin pipeline-stage-view docker-workflow git \
-restart
Baking Plugins into a Docker Image
When using Docker, bake plugins into your image for reproducible deployments:
FROM jenkins/jenkins:lts-jdk17
# Skip the setup wizard since we are pre-configuring
ENV JAVA_OPTS="-Djenkins.install.runSetupWizard=false"
# Install plugins from a list file
COPY plugins.txt /usr/share/jenkins/ref/plugins.txt
RUN jenkins-plugin-cli --plugin-file /usr/share/jenkins/ref/plugins.txt
Where plugins.txt contains one plugin per line with pinned versions:
workflow-aggregator:596.v8c21c963d92d
docker-workflow:572.v950f58993843
git:5.2.2
configuration-as-code:1810.v9b_c30a_249a_4c
credentials-binding:677.vdc9c38cb_15e0
blueocean:1.27.14
job-dsl:1.87
timestamper:1.27
ws-cleanup:0.45
warnings-ng:11.0.0
Pin versions to ensure reproducible builds. Update versions deliberately, not automatically.
Checking for Plugin Updates and Vulnerabilities
# List all installed plugins and their versions
java -jar jenkins-cli.jar -s http://localhost:8080/ \
-auth admin:token \
list-plugins
# Check for available updates
java -jar jenkins-cli.jar -s http://localhost:8080/ \
-auth admin:token \
list-plugins | grep -v "(.* => "
Jenkins also publishes a security advisory feed. Subscribe to the Jenkins security mailing list and check the update center regularly.
Creating a Freestyle Job
Freestyle jobs are the simplest job type. They are configured entirely through the UI and are good for one-off tasks, simple build-and-test flows, or quick prototypes. They are not version-controlled though, which is why you should migrate to pipeline jobs for anything real.
Step-by-Step Walkthrough
- Click New Item on the Jenkins dashboard
- Enter a name (use lowercase with hyphens:
my-app-build) - Select Freestyle project and click OK
- Under Source Code Management, select Git and enter your repo URL
- Add credentials if the repo is private
- Under Build Triggers, check "Poll SCM" with schedule
H/5 * * * *(every 5 minutes with hash-based jitter) - Under Build Environment, check "Delete workspace before build starts" for clean builds
- Under Build Steps, add "Execute shell":
#!/bin/bash
set -euo pipefail
echo "=== Environment ==="
node --version
npm --version
echo "=== Install ==="
npm ci
echo "=== Lint ==="
npm run lint
echo "=== Test ==="
npm test -- --ci --reporters=default --reporters=jest-junit
echo "=== Build ==="
npm run build
echo "=== Build Complete ==="
ls -la dist/
- Under Post-build Actions, add "Archive the artifacts" with pattern
dist/**/* - Add "Publish JUnit test result report" with pattern
junit.xml - Save and click Build Now
Freestyle jobs work, but they are not version-controlled and not reproducible. For anything beyond toy projects, use Pipeline jobs.
Creating a Pipeline Job
Pipeline jobs are defined by a Jenkinsfile stored in your repository. This is the approach you should use for all real projects. The Jenkinsfile is version-controlled, code-reviewed, and travels with your application.
Setting Up the Pipeline Job
- Click New Item, enter a name, select Pipeline
- Under Pipeline, select "Pipeline script from SCM"
- Choose Git, enter your repository URL, set the branch to
*/main - Set the Script Path to
Jenkinsfile(default) - Optionally check "Lightweight checkout" for faster SCM polling
- Save
Your First Jenkinsfile
Create a Jenkinsfile in your repo root:
pipeline {
agent any
options {
timeout(time: 20, unit: 'MINUTES')
timestamps()
buildDiscarder(logRotator(numToKeepStr: '20'))
disableConcurrentBuilds()
}
environment {
NODE_ENV = 'ci'
CI = 'true'
}
stages {
stage('Checkout') {
steps {
checkout scm
sh 'git log --oneline -5'
}
}
stage('Install') {
steps {
sh 'npm ci'
}
}
stage('Lint') {
steps {
sh 'npm run lint'
}
}
stage('Test') {
steps {
sh 'npm test -- --ci --coverage'
}
post {
always {
junit allowEmptyResults: true, testResults: '**/junit.xml'
}
}
}
stage('Build') {
steps {
sh 'npm run build'
}
}
}
post {
success {
archiveArtifacts artifacts: 'dist/**/*', fingerprint: true
echo 'Build succeeded.'
}
failure {
echo 'Build failed -- check the logs above.'
}
always {
cleanWs()
}
}
}
Commit and push this file, then trigger the pipeline. You now have infrastructure-as-code for your CI process.
Multibranch Pipelines
For most real-world projects, you want a Multibranch Pipeline instead of a plain Pipeline job. Multibranch pipelines automatically discover branches and pull requests, creating a sub-job for each one.
- Click New Item, select Multibranch Pipeline
- Under Branch Sources, add Git or GitHub
- Configure the repository URL and credentials
- Under Build Configuration, set Mode to "by Jenkinsfile" with Script Path
Jenkinsfile - Under Scan Multibranch Pipeline Triggers, set a scan interval (e.g., 1 minute) or use webhooks
- Save
Jenkins will scan the repository and create jobs for every branch that contains a Jenkinsfile. This is the standard approach for teams using feature branches and pull requests.
JCasC: Jenkins Configuration as Code
Manually clicking through the Jenkins UI to configure settings is not repeatable, not auditable, and not recoverable. JCasC lets you define your entire Jenkins configuration in YAML and apply it automatically at startup or on demand.
Basic JCasC Configuration
Install the Configuration as Code plugin, then create a YAML file:
# jenkins.yaml
jenkins:
systemMessage: "Jenkins configured via JCasC -- do not modify settings through the UI"
numExecutors: 0 # No builds on controller
securityRealm:
local:
allowsSignup: false
users:
- id: "admin"
password: "${JENKINS_ADMIN_PASSWORD}"
- id: "developer"
password: "${JENKINS_DEV_PASSWORD}"
authorizationStrategy:
globalMatrix:
permissions:
- "Overall/Administer:admin"
- "Overall/Read:developer"
- "Job/Build:developer"
- "Job/Read:developer"
- "Job/Workspace:developer"
remotingSecurity:
enabled: true
nodes:
- permanent:
name: "docker-agent-1"
remoteFS: "/home/jenkins"
launcher:
ssh:
host: "agent1.example.com"
port: 22
credentialsId: "agent-ssh-key"
sshHostKeyVerificationStrategy:
knownHostsFileKeyVerificationStrategy:
knownHostsFile: ""
labelString: "docker linux"
numExecutors: 4
unclassified:
location:
url: "https://jenkins.example.com/"
adminAddress: "devops@example.com"
gitscm:
globalConfigName: "Jenkins CI"
globalConfigEmail: "jenkins@example.com"
credentials:
system:
domainCredentials:
- credentials:
- usernamePassword:
scope: GLOBAL
id: "github-creds"
username: "${GITHUB_USERNAME}"
password: "${GITHUB_TOKEN}"
- basicSSHUserPrivateKey:
scope: GLOBAL
id: "agent-ssh-key"
username: "jenkins"
privateKeySource:
directEntry:
privateKey: "${AGENT_SSH_PRIVATE_KEY}"
- string:
scope: GLOBAL
id: "slack-webhook"
secret: "${SLACK_WEBHOOK_URL}"
tool:
git:
installations:
- name: "Default"
home: "git"
nodejs:
installations:
- name: "NodeJS-20"
properties:
- installSource:
installers:
- nodeJSInstaller:
id: "20.11.0"
npmPackages: ""
Applying JCasC
Set the environment variable CASC_JENKINS_CONFIG to point at your YAML file:
docker run -d \
--name jenkins \
-p 8080:8080 \
-e CASC_JENKINS_CONFIG=/var/jenkins_home/casc/jenkins.yaml \
-e JENKINS_ADMIN_PASSWORD=changeme \
-e GITHUB_USERNAME=myuser \
-e GITHUB_TOKEN=ghp_xxxxxxxxxxxx \
-e SLACK_WEBHOOK_URL=https://hooks.slack.com/services/xxx \
-v ./jenkins.yaml:/var/jenkins_home/casc/jenkins.yaml:ro \
-v jenkins_home:/var/jenkins_home \
jenkins/jenkins:lts-jdk17
You can also apply JCasC from the UI: navigate to Manage Jenkins, then Configuration as Code, and click "Apply new configuration" or "Reload existing configuration."
JCasC with Multiple Files
For complex configurations, split your YAML across multiple files. Point CASC_JENKINS_CONFIG at a directory:
-e CASC_JENKINS_CONFIG=/var/jenkins_home/casc/
Jenkins will load and merge all YAML files in the directory:
casc/
jenkins.yaml # Core Jenkins settings
security.yaml # Security realm and authorization
credentials.yaml # Credentials (use env vars for secrets)
tools.yaml # Tool installations
agents.yaml # Agent configurations
JCasC is not a silver bullet -- complex configurations can get unwieldy -- but for baseline settings, security, and credentials, it saves hours of manual setup and gives you a repeatable, version-controlled configuration.
Backup and Disaster Recovery
Jenkins stores everything in JENKINS_HOME. Losing this directory means losing all job configurations, build history, credentials, and plugin settings. Set up backups from day one.
What to Back Up
| Path | Content | Critical? |
|---|---|---|
config.xml | Global Jenkins configuration | Yes |
jobs/*/config.xml | Job configurations | Yes |
users/ | User accounts and API tokens | Yes |
secrets/ | Encryption keys and credentials | Yes |
plugins/ | Installed plugins | Recoverable, but saves time |
nodes/ | Agent configurations | Yes, if using static agents |
jobs/*/builds/ | Build history and logs | Optional (can be large) |
workspace/ | Build workspaces | No (temporary) |
Backup Script
#!/bin/bash
# backup-jenkins.sh
set -euo pipefail
JENKINS_HOME="/var/lib/jenkins"
BACKUP_DIR="/backups/jenkins"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="${BACKUP_DIR}/jenkins_backup_${DATE}.tar.gz"
mkdir -p "${BACKUP_DIR}"
# Create a compressed backup excluding workspaces and large build artifacts
tar -czf "${BACKUP_FILE}" \
--exclude='workspace' \
--exclude='*/builds/*/archive' \
--exclude='*/builds/*/stash' \
--exclude='.cache' \
--exclude='caches' \
-C "$(dirname ${JENKINS_HOME})" \
"$(basename ${JENKINS_HOME})"
# Keep only the last 7 daily backups
find "${BACKUP_DIR}" -name "jenkins_backup_*.tar.gz" -mtime +7 -delete
echo "Backup created: ${BACKUP_FILE} ($(du -sh ${BACKUP_FILE} | cut -f1))"
Run this daily via cron:
# Run backup at 3 AM daily
0 3 * * * /opt/scripts/backup-jenkins.sh >> /var/log/jenkins-backup.log 2>&1
For Docker-based installations, back up the named volume:
docker run --rm \
-v jenkins_home:/jenkins_home:ro \
-v /backups:/backups \
alpine tar czf /backups/jenkins_$(date +%Y%m%d).tar.gz \
-C / jenkins_home
Troubleshooting Common Installation Issues
Jenkins Will Not Start
Symptom: systemctl status jenkins shows "failed"
# Check for port conflicts
sudo ss -tlnp | grep 8080
# Check Java is available
java -version
# Check file permissions
ls -la /var/lib/jenkins/
ls -la /var/log/jenkins/
# View full startup error
sudo journalctl -u jenkins --no-pager | tail -50
Out of Memory Errors
Symptom: Jenkins becomes unresponsive or crashes with java.lang.OutOfMemoryError
# Check current heap usage
curl -s http://localhost:8080/computer/ | grep -i heap
# Increase heap size in JAVA_OPTS
# -Xmx4g -Xms2g
Plugin Compatibility Issues After Upgrade
Symptom: Jenkins starts but shows warnings or errors about plugins
# Start Jenkins in safe mode (no plugins loaded)
sudo systemctl stop jenkins
sudo java -jar /usr/share/java/jenkins.war --safe-mode
# Or, from the CLI, disable a problematic plugin
mv /var/lib/jenkins/plugins/problematic-plugin.jpi \
/var/lib/jenkins/plugins/problematic-plugin.jpi.disabled
sudo systemctl restart jenkins
Docker Socket Permission Denied
Symptom: permission denied while trying to connect to the Docker daemon socket
# Check Docker socket permissions
ls -la /var/run/docker.sock
# Add the jenkins user to the docker group
sudo usermod -aG docker jenkins
sudo systemctl restart jenkins
# For Docker-in-Docker, ensure the container runs with the correct GID
DOCKER_GID=$(stat -c '%g' /var/run/docker.sock)
docker run -d \
--group-add ${DOCKER_GID} \
-v /var/run/docker.sock:/var/run/docker.sock \
jenkins/jenkins:lts-jdk17
What Comes Next
With Jenkins installed and secured, you are ready to build real pipelines. The next steps are learning declarative pipeline syntax in depth, setting up Docker-based build agents for reproducible builds, and creating shared libraries so your teams are not copying Jenkinsfile snippets between repos.
Keep your Jenkins instance updated, back up JENKINS_HOME regularly, and treat your Jenkins configuration the same way you treat application code: version-controlled, reviewed, and tested. An unsecured, unmaintained Jenkins instance is a liability. A well-configured one is the backbone of your entire delivery process.
CI/CD Engineering Lead
Automation evangelist who believes no deployment should require a human. I write pipelines, break pipelines, and write about both. Code-first, always.
Related Articles
Jenkins Declarative Pipelines: The Complete Jenkinsfile Guide
Master Jenkins declarative pipelines — stages, steps, post actions, environment variables, credentials, parallel execution, and when conditions.
Docker Agents in Jenkins: Reproducible Builds Every Time
Run Jenkins pipeline stages inside Docker containers for clean, reproducible builds — agent configuration, custom images, Docker-in-Docker, and Kaniko alternatives.
Jenkins Shared Libraries: Reusable Pipeline Code at Scale
Build and maintain Jenkins shared libraries — directory structure, global vars, custom steps, class-based libraries, testing, and versioning strategies.