DevOpsil
Jenkins
92%
Fresh

Jenkins Installation & Configuration: From Zero to First Pipeline

Sarah ChenSarah Chen21 min read

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

ResourceMinimum (Single User)Recommended (Small Team)Production (Large Team)
CPU1 core4+ cores8+ cores
RAM256 MB4 GB8-16 GB
Disk1 GB50 GB SSD200+ GB SSD
JavaJDK 17JDK 17 or 21JDK 21
OSAny with JDK supportUbuntu 22.04/24.04 LTSUbuntu 24.04 LTS or RHEL 9
NetworkAny100 Mbps1 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 VersionStatusRecommendation
JDK 11Deprecated since Jenkins 2.357Do not use for new installations
JDK 17Fully supported, widely testedSafe default for most environments
JDK 21Fully supported since Jenkins 2.426Preferred 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 FlagPurpose
-Xmx4gMaximum heap size (adjust based on available RAM)
-Xms2gInitial heap size (set equal to or half of Xmx)
-XX:+UseG1GCUse the G1 garbage collector (best for Jenkins workloads)
-XX:+AlwaysPreTouchPre-allocate heap pages at startup for more predictable performance
-XX:+HeapDumpOnOutOfMemoryErrorDump heap on OOM for post-mortem analysis
-Djava.awt.headless=trueDisable 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:

RealmBest ForNotes
Jenkins' own user databaseSmall teams (under 20 users)Simple, no external dependencies
LDAPEnterprise environmentsIntegrates with Active Directory
SAML 2.0SSO with Okta, Azure AD, etc.Requires the SAML plugin
GitHub AuthenticationGitHub-centric teamsUses GitHub OAuth, maps orgs to roles
Google LoginGoogle Workspace orgsQuick SSO setup

Authorization Strategy options:

StrategyDescriptionRecommended?
Anyone can do anythingNo restrictions at allNever use in production
Logged-in users can do anythingAll authenticated users are adminsAvoid -- too permissive
Matrix-based securityGranular per-user/group permissionsYes, for most teams
Project-based MatrixPer-project permission overridesYes, for multi-team setups
Role-Based StrategyRole-based access control (RBAC)Yes, with the Role Strategy plugin

A practical matrix setup:

User/GroupOverall ReadJob BuildJob ConfigureJob CreateAdminister
adminYesYesYesYesYes
lead-devsYesYesYesYesNo
developersYesYesNoNoNo
viewersYesNoNoNoNo
AnonymousNoNoNoNoNo

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:

PluginArtifact IDPurpose
Pipelineworkflow-aggregatorDeclarative and scripted pipeline support
GitgitGit SCM integration
Docker Pipelinedocker-workflowRun stages in Docker containers
Credentials Bindingcredentials-bindingInject credentials into builds
Blue OceanblueoceanModern pipeline visualization UI
Job DSLjob-dslProgrammatic job creation
Configuration as Codeconfiguration-as-codeJCasC -- configure Jenkins via YAML
TimestampertimestamperAdd timestamps to console output
Workspace Cleanupws-cleanupClean workspace before/after builds
Warnings Next Generationwarnings-ngStatic analysis result aggregation
Pipeline Utility Stepspipeline-utility-stepsreadJSON, readYaml, zip, unzip helpers
SSH Agentssh-agentForward SSH keys into build steps

Plugins to Avoid

PluginReason
Swarm Plugin (client-side)Deprecated in favor of inbound agents
Jenkins CLI over RemotingSecurity risk, use SSH or HTTP API
Any plugin not updated in 12+ monthsLikely 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

  1. Click New Item on the Jenkins dashboard
  2. Enter a name (use lowercase with hyphens: my-app-build)
  3. Select Freestyle project and click OK
  4. Under Source Code Management, select Git and enter your repo URL
  5. Add credentials if the repo is private
  6. Under Build Triggers, check "Poll SCM" with schedule H/5 * * * * (every 5 minutes with hash-based jitter)
  7. Under Build Environment, check "Delete workspace before build starts" for clean builds
  8. 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/
  1. Under Post-build Actions, add "Archive the artifacts" with pattern dist/**/*
  2. Add "Publish JUnit test result report" with pattern junit.xml
  3. 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

  1. Click New Item, enter a name, select Pipeline
  2. Under Pipeline, select "Pipeline script from SCM"
  3. Choose Git, enter your repository URL, set the branch to */main
  4. Set the Script Path to Jenkinsfile (default)
  5. Optionally check "Lightweight checkout" for faster SCM polling
  6. 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.

  1. Click New Item, select Multibranch Pipeline
  2. Under Branch Sources, add Git or GitHub
  3. Configure the repository URL and credentials
  4. Under Build Configuration, set Mode to "by Jenkinsfile" with Script Path Jenkinsfile
  5. Under Scan Multibranch Pipeline Triggers, set a scan interval (e.g., 1 minute) or use webhooks
  6. 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

PathContentCritical?
config.xmlGlobal Jenkins configurationYes
jobs/*/config.xmlJob configurationsYes
users/User accounts and API tokensYes
secrets/Encryption keys and credentialsYes
plugins/Installed pluginsRecoverable, but saves time
nodes/Agent configurationsYes, if using static agents
jobs/*/builds/Build history and logsOptional (can be large)
workspace/Build workspacesNo (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.

Share:
Sarah Chen
Sarah Chen

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