Deployment guide

Jenkins LTS 2.580.2

Overview

Jenkins is the open source automation server for continuous integration and delivery. It runs jobs (builds, tests, deployments) on its own executors or on agents, and keeps their history and console output. You can trigger jobs by hand, on a schedule, through the REST API or with the command line client.

This guide covers launching and using the Jenkins LTS 2.580.2 on Ubuntu 24.04 AMI from SecureAnyCloud on Amazon EC2. It is one version of the SecureAnyCloud Hardened Apache Subversion (SVN) & CI/CD Tools listing on AWS Marketplace. You select it as the software version when you subscribe and launch.

What you get:

  • No setup wizard and no shared password. Upstream Jenkins shows an unlock page with a one-time password on first start. This image never does. First boot creates Jenkins' own user database with the admin account and a 24-character password generated on your instance. The password is written to /var/lib/sac/secrets/sac-credentials.txt (root only). Jenkins refuses to start on this image until first boot has secured it.
  • Nothing shared between instances. The image ships an empty JENKINS_HOME. At its first start on your instance, Jenkins creates its own secret.key, master.key and the keys that encrypt stored credentials. First boot creates the HTTPS certificate and SSH host keys there too.
  • A credential for everything. Anonymous read access is off and there is no sign-up page. Every page, the REST API and the CLI need a signed-in user or an API token. Logged-in users can do anything, so add other accounts only for people you trust (see the CI-risk notes).
  • The password only on the login form. The admin password is accepted only by Jenkins' sign-in page, which nginx rate-limits per client address (10 a minute, with a burst of 10). Scripts, the REST API and the CLI use an API token instead, which you create, name and revoke per use. CSRF protection is on.
  • Jenkins on loopback behind nginx. Jenkins listens on 127.0.0.1:8080. nginx serves it on port 443 with a certificate generated on your instance, and port 80 only redirects. The inbound TCP agent port is off: agents connect over WebSocket through port 443.
  • No plugins. This is Jenkins core: freestyle jobs, the shell build step, the REST API, the CLI and Jenkins' own user database. Add the plugins you need from Manage Jenkins > Plugins.
  • Builds kept away from the metadata service. Jenkins and every build it runs on the instance live in a systemd sandbox. The sandbox denies the EC2 instance metadata service, grants no capabilities, blocks privilege escalation (sudo, su) and keeps the system read-only outside Jenkins' own folders.
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.

What is on the image: Jenkins LTS 2.580.2 (MIT, the upstream package from pkg.jenkins.io), OpenJDK 21 and nginx from Ubuntu 24.04, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.

Default user: ubuntu. Architecture: x86_64. Recommended instance type: m5.large.

Read this before you share the instance: builds run with Jenkins' own access. Builds run on this instance as the jenkins user, the same account as Jenkins itself. Anyone who can create or change a job can run any command as that user. That includes reading everything Jenkins keeps in JENKINS_HOME, including the keys that decrypt stored credentials.

Only let trusted people create or change jobs. Keep the instance metadata hop limit at 1 and attach no instance profile (or a least-privilege one). For untrusted work, set the built-in node's executors to 0 and build on agents. Step 7 explains each point.

Trademark notice: Jenkins is a registered trademark of LF Charities Inc. (the Jenkins project). SecureAnyCloud is not affiliated with, endorsed by or sponsored by the Jenkins project or LF Charities. The name is used only to identify the open source software in this image, which is the unmodified upstream Jenkins LTS release.

Prerequisites

  • An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet.
  • A security group that allows TCP 443 (Jenkins over HTTPS) and TCP 80 (redirects to HTTPS) from the networks your users work from. Allow TCP 22 (SSH) from your own network only. Jenkins needs no other inbound port, because agents connect over 443.
1

Launch the instance

In the AWS Marketplace listing, choose Continue to Subscribe, then Continue to Configuration. Pick Jenkins LTS 2.580.2 on Ubuntu 24.04 as the software version, select your Region, and choose Launch through EC2.

Choose m5.large, your key pair, a security group as described above, and a subnet. Jenkins keeps its jobs, build history and workspaces on the root volume under /var/lib/jenkins. Raise the volume size under Configure storage if you keep many builds or large workspaces.

Under Advanced details, leave IAM instance profile empty (or choose a least-privilege one). Set Metadata version to V2 only and Metadata response hop limit to 1.

