The Real Problem: Jenkins Won’t Start Even When Installed Correctly
Last month I finished setting up a CI/CD system for a dev team of 8. We chose CentOS Stream 9 because the company is standardizing all servers on RHEL-compatible distributions — only to discover that “installing Jenkins” isn’t as simple as copy-pasting from the official documentation.
Following the Jenkins docs exactly, running the installation commands, service starts and reports active — but opening a browser gives you:
curl http://localhost:8080
# curl: (7) Failed to connect to localhost port 8080: Connection refused
The Jenkins log is full of lines like this:
Nov 08 10:23:41 server jenkins[12345]: java.io.IOException: Failed to bind to 0.0.0.0/0.0.0.0:8080
Adding an Nginx reverse proxy in front also fails to forward requests despite the config looking completely correct. Three problems stacked on top of each other that the official docs don’t adequately address.
Root Cause Analysis
After about 2 hours of debugging, I identified 3 main causes:
1. Incompatible Java Version
Jenkins LTS 2.426+ requires Java 17 or Java 21. CentOS Stream 9 defaults to pulling java-11-openjdk from the repo. I had installed Java 17 correctly, but the system still used Java 11 as the default because I hadn’t run alternatives yet.
# Check the currently active Java
java -version
# openjdk version "11.0.21" — wrong!
# List all installed Java versions
alternatives --list | grep java
2. SELinux Blocking Jenkins Port Binding and File Access
CentOS Stream 9 runs SELinux in enforcing mode by default. Jenkins needs to bind port 8080 and read/write its home directory — all of which get denied without proper context and port type configuration.
# View SELinux denials related to Jenkins
grep "avc: denied" /var/log/audit/audit.log | grep jenkins | tail -20
3. Nginx Not Allowed to Connect to Upstream
Nginx forwards requests to http://localhost:8080, but the SELinux boolean httpd_can_network_connect is disabled by default — Nginx cannot make outbound connections even on the same machine.
Solution Approaches
Option 1: Disable SELinux — Fast but Dangerous
Many guides online solve this by setting SELINUX=permissive in /etc/selinux/config. It works quickly, but this is a serious mistake in production. Jenkins runs build scripts from developers — disabling SELinux allows any script to escalate privileges arbitrarily on the server.
Option 2: Run Jenkins in Docker
Fully isolated, no host SELinux concerns. But in practice I’ve seen 2-3 teams hit Docker-in-Docker issues when building images with mounted volumes — debugging that is more painful than configuring SELinux. It adds unnecessary complexity if you just need basic bare-metal Jenkins.
Option 3: Properly Configure SELinux + Nginx from the Start
Takes about 30 minutes to set up but is rock solid. My server has been running stably for 6 months without a single infrastructure-related issue.
The Right Approach: Step-by-Step Guide
Step 1: Prepare the System
sudo dnf update -y
sudo dnf install -y wget curl policycoreutils-python-utils
Step 2: Install Java 17 and Set as Default
This step is most often skipped at the “set default” part — causing Jenkins to still use the old Java 11 even after Java 17 is installed.
# Install Java 17
sudo dnf install -y java-17-openjdk java-17-openjdk-devel
# Set Java 17 as default (interactive — select the corresponding number)
sudo alternatives --config java
# Verify
java -version
# openjdk version "17.x.x" — correct
Step 3: Install Jenkins LTS
# Add Jenkins repo
sudo wget -O /etc/yum.repos.d/jenkins.repo \
https://pkg.jenkins.io/redhat-stable/jenkins.repo
# Import GPG key
sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io-2023.key
# Install Jenkins
sudo dnf install -y jenkins
# Enable and start
sudo systemctl enable jenkins
sudo systemctl start jenkins
sudo systemctl status jenkins
Step 4: Temporarily Open the Firewall for Testing
# Open port 8080 temporarily — will close after Nginx is in place
sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --reload
Step 5: Configure SELinux for Jenkins
The most critical part that the official documentation completely omits.
# Confirm SELinux is in enforcing mode
getenforce
# Enforcing
# Jenkins needs to bind port 8080 — add it to http_port_t
sudo semanage port -a -t http_port_t -p tcp 8080
# If it says "already defined", check the current type
sudo semanage port -l | grep 8080
# If the port is currently a different type (e.g. webcache_port_t), modify it
sudo semanage port -m -t http_port_t -p tcp 8080
# Check Jenkins home context — RPM installation sets this automatically
ls -laZ /var/lib/jenkins
If you change JENKINS_HOME to a different directory, you must relabel it manually:
sudo semanage fcontext -a -t jenkins_home_t "/custom/jenkins/home(/.*)?"
sudo restorecon -Rv /custom/jenkins/home
Step 6: Install and Configure the Nginx Reverse Proxy
sudo dnf install -y nginx
sudo nano /etc/nginx/conf.d/jenkins.conf
Contents of /etc/nginx/conf.d/jenkins.conf:
upstream jenkins {
server 127.0.0.1:8080 fail_timeout=0;
}
server {
listen 80;
server_name jenkins.yourdomain.com;
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_set_header X-Forwarded-Host $host:$server_port;
# Upload size for artifacts
client_max_body_size 100m;
}
}
Step 7: Enable the SELinux Boolean for the Nginx Proxy
The reason Nginx can’t forward requests despite correct configuration — this boolean is disabled by default:
# Allow Nginx to connect to upstream
sudo setsebool -P httpd_can_network_connect 1
# Verify
getsebool httpd_can_network_connect
# httpd_can_network_connect --> on
# Start Nginx
sudo systemctl enable nginx
sudo systemctl start nginx
# Open port 80
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --reload
# Close port 8080 — no longer needed
sudo firewall-cmd --permanent --remove-port=8080/tcp
sudo firewall-cmd --reload
Step 8: Restrict Jenkins to Listen on Localhost Only
With Nginx in place, Jenkins no longer needs to be exposed externally. Add to /etc/sysconfig/jenkins:
sudo nano /etc/sysconfig/jenkins
JENKINS_ARGS="--httpListenAddress=127.0.0.1"
JAVA_OPTS="-Xmx2g -Xms512m"
sudo systemctl restart jenkins
Step 9: Unlock Jenkins for the First Time
# Get the initial admin password
sudo cat /var/lib/jenkins/secrets/initialAdminPassword
Open your browser at http://jenkins.yourdomain.com, enter the password above, install suggested plugins, and create an admin user. Then go to Manage Jenkins → System → Jenkins URL and enter the correct domain — getting this wrong means GitHub/GitLab webhooks won’t work, and you’ll spend half a day debugging it.
What I Learned After 6 Months in Production
My company still has a few CentOS 7 servers, and migrating them to AlmaLinux is a problem I’ve already dealt with. For the new Jenkins server, I chose CentOS Stream 9 — because it’s the direct upstream of RHEL 9. Whatever runs smoothly on Stream 9 will almost certainly pass on RHEL 9 when upgrading later, significantly reducing infrastructure standardization risk.
Some practical takeaways after 6 months of operation:
- Back up JENKINS_HOME regularly: All job configs, build history, and credentials live in
/var/lib/jenkins. I use a nightly cron job to rsync it to S3-compatible storage. Losing this directory means losing your entire CI/CD setup. - Check the audit log before debugging code: When a build job fails unexpectedly, run
grep "avc: denied" /var/log/audit/audit.log | tail -30first. I’ve wasted hours multiple times only to find the root cause was SELinux denying a minor file operation. - Limit the JVM heap: On servers with 4GB RAM or less, Jenkins can consume nearly all of it by default. The
JAVA_OPTS="-Xmx2g -Xms512m"line in Step 8 is not optional. - Never expose Jenkins port 8080 to the internet: Nginx in front + Jenkins bound to localhost is the standard pattern. Jenkins has poor rate limiting — exposing port 8080 is an open invitation to brute force attacks.
This setup is running smoothly for a team of 8, averaging 60-80 builds per day, with zero downtime over the past 6 months. SELinux enforcing and Nginx out front — not wasteful overhead, but necessary barriers for a tool like Jenkins that has the authority to execute arbitrary code.
