Secure Jenkins with Pomerium
What this guide does
You'll put Jenkins behind Pomerium so that Pomerium handles single sign-on and authorization, then forwards a cryptographically signed identity JWT to Jenkins. The Jenkins JWT Auth plugin verifies that assertion against Pomerium's public keys and signs the user in automatically, so there's no second login and no Jenkins-managed password to store or share.
Jenkins verifies the assertion by fetching Pomerium's public keys from a JSON Web Key Set (JWKS) endpoint that Pomerium publishes, so the rest of this guide refers to that JWKS URL.
When to use this guide
Use it when you want one front door for Jenkins with your existing identity, and you want Jenkins to trust Pomerium for authentication instead of running its own user database. Jenkins has no built-in JWT support, so this flow depends on the JWT Auth plugin. If you only need to reach Jenkins over a private network without browser SSO, a plain TCP route is a better fit.
Prerequisites
This guide assumes you've completed the Quickstart, so you already have Pomerium running and signing users in through the hosted authenticate service.
You also need:
- Docker and Docker Compose
- A domain you control for the Jenkins route (this guide uses
jenkins.yourdomain.com)
This guide uses the hosted authenticate service so you don't have to run an IdP. To run your own instead, follow Keycloak + Pomerium and swap the authenticate_service_url / idp_* settings into the config below.
Configure Pomerium
- Pomerium Zero
- Pomerium Core
In the Zero Console:
-
Create a Route. In From, enter
https://jenkins.<your-starter-domain>; in To, enterhttp://jenkins:8080. -
Set the policy to Any Authenticated User (or scope it to a group or domain). To restrict by email domain, create a policy with an ALLOW block matching the Domain criteria.

-
On the Headers tab, enable Pass Identity Headers, then save the route.
Zero manages the route's TLS certificate and the signing key behind its starter domain, so the JWT Auth plugin's JWKS URL is your authenticate service: https://authenticate.<your-starter-domain>/.well-known/pomerium/jwks.json. Use that value for the JWKS JSON URL in the next section.
Create a config.yaml. It routes jenkins.yourdomain.com to the Jenkins container, passes identity headers, and sets a signing_key so Pomerium publishes a JWKS that the JWT Auth plugin can verify the forwarded assertion against.
# Pomerium Core configuration for Jenkins. Uses the hosted authenticate service, so
# you don't run your own identity provider. To self-host the IdP, see the Keycloak
# guide: https://www.pomerium.com/docs/integrations/user-identity/oidc
authenticate_service_url: https://authenticate.pomerium.app
# Obtain TLS certificates automatically from Let's Encrypt.
autocert: true
# Signs the identity assertion Pomerium forwards to Jenkins, and is published at the
# route's /.well-known/pomerium/jwks.json for the JWT Auth plugin to verify against.
# Generate your own and keep it secret:
# openssl ecparam -genkey -name prime256v1 -noout | base64
signing_key: REPLACE_WITH_BASE64_ENCODED_EC_P256_PRIVATE_KEY
routes:
- from: https://jenkins.yourdomain.com
to: http://jenkins:8080
pass_identity_headers: true
policy:
- allow:
or:
- email:
is: you@example.com
Replace jenkins.yourdomain.com with your domain, you@example.com with your email, and generate your own signing_key with the command in the comment. With Core, the JWKS URL is the route's own well-known endpoint (https://jenkins.yourdomain.com/.well-known/pomerium/jwks.json).
Configure Jenkins
Jenkins doesn't read a JWT out of the box, so install the JWT Auth plugin and point it at Pomerium. After the stack is running and you've completed the Jenkins first-run wizard:
-
Go to Manage Jenkins -> Plugins -> Available plugins, search for JWT Auth Plugin, install it, and restart Jenkins.
-
Go to Manage Jenkins -> Security. In the Security Realm dropdown, select JWT Header Authentication Plugin and fill in the fields:
Field Value Header name X-Pomerium-Jwt-AssertionUsername claim name emailEmail claim name emailFull Name claim name nameGroups claim name groupsGroups claim list separator ,Acceptable issuers your route host Acceptable audiences your route host JWKS JSON URL the JWKS URL for your setup (see the tab above) Leeway seconds 30(clock-skew tolerance when validatingexp/nbf)Pomerium sets the JWT's
issandaudto the route host, so use the host from your From URL:jenkins.<your-starter-domain>on Zero, orjenkins.yourdomain.comon Core. -
Leave Allow verification failures unchecked once the flow works. While you're first wiring it up, you can enable it temporarily so a misconfiguration doesn't lock you out, then turn it off so only verified Pomerium requests are trusted.
-
Under Authorization, pick a strategy. Logged-in users can do anything is a reasonable start, since Pomerium's route policy is the real access gate; tighten it with matrix-based security as needed. With Matrix-based security, click Add user... and grant the signed-in user the permissions they need (for example, Administer) so you aren't locked out.

