Happy Dussehra! May good always win. Happy Diwali! Wishing you a bright year ahead. Merry Christmas! Warm wishes from Vizag. Happy New Year! Here’s to a great year ahead.

Archive · DevOps

Installing Jenkins LTS on Ubuntu 22.04

Install Jenkins 2.387 LTS with Java 17 from the stable repository, put nginx and TLS in front, change the port, harden the controller and plan upgrades and backups.

4 min read

  • Jenkins 2.387 LTS
  • OpenJDK 17
  • Ubuntu 22.04 LTS
  • nginx
  • systemd

Jenkins remains a common self-hosted automation server. A clean install on a small Ubuntu VM takes ten minutes; the work that matters afterwards is securing it, keeping it patched and being able to rebuild it. This post covers all three.

Versions in use on this date. Jenkins 2.387 LTS (the 2.387.1 release came out in March 2023; the 2.387.2 update is due this week), which runs on Java 11 or Java 17, and Ubuntu 22.04 LTS. Use Java 17 for new installs. The Jenkins project signs its Debian packages with a new key, jenkins.io-2023.key, after rotating the earlier one, so instructions older than the end of 2022 fail with a GPG error.

What you are building

flowchart TB
  U[Users] -->|HTTPS 443| NGX[nginx reverse proxy<br/>TLS certificate, security headers]
  NGX -->|HTTP 8080 on localhost| J
  subgraph VM[Ubuntu 22.04 VM - controller]
    J[Jenkins controller<br/>systemd service, user jenkins, Java 17]
    HOME[(JENKINS_HOME var lib jenkins<br/>jobs, config, credentials, plugins)]
    J --- HOME
  end
  J -->|SSH or inbound agent| A[Build agents<br/>ephemeral VMs or containers]
  HOME -->|nightly backup| S3[(Backup storage)]
  UFW[Firewall - allow 22 and 443 only] -.-> VM

Install

sudo apt update && sudo apt install -y openjdk-17-jre fontconfig
java -version

sudo wget -O /usr/share/keyrings/jenkins-keyring.asc https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key
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
sudo apt update && sudo apt install -y jenkins

sudo systemctl enable --now jenkins
systemctl status jenkins --no-pager
sudo cat /var/lib/jenkins/secrets/initialAdminPassword

The debian-stable repository carries LTS releases, which is what servers should use; the debian repository carries weekly releases. Pin and upgrade deliberately: sudo apt-mark hold jenkins before planned maintenance windows if you want control, then sudo apt update && sudo apt install --only-upgrade jenkins.

Open http://<server>:8080 (or tunnel with ssh -L 8080:localhost:8080), paste the initial admin password, install the suggested plugins, and create a named admin user. Do not leave the setup wizard on an internet-facing port.

Change the port or Java options

The service reads settings from a systemd override, which survives upgrades:

sudo systemctl edit jenkins
[Service]
Environment="JENKINS_PORT=8080"
Environment="JENKINS_LISTEN_ADDRESS=127.0.0.1"
Environment="JAVA_OPTS=-Djava.awt.headless=true -Xms512m -Xmx1g"
sudo systemctl restart jenkins

Listening on 127.0.0.1 means only the local reverse proxy can reach it. Size the heap to the VM (typically half of RAM at most) and watch garbage collection if the UI gets slow.

nginx with TLS in front

server {
  listen 443 ssl http2;
  server_name ci.example.com;
  ssl_certificate     /etc/letsencrypt/live/ci.example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/ci.example.com/privkey.pem;
  add_header Strict-Transport-Security "max-age=31536000" always;

  location / {
    proxy_pass http://127.0.0.1:8080;
    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_http_version 1.1;
    proxy_request_buffering off;
    proxy_read_timeout 90s;
  }
}
server { listen 80; server_name ci.example.com; return 301 https://$host$request_uri; }

Then set Manage Jenkins, System, Jenkins URL to https://ci.example.com/ so links and webhooks are right. If you see the “reverse proxy setup is broken” warning, check the X-Forwarded-Proto header and the Jenkins URL.

First hardening steps

  1. Authentication and authorisation: connect SSO (OIDC, SAML or LDAP plugin) or use Jenkins’ own user database with strong passwords; under Security choose Matrix-based or Role-based authorisation, disable anonymous read and sign-up.
  2. Controller executors = 0 (Manage Jenkins, Nodes, Built-In Node): builds run on agents, so a malicious job cannot read the controller’s files.
  3. CSRF protection stays on (default), and agent-to-controller access control stays on.
  4. Credentials: store secrets in the credentials store with folder scopes; never in job configs or the workspace.
  5. Firewall: allow only 22 (from your admin network) and 443; sudo ufw allow OpenSSH && sudo ufw allow 443/tcp && sudo ufw enable.
  6. Plugins: install only what you need; update monthly. Subscribe to the Jenkins security advisories and apply core and plugin fixes quickly, because Jenkins is a frequent target.

Backups and upgrades

Everything that matters is in JENKINS_HOME (/var/lib/jenkins): jobs, configuration, credentials (encrypted with keys in secrets/) and plugins.

sudo tar --exclude='workspace' --exclude='caches' -czf /backup/jenkins-$(date +%F).tgz -C /var/lib jenkins

Take a VM snapshot or file backup before each upgrade, upgrade a staging copy first when you can, and restore-test the backup at least once. Pair this with Configuration as Code (next post) so the controller can be rebuilt, not just restored.

Troubleshooting

Symptom Likely cause
NO_PUBKEY or GPG error on apt update Old repository key; install jenkins.io-2023.key
jenkins.service failed Wrong Java version or port in use; journalctl -u jenkins -n 100
Locked out after enabling security Stop Jenkins, set <useSecurity>false</useSecurity> in config.xml temporarily, restart, fix, re-enable (a last resort, do this offline)
Slow UI Heap too small, too many plugins, disk full; check df -h /var/lib/jenkins
Webhooks fail Jenkins URL wrong or the proxy blocks the endpoint