Skip to content
TutorialDevOps

How to Run Jenkins Behind Nginx with SSL on a Subdomain

D

Dinesh Wijethunga

September 10, 2026 3 min readBeginner
ShareX / TwitterLinkedIn
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?
A wildcard vhost (*.yourdomain.com) is catching the request. nginx prefers an exact server_name over a wildcard, so create a dedicated vhost with server_name jenkins.yourdomain.com and reload nginx.
Certbot --nginx vs certbot certonly --nginx?
certbot --nginx also EDITS a vhost to install the cert — and on multi-site servers it can pick the wrong one. certonly only issues the certificate; you wire the ssl_certificate lines yourself. On any server with more than one site, use certonly.
Do I need certbot if I'm behind Cloudflare?
If Cloudflare proxies your DNS and your origin already has a wildcard certificate, a new subdomain can reuse it — check your origin cert's SAN list with openssl before issuing anything new.
D
Dinesh Wijethunga

Senior Full Stack Developer · Building SaaS products & teaching Laravel/React · 10+ years experience · Founder of Orion360 · Based in Dubai, UAE.

Was this post helpful?

Add a comment

Comments

Guest comments are held for moderation.

You might also like

Deploy Laravel to a VPS with Laravel Forge: Complete Walkthrough
TutorialIntermediateDevOps

Deploy Laravel to a VPS with Laravel Forge: Complete Walkthrough

Laravel Forge isn't a host — it turns any VPS into a managed Laravel server. Full walkthrough: provisioning, GitHub push-to-deploy, the deploy script, one-click SSL, queue workers, the scheduler, and an honest look at zero-downtime options.

D
Dinesh Wijethunga
6 months ago
6m