Save and restart Jenkins.
Run the stack
The Compose file runs Pomerium Core and Jenkins together (for Zero, drop the pomerium service and use the compose.yaml from the Quickstart with your POMERIUM_ZERO_TOKEN, keeping the jenkins service below):
services:
pomerium:
image: pomerium/pomerium@sha256:e10d1d267af24f581157f485d9b0bc08469e2428675b696a08e42ceb09b2279c # v0.32.7
volumes:
- ./config.yaml:/pomerium/config.yaml:ro
- pomerium-cache:/data
ports:
- 443:443
- 80:80
restart: always
jenkins:
image: jenkins/jenkins@sha256:01c992ffef29dcf41c7164e8c16285c657c2368c4943dda8d68c93fdf54447d5 # lts (2.555.2)
# Jenkins is reached only through Pomerium, so no host ports are published.
# 8080 stays on the internal Docker network. You complete the first-run
# setup wizard and install the JWT Auth plugin through the browser, as the
# guide describes.
volumes:
- jenkins-home:/var/jenkins_home
restart: always
volumes:
pomerium-cache:
jenkins-home:
Start it:
docker compose up -d
Jenkins prints an initial admin password to the container log on first start (docker compose logs jenkins), which you'll need to complete the setup wizard the first time you reach it through Pomerium.
Verify the setup
-
The route requires authentication. In a fresh browser, open
https://jenkins.yourdomain.com. You should be redirected to sign in, not straight into Jenkins. -
An allowed user gets in. Sign in. Pomerium redirects you back to Jenkins.
-
JWT SSO works. Once the JWT Auth plugin is configured, Jenkins signs you in automatically as the identity in the assertion. The top-right corner shows your account, and the Who Am I page (
/whoAmI) reports your email under Name and IsAuthenticated?: true.
-
A disallowed user is blocked. Sign in as a user your policy excludes and open
https://jenkins.yourdomain.com. Pomerium denies access, so no JWT assertion is forwarded and you never reach Jenkins.
Common failure modes
| Symptom | Cause | Fix |
|---|---|---|
| Jenkins shows its own login form instead of signing you in | The assertion didn't verify | Confirm the JWKS JSON URL is reachable from the Jenkins container, and that Pomerium has a signing_key (Core) so the JWKS isn't empty |
unable to find valid certification path to requested target in the Jenkins log | Jenkins (the Java runtime) doesn't trust the certificate on the JWKS endpoint | With the hosted authenticate service or a public certificate this won't happen; if you front Jenkins with a private CA, import that CA into the Java truststore |
Unable to find a suitable verification key for the JSON Web Signature (JWS) | Empty or unreachable JWKS: no published signing key, or the plugin can't fetch it | Set a signing_key (Core) and confirm the JWKS URL resolves from inside the container |
| Redirect loop or certificate errors | DNS or autocert | Make sure DNS for jenkins.yourdomain.com points at Pomerium and that Pomerium can get a TLS certificate (Core needs ports 80/443 open for autocert; Zero manages it) |
Security considerations
- The JWT Auth plugin trusts any request carrying an assertion it can verify, so don't expose Jenkins directly. Only Pomerium should reach
jenkins:8080: keep it off published host ports and on the internal Docker network. If Jenkins is reachable directly, an attacker who can craft or replay an accepted header bypasses Pomerium entirely. - Keep Allow verification failures off in production. It's a recovery hatch while configuring the plugin, not a steady state: with it on, Jenkins accepts requests whose JWT didn't verify.
- Scope the route policy (group or domain) to who should have access, and manage Jenkins permissions (matrix-based security) separately for fine-grained authorization.
Operations
- Tear the stack down with
docker compose down. Add-vto also delete thejenkins-homeandpomerium-cachevolumes, which resets Jenkins (including its JWT configuration) to a fresh first run. - Rotating the
signing_keyre-signs assertions under a new key. After rotation, Jenkins picks up the new key on its next JWKS fetch; restart Jenkins if you want it to re-fetch immediately. - Last tested with Jenkins LTS (
jenkins/jenkins:lts, 2.555.2), the JWT Auth plugin, and Pomerium Core v0.32.7.