Jenkins Build Triggers: From cron H() to Webhooks (Pitfalls Inside)
Master Jenkins build triggers: cron H hash syntax, pollSCM, webhooks, upstream triggers, Generic Webhook plugin.
20+ years shipping production infrastructure and CI/CD at scale. Lessons pulled from things that broke in production.
- ✓Solid grasp of devops fundamentals
- ✓Comfortable reading and writing code examples independently
- ✓Basic understanding of production deployment concepts
- Use cron with H/15 syntax to spread load; never use absolute minutes like */5.
- Prefer webhooks (GitHub/GitLab/Bitbucket) over pollSCM to avoid SCM load and latency.
- Upstream triggers filter by result (SUCCESS, UNSTABLE, etc.) to avoid cascading failures.
- Generic Webhook Trigger plugin supports JSONPath filtering for fine-grained control.
- In Declarative Pipeline, triggers {} block supports cron, pollSCM, upstream; webhooks require separate plugin config.
- Trigger ordering: triggers defined first in Pipeline take precedence; freestyle triggers can conflict.
- Avoid using @daily or @midnight; use H 0 * to prevent midnight stampede.
- Always test webhook triggers with curl before relying on them in production.
Imagine a coffee shop that makes coffee only when someone orders. Jenkins build triggers are like the order bell. A cron trigger is like a timed alarm that rings every hour to ask 'any orders?' – but if the alarm rings exactly on the hour, everyone rushes at once. The H hash syntax spreads those alarms randomly within the hour so the barista isn't overwhelmed. Webhook triggers are like a direct call from the customer saying 'order now!' – much faster and more efficient. Upstream triggers are like the barista starting a new coffee as soon as the previous one is done. And the Generic Webhook Trigger plugin is like a smart phone that can filter calls – only accept orders for a specific drink.
| Chrome | Firefox | Safari | Edge |
|---|---|---|---|
| ✓ | ✓ | ✓ | ✓ |
I once had a Jenkins master grind to a halt every midnight. The culprit? A @daily cron trigger on a hundred jobs. They all fired at exactly 00:00, hammering the SCM and database. That night, I learned the H hash syntax the hard way. Since then, I've battled misconfigured webhooks, cascading upstream failures, and silent pollSCM timeouts. This article distills those scars into a practical guide on Jenkins build triggers – the good, the bad, and the gotchas.
Triggers are the heartbeat of CI/CD – they decide when pipelines run. But with great power comes great foot-gunning. A wrong cron expression can bring down your master; a missing webhook secret can let anyone trigger builds. In production, you need to understand every trigger type, their quirks, and how to debug them when they go silent.
I'll walk through each trigger type: cron with H syntax (the lifesaver), pollSCM (the resource hog), upstream triggers (the dependency chain), and webhooks (the real-time heroes). We'll cover the Generic Webhook Trigger plugin – a swiss army knife – and parameterized triggers for manual runs. Finally, I'll share debugging techniques and common mistakes that have burned me.
By the end, you'll not only configure triggers correctly but also know how to diagnose why a build didn't start when it should have. Let's dive into the trenches.
1. Cron Trigger with H Hash Syntax
The cron trigger is the most common time-based trigger. In Jenkins, the syntax follows standard cron with five fields: MINUTE HOUR DOM MONTH DOW. However, Jenkins extends it with the H (hash) symbol to distribute load. For example, H/15 means 'every 15 minutes, but starting at a random minute within the hour'. This prevents all jobs from polling at the same time.
In production, I always use H for the minute field. Never use /5 or 0 * because that causes thundering herd. The H symbol computes a hash of the job name to determine the offset. So the same job always gets the same offset, but different jobs get different offsets. This is crucial when you have hundreds of jobs.
Common patterns: H (once per hour at a random minute), H/15 (four times per hour spread), H 0 (once per day between midnight and 1 AM). Avoid @daily, @hourly, etc. because they are absolute. Also avoid H H because the hour hash might conflict.
One gotcha: cron triggers in Declarative Pipeline are evaluated when the pipeline is loaded. If you change the cron expression, you need to run the pipeline once to register the new trigger. Also, cron expressions are evaluated based on the master's timezone, so be mindful of DST changes.
Code example in Declarative Pipeline: ``groovy pipeline { agent any triggers { cron('H/15 ') } stages { stage('Build') { steps { echo 'Building...' } } } } ``
For freestyle jobs, you set cron in the 'Build Triggers' section. The same H syntax applies. I recommend using the 'Poll SCM' option with cron only if you can't use webhooks; otherwise, it's wasteful.
2. pollSCM Trigger
The pollSCM trigger periodically checks the SCM for changes. It's a fallback when webhooks aren't feasible (e.g., behind a firewall). But it's resource-intensive: each poll hits the SCM server, and if you have many jobs, it can cause load. The syntax is the same as cron: pollSCM('H/5 ') means every 5 minutes (spread).
In Declarative Pipeline, the pollSCM trigger is defined inside triggers{} block. But note: pollSCM only works if the pipeline has an SCM checkout step that Jenkins can track. If you use 'checkout scm' or 'git' step, Jenkins knows the SCM URL and credentials. Otherwise, polling won't detect changes.
One major gotcha: pollSCM does not work with Pipeline Multibranch jobs because those already have built-in webhook support. For Multibranch, use webhooks or folder-level triggers.
In production, I use pollSCM only as a backup for webhooks. For example, if webhook fails, pollSCM will eventually catch the change. But set a longer interval (e.g., H/10 ) to reduce load. Also, always check the poll log (available in job page) to see if polling is working.
Code example: ``groovy pipeline { agent any triggers { pollSCM('H/5 ') } stages { stage('Checkout') { steps { checkout scm } } } } ``
Freestyle jobs have a separate 'Poll SCM' checkbox and cron field. The same H syntax applies.
3. Upstream Trigger with Result Filtering
The upstream trigger starts a build when another job completes. You can specify which upstream jobs to watch and filter by their result: SUCCESS, UNSTABLE, FAILURE, NOT_BUILT, or ABORTED. This is useful for chaining pipelines: e.g., after deploy, run integration tests.
In Declarative Pipeline, the syntax is: ``groovy triggers { upstream(upstreamProjects: 'jobA,jobB', threshold: hudson.model.Result.SUCCESS) } `` The threshold enum can be SUCCESS, UNSTABLE, FAILURE, etc. You can also use the string version: 'SUCCESS'.
One common pitfall: the upstream job name must be exact, including case and folder path. If the job is in a folder, use 'folder/jobname'. Also, the upstream trigger only fires once per upstream build, even if multiple upstream jobs complete. If you need to wait for multiple upstreams, use the 'Build after other projects are built' option in freestyle, or use a separate orchestrator.
In production, I use upstream triggers to create dependency chains. But I always set a threshold to avoid cascading failures. For example, trigger integration tests only if deploy succeeded. Also, be careful with circular dependencies – Jenkins will detect them and not trigger.
Freestyle jobs have a 'Build after other projects are built' option. You can specify project names and threshold. It's simpler but less flexible than pipeline.
Code example: ``groovy pipeline { agent any triggers { upstream(upstreamProjects: 'deploy-job', threshold: hudson.model.Result.SUCCESS) } stages { stage('Test') { steps { echo 'Running integration tests...' } } } } ``
4. GitHub Webhook Trigger
GitHub webhook trigger is the most common SCM webhook. It uses the GitHub plugin or the native GitHub Branch Source plugin for Multibranch pipelines. When you push to GitHub, Jenkins receives a POST and triggers the appropriate job.
For freestyle jobs, you need the 'GitHub plugin' and configure 'Build when a change is pushed to GitHub' in triggers. You also need to set up a webhook in GitHub pointing to http://jenkins:8080/github-webhook/. The secret is shared between GitHub and Jenkins.
For Pipeline Multibranch, it's simpler: just enable 'Build on push events' in the branch source configuration. Jenkins automatically registers a webhook with GitHub. But you need to ensure Jenkins URL is correct and firewall allows inbound.
One common issue: the webhook fires but the wrong job triggers. This happens when multiple jobs listen to the same repository. Use the 'GitHub SCM' filter or restrict by branch. Also, the GitHub plugin has a 'GitHub hook log' in job configuration to debug.
In production, I always verify webhook delivery in GitHub's settings. The 'Recent Deliveries' tab shows payload and response. A 200 response from Jenkins means success; a 404 means wrong URL or missing plugin.
Code example (Declarative Pipeline Multibranch): ``groovy pipeline { agent any triggers { // No explicit trigger needed; Multibranch handles it } stages { stage('Build') { steps { echo 'Building...' } } } } ``
For freestyle, just check the trigger checkbox.
5. Bitbucket Webhook Trigger
Bitbucket webhook trigger works similarly to GitHub but uses the Bitbucket plugin. For freestyle jobs, check 'Build when a change is pushed to Bitbucket' and configure a webhook in Bitbucket pointing to http://jenkins:8080/bitbucket-hook/. For Multibranch, use the Bitbucket Branch Source plugin.
One difference: Bitbucket sends different payloads than GitHub. The plugin handles parsing, but you may need to configure 'Bitbucket Server' or 'Bitbucket Cloud' depending on your version. Also, Bitbucket Cloud uses a different endpoint: /bitbucket-hook/ for Cloud, /bitbucket-server-hook/ for Server.
In production, I've seen issues with Bitbucket webhooks not triggering because of incorrect credentials or missing 'Repository access' in Jenkins. Also, Bitbucket Server requires a personal access token with read permissions.
Debugging: Check the Bitbucket plugin log in Jenkins system log. Also, use the 'Bitbucket Hook Log' in job configuration. If the webhook is received but no build triggers, check branch filters.
Code example (freestyle trigger config): ``groovy // Not applicable; done via UI ``
For Multibranch: ``groovy pipeline { agent any triggers { // Built-in } stages { stage('Build') { steps { echo 'Building...' } } } } ``
6. GitLab Webhook Trigger
GitLab webhook trigger uses the GitLab plugin. For freestyle jobs, check 'Build when a change is pushed to GitLab' and configure a webhook in GitLab pointing to http://jenkins:8080/project/<jobname>. For Multibranch, use the GitLab Branch Source plugin.
One unique feature: GitLab webhooks can trigger on merge requests, tags, and pushes. The plugin allows filtering by branch name, merge request status, etc. You can also use the 'GitLab CI' integration for more advanced scenarios.
In production, I've found that GitLab webhooks require the 'Secret token' to be configured both in GitLab and Jenkins. Without it, Jenkins rejects the request. Also, the job name in the URL must match exactly, including folders.
Debugging: Check GitLab's 'Webhook' settings for recent deliveries. Jenkins logs show incoming requests. If you see 403 errors, check token.
Code example (freestyle trigger config): ``groovy // UI configuration ``
For Multibranch: ``groovy pipeline { agent any triggers { // Built-in } stages { stage('Build') { steps { echo 'Building...' } } } } ``
7. Generic Webhook Trigger Plugin
The Generic Webhook Trigger plugin is the swiss army knife of webhooks. It allows Jenkins to receive any HTTP POST and trigger a job based on JSONPath or XPath expressions. You can extract variables from the payload and pass them as build parameters.
Configuration: In freestyle, add 'Generic Webhook Trigger' as a build trigger. Set a token (shared secret), specify JSONPath filters, and map variables. For Pipeline, you can use the 'GenericTrigger' step.
One powerful feature: you can filter by branch, file path, or any payload field. For example, only trigger if the push includes changes to 'src/**'. This avoids unnecessary builds.
In production, I use this for custom integrations, like triggering from a monitoring system or a manual approval tool. The token ensures only authorized requests trigger builds.
Code example (Declarative Pipeline): ``groovy pipeline { agent any triggers { GenericTrigger( genericVariables: [ [key: 'ref', value: '$.ref'] ], token: 'my-secret', causeString: 'Triggered by $ref', printContributedVariables: true, printPostContent: true, silentResponse: false ) } stages { stage('Build') { steps { echo "Ref: ${env.ref}" } } } } ``
You can also use 'regexpFilterText' and 'regexpFilterExpression' to filter based on content.
8. Parameterized Trigger
Parameterized triggers allow you to pass parameters when triggering another job. This is different from upstream triggers: you explicitly call another job with parameters. It's done via the 'Parameterized Trigger' plugin, which adds a post-build action or build step.
In Pipeline, you can use the 'build' step: ``groovy build job: 'downstream', parameters: [ string(name: 'BRANCH', value: 'main'), booleanParam(name: 'DEPLOY', value: true) ] `` This triggers 'downstream' with those parameters. You can also wait for the downstream job to complete using 'wait: true'.
One common use case: after building a Docker image, trigger a deployment job with the image tag. The parameterized trigger ensures the deployment job knows which version to deploy.
In production, I use parameterized triggers to create fan-out patterns: one build triggers multiple downstream jobs with different parameters. But be careful with circular dependencies and resource usage.
Freestyle jobs: add 'Trigger parameterized build on other projects' as a post-build action. You can specify parameters via predefined properties or from file.
Code example: ``groovy pipeline { agent any stages { stage('Build') { steps { script { build job: 'deploy', parameters: [ string(name: 'VERSION', value: '1.2.3') ], wait: false } } } } } ``
9. Triggers in Declarative Pipeline: The triggers{} Block
In Declarative Pipeline, triggers are defined inside the 'triggers' block at the top level, before stages. You can combine multiple triggers: cron, pollSCM, upstream, and GenericTrigger. However, note that some triggers (like webhooks) are configured via the job configuration UI or branch source, not in the pipeline itself.
Syntax: ``groovy pipeline { agent any triggers { cron('H 0 ') pollSCM('H/10 *') upstream(upstreamProjects: 'jobA', threshold: hudson.model.Result.SUCCESS) } stages { stage('Build') { steps { echo 'Building...' } } } } ``
The triggers block is evaluated when the pipeline is loaded. If you change triggers, you need to run the pipeline once (or reload from SCM) for the changes to take effect. Also, triggers are only active if the pipeline is saved with the triggers block.
One gotcha: if you define triggers in the pipeline but also have triggers configured in the job UI (for freestyle), the pipeline triggers take precedence? Actually, for Pipeline jobs, the pipeline script defines triggers; UI triggers are ignored. For Multibranch, triggers are configured in the branch source.
In production, I always define triggers in the pipeline script for version control. This ensures consistency across environments. However, for webhooks, I still rely on UI configuration because the pipeline doesn't know the Jenkins URL or secret.
Best practice: Use a shared library to standardize trigger configurations across pipelines.
10. Trigger Ordering and Priority
When multiple triggers are configured, which one fires first? Jenkins doesn't have a strict priority; each trigger evaluates independently and can start a build. If a build is already running, the trigger will queue a new build (unless 'Do not trigger if a build is running' is set).
In practice, if you have both cron and webhook, a push may trigger a build, and then a cron may trigger another build immediately after. This can lead to duplicate builds. To avoid this, use a 'quiet period' to coalesce changes. The quiet period is a delay before the build starts, allowing multiple triggers to be consolidated.
In freestyle, you can set 'Quiet period' in job configuration. In Pipeline, you can set 'options { quietPeriod(5) }'.
Another aspect: trigger evaluation order. Jenkins evaluates triggers in the order they appear in the config? Not exactly. It's better to rely on one primary trigger and disable others. For example, if you have webhook, disable pollSCM to avoid polling.
In production, I use the following priority: webhook > upstream > cron. I disable pollSCM entirely if webhook is available. This minimizes resource usage and duplicate builds.
Also, be aware of 'Build trigger log' in Jenkins UI. It shows which trigger caused the build. Use it to debug ordering issues.
11. Configuring Triggers in Freestyle vs Pipeline
Freestyle jobs have a dedicated 'Build Triggers' section in the UI. You can check boxes for various triggers and fill in cron expressions, upstream jobs, etc. This is simple but not version-controlled.
Pipeline jobs (Declarative or Scripted) define triggers in code. This is better for reproducibility. However, some triggers (like webhooks) still require UI configuration for the webhook endpoint. For example, GitHub webhook in Pipeline Multibranch is configured in the folder or job configuration.
- Freestyle: triggers configured via UI; easy for non-developers.
- Pipeline: triggers in code; version-controlled, but requires code change for trigger modification.
- Multibranch: triggers configured in branch source; cannot use triggers{} block.
In production, I prefer Pipeline with triggers in code for all triggers that can be expressed (cron, pollSCM, upstream, GenericTrigger). For webhooks, I use the branch source or job configuration UI but document the webhook URL and token.
One gotcha: if you convert a freestyle job to pipeline, the triggers from UI are ignored. You must define them in the pipeline script.
Code comparison: Freestyle: UI checkboxes. Pipeline: ``groovy triggers { cron('H 0 *') } ``
12. Build Trigger Best Practices and Common Pitfalls
After years of managing Jenkins, here are my hard-earned best practices: 1. Always use H hash syntax for cron. Never use absolute minutes. 2. Prefer webhooks over pollSCM. Webhooks are real-time and reduce load. 3. For upstream triggers, always set a result threshold to avoid cascading failures. 4. Use Generic Webhook Trigger for custom integrations; secure it with a strong token. 5. Avoid combining multiple triggers for the same job unless necessary. Use quiet period to coalesce. 6. Version-control your pipeline triggers. Don't rely on UI configuration alone. 7. Test webhooks with curl before relying on them in production. 8. Monitor trigger logs and set up alerts for missed triggers.
- Using @daily causing midnight stampede.
- Forgetting to update webhook URL after Jenkins migration.
- Using pollSCM on a large repository without throttling.
- Not filtering upstream results, causing downstream builds on failures.
- Exposing webhook tokens in pipeline code (use credentials binding).
- Ignoring quiet period, leading to duplicate builds.
In production, I also recommend using a global 'quiet period' default in Jenkins system configuration. This adds a small delay to all builds, allowing triggers to settle.
Finally, always document trigger configurations. A wiki page with webhook URLs, tokens, and cron expressions saves hours of debugging.
The Midnight Stampede: How @daily Took Down Jenkins
- Never use absolute cron expressions.
- Always use H hash syntax to spread load.
- Use @midnight only if you want to crash your master.
Print-friendly master reference covering all topics in this track.
Key takeaways
Common mistakes to avoid
6 patternsUsing absolute cron minutes like */5 instead of H/5
Configuring both webhook and pollSCM for the same job
Not setting result threshold on upstream trigger
Using @daily or @midnight cron
Storing webhook token in plain text in pipeline code
Not testing webhook with curl before production
Interview Questions on This Topic
What is the H hash syntax in Jenkins cron triggers and why is it important?
Frequently Asked Questions
20+ years shipping production infrastructure and CI/CD at scale. Lessons pulled from things that broke in production.
That's Jenkins. Mark it forged?
11 min read · try the examples if you haven't