How to Run Jenkins Behind Nginx with SSL on a Subdomain

In this tutorial, I will show you step by step how to run Jenkins behind nginx with SSL on its own subdomain — on a server that already hosts a production app on a wildcard domain, which is exactly where the sharp edges are.
Setup: one VPS, an existing Laravel SaaS serving *.example.com (multi-tenant subdomains), and Jenkins on 127.0.0.1:8080 that we want at https://jenkins.example.com.
Step 1 — The nginx vhost (exact name beats wildcard)
Key nginx fact: when a request arrives, an exact server_name always wins over a wildcard. So even though *.example.com would swallow jenkins.example.com, a dedicated vhost takes precedence:
# /etc/nginx/sites-available/jenkins
server {
listen 80;
server_name jenkins.example.com;
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; # long-running build log streams
proxy_read_timeout 90s;
}
}
sudo ln -sf /etc/nginx/sites-available/jenkins /etc/nginx/sites-enabled/jenkins
sudo nginx -t && sudo systemctl reload nginx
Gotcha from the field: if ln says File exists, there's a stale symlink from an earlier attempt shadowing your new config — rm it and re-link. And if the subdomain still shows your main app, the wildcard vhost is winning because your new vhost isn't actually enabled; verify with nginx -T | grep -A2 "server_name jenkins".
Step 2 — TLS. Read this before running certbot
Here is the mistake, so you don't repeat it. I ran the "friendly" installer mode:
certbot --nginx -d jenkins.example.com # ← DON'T, on a multi-site server
certbot issued the certificate fine — and then deployed it into the wrong vhost: the production wildcard site. Its ssl_certificate lines suddenly pointed at the Jenkins cert, and every tenant subdomain on *.example.com started throwing certificate-mismatch errors. Production TLS, broken by a CI chore.
The fix was to point those two lines back at the wildcard cert and reload — but the lesson is permanent:
# On any server with more than one vhost: issue only, wire manually
sudo certbot certonly --nginx -d jenkins.example.com
Then add the 443 block yourself, where you decide which file changes:
server {
listen 443 ssl;
server_name jenkins.example.com;
ssl_certificate /etc/letsencrypt/live/jenkins.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/jenkins.example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
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;
}
}
The Cloudflare shortcut: maybe you need no cert at all
If Cloudflare proxies your zone, check what your origin certificate already covers before issuing anything:
echo | openssl s_client -servername app.example.com -connect YOUR_SERVER_IP:443 2>/dev/null \
| openssl x509 -noout -ext subjectAltName
# X509v3 Subject Alternative Name:
# DNS:*.example.com, DNS:example.com
A wildcard SAN like that means any new first-level subdomain can simply reuse the existing cert files in its 443 block — zero certbot, zero new renewals, and Cloudflare's edge certificate covers the browser side. We used exactly this later in the series for the staging vhost.
Step 3 — Lock the box down
# Jenkins should only be reachable through nginx now
sudo ufw deny 8080/tcp
Also set Manage Jenkins → System → Jenkins URL to https://jenkins.example.com/ so redirects and webhook URLs generate correctly.
Next in the series: post #5 connects Jenkins to GitHub — per-repo deploy keys, a minimal fine-grained PAT, the webhook, and the 403 that breaks multibranch scanning when your token is (correctly) minimal.
Frequently Asked Questions
Why does my Jenkins subdomain show my main app instead of Jenkins?
Certbot --nginx vs certbot certonly --nginx?
Do I need certbot if I'm behind Cloudflare?
Senior Full Stack Developer · Building SaaS products & teaching Laravel/React · 10+ years experience · Founder of Orion360 · Based in Dubai, UAE.
Was this post helpful?