To launch with the AWS CLI instead, replace the values in angle brackets. This command sets a 50 GiB root volume and no instance profile:

aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
  --instance-type m5.large --key-name <your-key-pair> \
  --security-group-ids <your-security-group> --subnet-id <your-subnet> \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled,HttpPutResponseHopLimit=1 \
  --block-device-mappings 'DeviceName=/dev/sda1,Ebs={VolumeSize=50,VolumeType=gp3}' \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=jenkins}]'
2

Connect to your instance

Connect over SSH as ubuntu, the default user for Jenkins LTS 2.580.2 on Ubuntu 24.04.

ssh -i <your-key.pem> ubuntu@<public-ip>
3

Check the services

Check that first boot has finished. First boot works out the instance's name, generates the certificate and the admin password, and starts Jenkins once to create the admin account. It then proves over HTTPS that anonymous requests are refused and the new password is accepted, and only then opens the web server. This takes about two minutes:

systemctl is-active jenkins nginx jenkins-set-host
active active active

jenkins-set-host runs on every boot before nginx. It keeps the served name, the certificate and nginx's host rules in sync (Step 9).

Only SSH (22) and nginx (80, 443) listen on the network. Jenkins (8080) listens on the loopback address only, which Java shows as the IPv4-mapped [::ffff:127.0.0.1]. The :53 entries are Ubuntu's local DNS resolver, :323 is the time service on the loopback address and :68 is the DHCP client:

sudo ss -lntup | awk 'NR>1 {print $1, $5}' | sort -u
tcp 0.0.0.0:22 tcp 0.0.0.0:443 tcp 0.0.0.0:80 tcp 127.0.0.53%lo:53 tcp 127.0.0.54:53 tcp [::]:22 tcp [::]:443 tcp [::]:80 tcp [::ffff:127.0.0.1]:8080 udp 127.0.0.1:323 udp 127.0.0.53%lo:53 udp 127.0.0.54:53 udp 172.31.81.62%ens5:68 udp [::1]:323

Jenkins runs as the jenkins account on Ubuntu's OpenJDK 21. It starts from the package's own jenkins.service, with SecureAnyCloud's settings in /etc/systemd/system/jenkins.service.d/10-sac.conf:

ps -o user:10=,args= -C java java -version 2>&1 | head -1
jenkins /usr/bin/java -Djava.awt.headless=true -Djenkins.install.runSetupWizard=false -Djenkins.security.ignoreBasicAuth=true -Dhudson.model.UsageStatistics.disabled=true -XX:MaxRAMPercentage=50 -XX:+ExitOnOutOfMemoryError -jar /usr/share/java/jenkins.war --webroot=/var/cache/jenkins/war --httpPort=8080 --httpListenAddress=127.0.0.1 openjdk version "21.0.12.1" 2026-08-18

runSetupWizard=false turns the unlock wizard off, because first boot has already secured Jenkins. ignoreBasicAuth=true makes Jenkins refuse a password sent as HTTP Basic (Step 4). Usage statistics are not sent.

Jenkins may use up to half the instance's memory for its Java heap. A larger instance type gives it more room without any change.

If nginx is not running, first boot has not finished or has failed, and the web server stays closed. Check systemctl status jenkins-firstboot and sudo tail -40 /var/log/sac/jenkins-firstboot.log.

4

Get your address and password

Read the credentials file on the instance:

sudo cat /var/lib/sac/secrets/sac-credentials.txt

The file is readable by root only. It holds the address, the admin user name and password, and the certificate fingerprint:

# Jenkins - generated on first boot. Unique to this instance; keep it safe.
# Sign in at JENKINS_URL as JENKINS_ADMIN_USER / JENKINS_ADMIN_PASSWORD (the login form; API calls and the CLI need an
# API token: your name > Security > API Token). Compare JENKINS_TLS_SHA256 with the certificate your browser shows (or
# install your own certificate: see the guide). Rotate the password:
sudo jenkins-set-password JENKINS_URL=https://ec2-203-0-113-10.compute-1.amazonaws.com/ JENKINS_ADMIN_USER=admin JENKINS_ADMIN_PASSWORD=<JENKINS_ADMIN_PASSWORD> JENKINS_TLS_SHA256=15:1A:95:B3:94:79:1A:2D:4C:0E:A8:91:DC:F4:BC:AF:C8:80:66:6B:22:BD:AA:50:89:ED:C0:2C:F3:0E:BB:1B

