Jenkins Error: 'HTTP ERROR 403 No valid crumb was included in the request' — Cause, Fix, and Troubleshooting Guide
Fix Jenkins 'HTTP ERROR 403 No valid crumb was included in the request': fetch a CSRF crumb, use API tokens, and stop your reverse proxy stripping headers.
- #jenkins
- #ci-cd
- #troubleshooting
- #errors
Fixing errors like this? Get 500 free DevOps AI prompts
500 copy-paste AI prompts for the stack you actually run — one PDF, free.
Overview
Jenkins rejected a state-changing HTTP request because it did not carry a valid CSRF token — a “crumb”. Jenkins protects POST endpoints (triggering builds, creating jobs, running Script Console, most REST/CLI writes) with cross-site request forgery prevention: the client must present a crumb tied to its session or authentication. Without it you get:
HTTP ERROR 403 No valid crumb was included in the request
URI: /job/my-app/build
STATUS: 403
This is a security control working as designed, not a broken Jenkins. The fix is to obtain a crumb from the crumb issuer and send it (or use an API token, which the modern crumb issuer honors), and to make sure nothing between your client and Jenkins — a reverse proxy, a load balancer, a caching layer — is stripping the header or breaking session affinity.
Symptoms
- Scripted
curl/REST calls that POST to Jenkins return403 No valid crumb was included in the request. jenkins-cliover HTTP fails with a 403 while the same operation works in the web UI.- Automation that worked before breaks after an upgrade, a proxy change, or enabling CSRF protection.
- The request succeeds intermittently — working on one node of a Jenkins cluster and failing on another.
- Fetching the crumb succeeds, but the follow-up POST still 403s (the crumb and the POST used different sessions).
Common Root Causes
- No crumb sent at all — the client POSTs directly without first requesting a crumb from the crumb issuer.
- Crumb/session mismatch — the crumb was fetched in one HTTP session but the POST was made in another, so the crumb no longer matches (very common when cookies are not persisted between the two calls).
- Reverse proxy strips the crumb header — nginx/Apache/HAProxy in front of Jenkins drops or rewrites the
Jenkins-Crumbheader, so it never reaches Jenkins. - No sticky sessions behind a load balancer — the crumb GET hits one Jenkins/backend and the POST hits another; without session affinity the crumb is not recognized.
- Using a password instead of an API token for automation — session-based auth needs the crumb dance; an API token is the supported path for scripts.
- Proxy compatibility setting mismatch — the crumb is bound to the client IP but the proxy presents a different source address on the second request.
How to diagnose
Confirm CSRF protection is on and check its settings under Manage Jenkins → Security → CSRF Protection. Then reproduce the crumb fetch directly against Jenkins.
Fetch a crumb from the crumb issuer API and see what header name Jenkins expects:
# Ask the crumb issuer for a crumb (name:value)
curl -s -u "user:<api-token>" \
"https://jenkins.example.com/crumbIssuer/api/json"
# => {"crumb":"<crumb-value>","crumbRequestField":"Jenkins-Crumb", ...}
Verify the header actually reaches Jenkins (i.e. the proxy is not stripping it) by checking the controller access/log or echoing headers at the proxy. From the Script Console you can confirm which crumb issuer is active:
// Which crumb issuer is configured, and is it proxy-compatible? (Script Console)
def issuer = Jenkins.instance.crumbIssuer
println issuer?.class?.name
if (issuer instanceof hudson.security.csrf.DefaultCrumbIssuer) {
println "excludeClientIPFromCrumb = ${issuer.isExcludeClientIPFromCrumb()}"
}
If POSTs still fail, check whether the load balancer keeps you on one backend:
# Are the crumb GET and the POST hitting the same backend? Compare Set-Cookie / backend headers.
curl -sI -u "user:<api-token>" https://jenkins.example.com/crumbIssuer/api/json | grep -i 'set-cookie\|x-backend'
Fixes
1. Fetch a crumb and reuse the same session for the POST
The reliable pattern is: GET the crumb and the POST in one cookie jar so the session matches. With curl:
# Persist cookies so the crumb and the POST share one session
CRUMB=$(curl -s -c cookies.txt -u "user:<api-token>" \
'https://jenkins.example.com/crumbIssuer/api/json' \
| python3 -c 'import sys,json;print(json.load(sys.stdin)["crumb"])')
curl -s -b cookies.txt -u "user:<api-token>" \
-H "Jenkins-Crumb: ${CRUMB}" \
-X POST 'https://jenkins.example.com/job/my-app/build'
The -c/-b cookies.txt is the important part — without a shared cookie jar the crumb is bound to a session you throw away.
2. Use an API token (the supported path for automation)
Generate a per-user API token under your user → Security → API Token → Add new Token, and authenticate with it. Modern Jenkins accepts an API token on the REST API without a separate crumb for token-authenticated requests in many endpoints; where a crumb is still required, combine both as above.
# jenkins-cli authenticated with an API token
java -jar jenkins-cli.jar -s https://jenkins.example.com/ \
-auth user:<api-token> build my-app -f -s
Prefer the SSH CLI transport for jenkins-cli where possible — it does not go through the HTTP CSRF path at all:
java -jar jenkins-cli.jar -s https://jenkins.example.com/ \
-ssh -user user build my-app
3. Stop the reverse proxy stripping the crumb header
Ensure the proxy passes the crumb header and the Cookie/Authorization headers through unchanged. For nginx:
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_pass_request_headers on; # do not drop Jenkins-Crumb
proxy_buffering off;
}
Underscore-containing headers are rarely an issue here (Jenkins-Crumb uses hyphens), but if you use a custom header, remember nginx drops underscored headers unless underscores_in_headers on;.
4. Make the crumb tolerant of proxied client IPs
If the proxy presents a different source IP on the second request, the default issuer’s IP binding rejects the crumb. Either enable sticky sessions on the load balancer, or configure the crumb issuer to exclude the client IP. Via Configuration as Code:
jenkins:
crumbIssuer:
standard:
excludeClientIPFromCrumb: true
Excluding the client IP is the standard fix behind a proxy/load balancer. Do not disable CSRF protection wholesale — that removes the control instead of fixing the transport.
What to watch out for
- Never “fix” this by turning off CSRF protection. It defeats a real security control; the correct fix is sending the crumb or using a token.
- The crumb header field name is not always
Jenkins-Crumb— readcrumbRequestFieldfrom the crumb issuer response and use exactly that. - A crumb without its session is worthless. If you fetch and POST in separate processes, carry the cookie too, or use API-token auth.
- Behind a load balancer, enable session affinity (sticky sessions) or
excludeClientIPFromCrumb, otherwise POSTs will fail non-deterministically. - API tokens are per-user and revocable — scope them to a service account, never embed a human password in automation.
- The Jenkins guides hub collects related auth and pipeline fixes.
Related
- Jenkins Error: Cannot find credentials with id — the other common authentication failure in Jenkins automation.
- Jenkins Error: scripts not permitted to use method — another Jenkins security gate (script approval) that blocks automation until configured.
- Jenkins Error: hudson.remoting.ChannelClosedException — proxy/transport issues that also break controller connectivity.
More Jenkins prompts & error guides
Every Jenkins AI prompt and troubleshooting guide, in one place.
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.