GitHub webhooks are a surprisingly brittle way to trigger Jenkins jobs.
Here’s a Jenkins setup that reacts to GitHub pushes:
// Jenkinsfile
pipeline {
agent any
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build') {
steps {
sh 'make build'
}
}
stage('Test') {
steps {
sh 'make test'
}
}
}
}
This Jenkinsfile lives in your GitHub repository. Jenkins clones the repo, then runs make build and make test. GitHub tells Jenkins when code is pushed via a webhook.
To set this up:
- Jenkins Plugin: Install the "GitHub Integration" plugin in Jenkins (Manage Jenkins -> Plugins -> Available plugins). This is the core piece that allows Jenkins to talk to GitHub.
- GitHub Integration: In Jenkins, go to Manage Jenkins -> Configure System. Scroll down to the "GitHub" section. Add a new GitHub server.
- Credentials: Create a GitHub Personal Access Token with
reposcope. Add this as a "Secret text" credential in Jenkins. Select it here. - API URL:
https://api.github.com(for GitHub.com) or your GitHub Enterprise URL. - Webhook secret: This is a shared secret. Generate a strong random string (e.g.,
openssl rand -hex 32). You’ll use this in GitHub and Jenkins.
- Credentials: Create a GitHub Personal Access Token with
- Jenkins Job Configuration:
- Create a new "Pipeline" job in Jenkins.
- General: Check "GitHub hook trigger for GITScm polling."
- SCM: Select "Git."
- Repository URL: Your GitHub repo URL (e.g.,
https://github.com/your-org/your-repo.git). - Credentials: Select the GitHub PAT credential you created.
- Branches to build:
*/main(or your default branch).
- Repository URL: Your GitHub repo URL (e.g.,
- Build Triggers: Check "GitHub hook trigger for GITScm polling."
- Pipeline:
- Definition: "Pipeline script from SCM."
- SCM: "Git."
- Repository URL: Same as above.
- Credentials: Same as above.
- Branches to build:
*/main. - Script Path:
Jenkinsfile(this is the name of your pipeline script file).
- GitHub Repository Webhook:
- Go to your GitHub repository -> Settings -> Webhooks.
- Click "Add webhook."
- Payload URL:
YOUR_JENKINS_URL/github-webhook/(Make sure this is accessible from GitHub.com and ends with a trailing slash). - Content type:
application/json. - Secret: The same webhook secret you generated.
- Which events would you like to trigger this webhook? Select "Just the push event."
Now, when you push to your GitHub repository, GitHub will send a POST request to Jenkins at /github-webhook/. Jenkins, configured with the GitHub Integration plugin and the webhook secret, will process this payload. If the payload indicates a push to a branch that your job is configured to build, Jenkins will trigger a build. The "GitHub hook trigger for GITScm polling" option in Jenkins tells it to look for these incoming webhook events.
The most surprising thing about this entire process is how often the network is the silent killer. GitHub can send the webhook, but if your Jenkins instance isn’t directly accessible via HTTPS from the public internet, or if there’s a firewall rule blocking the POST request, the webhook will simply fail to reach Jenkins. You’ll see delivery failures in the GitHub webhook logs.
Let’s look at a real-time transaction. You push a commit:
git commit -m "feat: add webhook test"
git push origin main
GitHub receives this. It then makes a POST request to your Jenkins webhook URL. The request body looks something like this (simplified):
{
"ref": "refs/heads/main",
"before": "a1b2c3d4e5f6...",
"after": "f6e5d4c3b2a1...",
"pusher": { "name": "your-github-user" },
"repository": {
"full_name": "your-org/your-repo",
"name": "your-repo",
"html_url": "https://github.com/your-org/your-repo"
},
"commits": [
{
"id": "f6e5d4c3b2a1...",
"message": "feat: add webhook test",
"url": "https://github.com/your-org/your-repo/commit/f6e5d4c3b2a1...",
"author": { "name": "your-github-user" }
}
]
}
Jenkins receives this. The GitHub Integration plugin intercepts it. It checks the X-Hub-Signature-256 header against the payload and your configured secret. If it matches, it then parses the ref (e.g., refs/heads/main). It compares this to the branches configured in your job (*/main). If they match, and the job has the "GitHub hook trigger" enabled, it queues the job. The checkout scm step in your Jenkinsfile then uses the configured Git credentials to pull the exact commit after the push.
The most common misconception is thinking Jenkins polls GitHub. It doesn’t; GitHub pushes to Jenkins. This means Jenkins needs to be publicly reachable, or at least reachable from GitHub’s servers. If you’re running Jenkins on a private network, you’ll need something like ngrok for testing, or a more robust solution like a reverse proxy with a publicly accessible IP and SSL termination, or a GitHub Actions workflow that triggers Jenkins via its API.
The part that most people miss is the exact URL Jenkins expects for the webhook. It must end with /github-webhook/ and be over HTTPS. If you omit the trailing slash, or use HTTP instead of HTTPS (which GitHub will reject), the webhook delivery will fail. The GitHub hook trigger for GITScm polling checkbox in the Jenkins job itself is also crucial; without it, Jenkins receives the webhook but doesn’t know to associate it with that specific job.
If your webhooks are failing to deliver, the next thing you’ll likely encounter is Jenkins not picking up changes, leading to stale builds or manual triggers.