To print everything except the password:

sudo grep -E '^JENKINS_(URL|ADMIN_USER|TLS_SHA256)=' /var/lib/sac/secrets/sac-credentials.txt
JENKINS_URL=https://ec2-203-0-113-10.compute-1.amazonaws.com/ JENKINS_ADMIN_USER=admin JENKINS_TLS_SHA256=15:1A:95:B3:94:79:1A:2D:4C:0E:A8:91:DC:F4:BC:AF:C8:80:66:6B:22:BD:AA:50:89:ED:C0:2C:F3:0E:BB:1B

Run the verification helper to confirm the whole path from the instance itself. It goes through nginx over HTTPS with the certificate pinned. It checks that:

  • 15 kinds of page, the script console and the CLI are refused without a credential
  • there is no sign-up page
  • a wrong password and the well-known defaults are refused
  • the password is refused when sent as HTTP Basic
  • this instance's password signs in on the login form
sudo /usr/local/sbin/jenkins-verify.sh
JENKINS_ROUNDTRIP_OK (HTTPS: 15 routes 403 + scriptText 403 + jnlpJars/cli 401 anonymously, sign-up 404, wrong/default passwords refused, the password refused as HTTP Basic; the minted password accepted on the login form, session admin)

Change the password with jenkins-set-password. With no argument, it generates a new 24-character password. With -, it reads yours from standard input (at least 16 printable characters, no spaces). It never accepts the password as an argument, so the password does not appear in the process list or your shell history.

Jenkins restarts to apply the change, so running builds are interrupted. The command also updates /var/lib/sac/secrets/sac-credentials.txt, and it is the way back in if the password is lost:

$
sudo jenkins-set-password jenkins-set-password: the admin password was replaced (sudo cat /var/lib/sac/secrets/sac-credentials.txt)

To choose your own password:

$ read -rs NEWPW && printf '%s\n' "$NEWPW" | sudo jenkins-set-password - ; unset NEWPW jenkins-set-password: the admin password was replaced (sudo cat /var/lib/sac/secrets/sac-credentials.txt)
5

Sign in

Open the JENKINS_URL from Step 4 in your browser. The certificate is self-signed until you install your own (Step 9), so the browser warns once. Compare the fingerprint it shows with JENKINS_TLS_SHA256. On the sign-in page, enter admin and the password from Step 4:

The dashboard lists your jobs (none yet), the build queue and the built-in node's two executors. A red dot on the gear icon means Jenkins has a notice for administrators. Open Manage Jenkins to read it.

Open Manage Jenkins > Security to see what first boot configured: Jenkins' own user database with sign-up off, and "Logged-in users can do anything" with anonymous read access off.

Keep me signed in sets a long-lived cookie. Tick Disable "Keep me signed in" if you would rather every session expire.

Add accounts under Manage Jenkins > Users > Create User. Every account can do everything Jenkins can, including running any command on this instance through a job, so create accounts only for trusted people. For finer permissions, install the Matrix Authorization Strategy or Role-based Authorization Strategy plugin.

6

Create and run a job

Choose Create a job (or New Item), enter a name, select Freestyle project and choose OK. Under Build Steps, add Execute shell, type the commands to run, and choose Save:

Choose Build Now, then the build number and Console Output. The shell step ran as the jenkins user on this instance. The console line "Running as SYSTEM" is Jenkins' own way of saying the build has Jenkins' full authority inside Jenkins:

Do the same from a script, with an API token. The REST API and the CLI do not accept your password. They take an API token, which you create under your name (top right): choose Security, API Token, then Add new token. The token is shown once. Keep it in a password manager and revoke it when you no longer need it.

The round trip below runs on the instance itself. It signs in on the login form, creates a token, and uses the token to create a job whose shell step also tries the metadata service. It then runs the job, prints the console, deletes the job and revokes the token. The password and the token are read from and written to files only, never printed or put on a command line:

D=$(mktemp -d) && cd "$D" && umask 077
HOST=$(sudo sed -n 's#^JENKINS_URL=https://\([^/]*\)/$#\1#p' /var/lib/sac/secrets/sac-credentials.txt) B="https://${HOST}" C=(-s --max-time 30 --cacert /etc/ssl/jenkins/jenkins.crt --resolve "${HOST}:443:127.0.0.1")
sudo sed -n 's/^JENKINS_ADMIN_PASSWORD=//p' /var/lib/sac/secrets/sac-credentials.txt | tr -d '\n' > pw
# 1. sign in on the login form (it needs the page's session cookie and its Jenkins-Crumb field)
curl "${C[@]}" -c jar -o login.html "$B/login" grep -oE '<input[^>]*name="Jenkins-Crumb"[^>]*>' login.html | sed -nE 's/.*value="([^"]*)".*/\1/p' | head -1 | tr -d '\n' > crumb
curl "${C[@]}" -b jar -c jar -o /dev/null -w 'sign in: %{http_code}\n' --data-urlencode j_username=admin \
  --data-urlencode j_password@pw --data-urlencode Jenkins-Crumb@crumb --data-urlencode from=/ "$B/j_spring_security_check"
curl "${C[@]}" -b jar -c jar "$B/crumbIssuer/api/json" | python3 -c 'import json,sys; print("Jenkins-Crumb: " + json.load(sys.stdin)["crumb"])' > crumb.hdr
# 2. create an API token for this script
curl "${C[@]}" -b jar -c jar -H @crumb.hdr --data newTokenName=guide-demo -o token.json \
  "$B/me/descriptorByName/jenkins.security.ApiTokenProperty/generateNewToken" python3 -c 'import json,sys; d=json.load(open("token.json"))["data"]; open("auth.cfg","w").write("user = \"admin:%s\"\n" % d["tokenValue"]); open("uuid","w").write(d["tokenUuid"])' T=("${C[@]}" -K auth.cfg)
# 3. create a freestyle job with one shell step, run it and print its console cat > job.xml <<'XML' <?xml version='1.1' encoding='UTF-8'?> <project><description>Created by the sac guide.</description><builders><hudson.tasks.Shell><command>echo "Hello from Jenkins as $(id -un)" echo "metadata service from a build: $(curl -s -m 3 -o /dev/null -w '%{http_code}' -X PUT -H 'X-aws-ec2-metadata-token-ttl-seconds: 60' http://169.254.169.254/latest/api/token || true)" </command></hudson.tasks.Shell></builders></project> XML
curl "${T[@]}" -H 'Content-Type: application/xml' --data-binary @job.xml -o /dev/null -w 'create job: %{http_code}\n' "$B/createItem?name=guide-demo"
curl "${T[@]}" -X POST -o /dev/null -w 'start build: %{http_code}\n' "$B/job/guide-demo/build" for i in $(seq 1 60); do
R=$(curl "${T[@]}" -f "$B/job/guide-demo/lastBuild/api/json?tree=result" 2>/dev/null | python3 -c 'import json,sys; print(json.load(sys.stdin).get("result") or "")' 2>/dev/null || true) [ -n "$R" ] && break; sleep 2 done
curl "${T[@]}" "$B/job/guide-demo/lastBuild/consoleText" | grep -vE '^(Running as|Building in|\[guide-demo\])'
# 4. tidy up: delete the job, revoke the token, remove the files
curl "${T[@]}" -X POST -o /dev/null -w 'delete job: %{http_code}\n' "$B/job/guide-demo/doDelete"
curl "${C[@]}" -b jar -c jar -H @crumb.hdr --data-urlencode tokenUuid@uuid -o /dev/null -w 'revoke token: %{http_code}\n' \
  "$B/me/descriptorByName/jenkins.security.ApiTokenProperty/revoke" cd / && rm -rf "$D"
sign in: 302 create job: 200 start build: 201 Started by user admin + id -un + echo Hello from Jenkins as jenkins Hello from Jenkins as jenkins + curl -s -m 3 -o /dev/null -w %{http_code} -X PUT -H X-aws-ec2-metadata-token-ttl-seconds: 60 http://169.254.169.254/latest/api/token + true + echo metadata service from a build: 000 metadata service from a build: 000 Finished: SUCCESS delete job: 302 revoke token: 200

000 means the build could not reach the metadata service at all. From your workstation, make the same calls with your token in place of the sign-in, for example curl -u admin:<api-token> https://<host>/api/json.

The CLI works over HTTPS with WebSocket and an API token. The remoting CLI is gone from Jenkins, and nginx refuses /cli and /jnlpJars without credentials. Download jenkins-cli.jar with your token, then:

curl -fsS -u admin:<api-token> -o jenkins-cli.jar https://<host>/jnlpJars/jenkins-cli.jar java -jar jenkins-cli.jar -s https://<host>/ -webSocket -auth admin:<api-token> who-am-i Authenticated as: admin

While the certificate is self-signed, import it into a Java trust store and pass the store with -Djavax.net.ssl.trustStore=..., or install your own certificate (Step 9).

7

CI risk: who can run what

Jenkins runs other people's code, so deciding who may define jobs is the main security decision. On this image:

  • Builds run on the instance as the jenkins user. The built-in node has 2 executors, so a freestyle shell step (or any build step) runs inside the Jenkins service, as the same account as Jenkins.
  • Job authors can read Jenkins' secrets. A build can read every file in JENKINS_HOME (/var/lib/jenkins): the configuration, secrets/master.key and secrets/hudson.util.Secret, and with them every credential Jenkins stores. Treat "can create or change a job" as "can act as Jenkins". Only trusted people should have accounts.
  • What a build cannot do here. The service's sandbox applies to every build on the built-in node. Builds have no sudo or su (no new privileges), no capabilities, a read-only system outside /var/lib/jenkins, /var/cache/jenkins and /var/log/jenkins, a private /tmp, and no route to the instance metadata service (169.254.169.254 and its IPv6 address). The jenkins account has no login shell and is in no group but its own (not docker). It cannot use cron, at, systemd-run or lingering user services, and SSH refuses it, so a build cannot start a process outside the sandbox. The credentials file /var/lib/sac/secrets/sac-credentials.txt, the TLS key and the SSH host keys are readable by root only, so a build cannot read them.
  • Keep the hop limit at 1. The metadata block covers the Jenkins service. A metadata hop limit of 1 (Step 1) also stops any container you later run on this host from fetching IMDSv2 tokens.
  • Attach no instance profile, or a least-privilege one. Other software on the instance, and root, can still reach the metadata service, so an instance profile's permissions are only as safe as the instance. Give Jenkins cloud credentials through Jenkins' credentials store (or an agent's own role), scoped to what the jobs need.
  • Untrusted work goes on agents. To build pull requests or code from people you do not fully trust, set the built-in node's executors to 0 (Manage Jenkins > Nodes > Built-In Node > Configure > Number of executors). Run those builds on separate agent machines that hold nothing Jenkins would miss.

The sandbox and the metadata block, as systemd applies them to Jenkins (and so to its builds) and nginx:

systemctl show jenkins -p User -p NoNewPrivileges -p ProtectSystem -p ProtectHome -p PrivateTmp -p CapabilityBoundingSet -p UMask
systemctl show jenkins nginx -p Id -p IPAddressDeny
UMask=0027 CapabilityBoundingSet= User=jenkins PrivateTmp=yes ProtectHome=yes ProtectSystem=strict NoNewPrivileges=yes IPAddressDeny=168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128 Id=jenkins.service IPAddressDeny=168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128 Id=nginx.service

The jenkins account and the ways out of the sandbox that are closed:

id jenkins getent passwd jenkins | cut -d: -f7 cat /etc/cron.allow
sudo sshd -T | grep -iE '^(denyusers|passwordauthentication|permitrootlogin)'
uid=997(jenkins) gid=987(jenkins) groups=987(jenkins) /usr/sbin/nologin root permitrootlogin no passwordauthentication no denyusers jenkins
8

Agents and plugins

Agents. The inbound TCP agent port is off, so agents use WebSocket through port 443. Under Manage Jenkins > Nodes > New Node, create a Permanent Agent with Launch agent by connecting it to the controller, tick Use WebSocket, and save.

The node page then shows the command that starts the agent on its machine. Downloading agent.jar needs your API token (curl -u admin:<api-token> ...), because nginx refuses /jnlpJars without credentials. To start agents over SSH instead, install the SSH Build Agents plugin. Run each agent on a machine you are willing to let job authors control.

Plugins. There are no plugins on this image. Jenkins can reach the update center over the internet, so you can install plugins (Git, Pipeline, credentials bindings and so on) from Manage Jenkins > Plugins > Available plugins. Each plugin runs inside Jenkins with its full access. Install only what you need, keep plugins updated from the same page, and read the security warnings Jenkins shows there.

9

Your own host name and certificate

On every boot, jenkins-set-host works out the name to serve. It uses the name you set, or else the instance's public DNS name from the instance metadata service (IMDSv2). The Jenkins URL (Manage Jenkins > System) follows that name:

systemctl is-enabled jenkins-set-host.service
sudo jenkins-set-hostname --boot
enabled jenkins-set-hostname: unchanged (ec2-203-0-113-10.compute-1.amazonaws.com)

Use your own DNS name. Point a DNS record at the instance (an Elastic IP keeps the address fixed). Then run sudo jenkins-set-hostname jenkins.example.com and sudo systemctl restart jenkins.

The name is kept in /etc/sac/jenkins-hostname for every later boot, and the credentials file is updated. nginx serves that name, redirects the instance's other addresses to it, and refuses browser requests from other origins. A Jenkins URL you set by hand is left alone. If the certificate in place is the one SecureAnyCloud generated, it is re-created for the new name.

Which certificates are replaced. Only a certificate that SecureAnyCloud generated is ever re-created. Its SHA-256 fingerprint is recorded, root-owned, in /var/lib/sac/data/jenkins-cert.sha256. A certificate is treated as SecureAnyCloud's only while it matches that record:

sudo cat /var/lib/sac/data/jenkins-cert.sha256 openssl x509 -in /etc/ssl/jenkins/jenkins.crt -noout -fingerprint -sha256
15:1A:95:B3:94:79:1A:2D:4C:0E:A8:91:DC:F4:BC:AF:C8:80:66:6B:22:BD:AA:50:89:ED:C0:2C:F3:0E:BB:1B sha256 Fingerprint=15:1A:95:B3:94:79:1A:2D:4C:0E:A8:91:DC:F4:BC:AF:C8:80:66:6B:22:BD:AA:50:89:ED:C0:2C:F3:0E:BB:1B

Install your own certificate (from your CA, or Let's Encrypt with a DNS challenge) by putting the full chain and the key at the same paths. Anything there that does not match the record is yours. It is never replaced, neither on boot nor when the name changes:

sudo install -m 0644 -o root -g root fullchain.pem /etc/ssl/jenkins/jenkins.crt
sudo install -m 0600 -o root -g root privkey.pem /etc/ssl/jenkins/jenkins.key
sudo nginx -t && sudo systemctl reload nginx

Go back to a generated certificate by removing both files and running the helper with the name to serve:

$
sudo rm /etc/ssl/jenkins/jenkins.crt /etc/ssl/jenkins/jenkins.key $
sudo jenkins-set-hostname jenkins.example.com

To return to the instance's own public name on every boot, run sudo rm /etc/sac/jenkins-hostname && sudo jenkins-set-hostname --boot.

10

Backups and updates

Everything Jenkins stores is under /var/lib/jenkins: the configuration, jobs and build history, accounts, plugins and the secrets/ folder. Back it up with an EBS snapshot of the root volume, or stop Jenkins and copy the folder. The backup holds secrets/master.key, which decrypts every stored credential, so protect it as carefully as the instance:

sudo systemctl stop jenkins
sudo tar -C /var/lib -czf /root/jenkins-backup.tar.gz jenkins
sudo systemctl start jenkins

Unattended security upgrades update Ubuntu packages, including OpenJDK and nginx. Jenkins itself comes from the Jenkins project's LTS repository (pkg.jenkins.io/debian-stable, signing key pinned), which unattended upgrades does not touch. Run sudo apt-get update && sudo apt-get install jenkins to move to the newest LTS release when you choose. Jenkins restarts on the new release:

systemctl is-enabled unattended-upgrades apt-daily-upgrade.timer cat /etc/apt/sources.list.d/jenkins.list dpkg-query -W -f='${Package} ${Version}\n' jenkins
enabled enabled deb [signed-by=/usr/share/keyrings/jenkins-keyring.gpg] https://pkg.jenkins.io/debian-stable binary/ jenkins 2.580.2

Back up /var/lib/jenkins before you upgrade. SecureAnyCloud also publishes a new version of this listing for new Jenkins LTS releases.

Security notes

  • Credential first. Anonymous users get the sign-in page and nothing else (403 for every page, the API and the script console). nginx answers /cli and /jnlpJars without credentials with 401 and /signup with 404. Sign-up is off.
  • Password or token, never both ways. The password works only on the sign-in form, which is rate-limited per client address. The REST API and CLI take API tokens, which a user can revoke one by one.
  • Exact listeners. Only 22, 80 and 443 listen on the network (Step 3). Jenkins is on loopback and the TCP agent port is off. Port 80 only redirects to HTTPS. Open the ports in the security group only to the networks that need them.
  • Forwarded headers. nginx sends Jenkins the configured host name, https, port 443 and the real client address (X-Forwarded-For is set from the connection, never passed through), drops client-supplied Forwarded headers, and refuses state-changing requests and WebSocket upgrades from other origins. Responses carry HSTS.
  • Instance metadata. systemd denies the EC2 instance metadata service to Jenkins, its builds, nginx, first boot and the helper commands (Step 7). Launch with IMDSv2 required and a hop limit of 1, as in Step 1.
  • Secrets per instance. The admin password, Jenkins' secret.key, master.key and credential keys, the HTTPS certificate and the SSH host keys are all created on your instance's first boot. None is in the image.

Build provenance and licence

Jenkins LTS 2.580.2 is installed from the Jenkins project's Debian LTS repository with apt, with the repository's signing key pinned to fingerprint 5E386EADB55F01504CAE8BCF7198F4B714ABFC68; the build also checks the SHA-256 of the package and the war. No plugin is installed:

head -3 /usr/share/doc/sac/jenkins-BUILD-INFO.txt sha256sum /usr/share/java/jenkins.war
sudo ls -A /var/lib/jenkins/plugins | wc -l
Jenkins LTS 2.580.2 on Ubuntu 24.04 (sac AWS bundle member dev-cicd) package jenkins=2.580.2 from https://pkg.jenkins.io/debian-stable (deb sha256 c0a735a91d20b6a0eb60052b81524f939d6eec58d6e8d9546414913f66c54630), Signed-By /usr/share/keyrings/jenkins-keyring.gpg = exactly key 5E386EADB55F01504CAE8BCF7198F4B714ABFC68 war /usr/share/java/jenkins.war sha256 7adc065557c52e464ce9221a5a6a3de642f7b02e00b6c01043c0e5835a1c61b6; MIT (core); plugins: none 7adc065557c52e464ce9221a5a6a3de642f7b02e00b6c01043c0e5835a1c61b6 /usr/share/java/jenkins.war 0

Jenkins core is MIT licensed. The libraries inside the war carry their own open source licences. The Jenkins source is at github.com/jenkinsci/jenkins (tag jenkins-2.580.2).

Jenkins security advisories are published at jenkins.io/security/advisories. SecureAnyCloud publishes a new version of this listing when an LTS release fixes one.

Troubleshooting

  • "Invalid username or password". Read the password again from /var/lib/sac/secrets/sac-credentials.txt (the user name is admin). After jenkins-set-password, the old password is refused.
  • 401 from a script or the CLI with the right password. Scripts and the CLI need an API token, not the password (Step 6).
  • 403 No valid crumb was included in the request. A form post or script used a session cookie without the crumb. Use an API token for scripts, because tokens need no crumb.
  • 429 Too Many Requests. One address made too many sign-in attempts (10 a minute) or too many requests in a short time. Wait a minute.
  • 403 on a form or WebSocket from another site, or the connection dropped with no answer. The request named a host or origin the instance does not serve. Use the name in JENKINS_URL, or set yours with jenkins-set-hostname (Step 9).
  • Builds stay in the queue. The built-in node has 0 executors and no agent is online (Step 8).
  • A build cannot reach the metadata service or use sudo. That is the sandbox (Step 7). Give jobs cloud credentials through Jenkins' credentials store, or run them on an agent.
  • Logs: journalctl -u jenkins, /var/log/nginx/access.log and /var/log/nginx/error.log, and /var/log/sac/jenkins-firstboot.log.

Support

SecureAnyCloud images come with 24/7 support. Email sales@secureanycloud.com with your AWS account ID, the instance ID and the output of sudo /usr/local/sbin/jenkins-verify.sh. For Jenkins itself, see the Jenkins documentation.

Deploy on AWS

Launch Jenkins LTS 2.580.2 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.

View on Marketplace

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on AWS