Triggers & webhooks
Build on push, on schedule, on demand.
You push a one-line fix, tab over to Slack, and start typing 'deploying now'. On a well-wired Jenkins the build is already running before you finish the sentence. On a badly-wired one you sit refreshing the job page for four minutes, wondering whether anything even noticed. The difference is the trigger: the rule that decides when a pipeline starts. It works like the doorbell wiring on a house. Jenkins never runs a job on a hunch, so something has to press the button. This lesson covers the three automated ways to press it, all of them declared in code: a push notification (a webhook), a wall-clock schedule (cron), and a periodic check (polling). It also covers the humans and upstream jobs that round them out, and exactly how a webhook-driven build fires the instant a commit lands.
Three automatic ways to start a build, plus two by hand
A trigger is a condition you configure that puts a build in the queue. A webhook is the fastest one and the one you should reach for first. It behaves like the bell over a shop door: the moment someone walks in, you hear it. Technically it is an inbound HTTP (HyperText Transfer Protocol, the language web servers speak) request that your source-control manager (the SCM, the server holding your git repository, such as GitHub, GitLab, or Bitbucket) sends to Jenkins the instant a push lands. Polling is the mirror image, and it behaves like walking to the front door every fifteen minutes to check whether anybody came by. Jenkins asks the SCM 'anything new since last time?' on a fixed schedule. That is the fallback for a Jenkins sitting behind a firewall that cannot receive inbound calls. It burns API (application programming interface) calls and adds up to a full interval of lag. cron fires on the wall clock whether or not anyone committed anything: nightly image rebuilds, weekly cache cleanups. And a build can always be started by hand from the web UI (user interface) or the CLI (command-line interface), or by an upstream job finishing. All three automated kinds live in one place in a declarative pipeline, the triggers block, so the timing rules get versioned and code-reviewed right beside the steps they launch.
Under the hood, one controller thread wakes about once a minute and walks every job's cron and pollSCM schedule to see what is due. The controller is the Jenkins brain that decides what to queue, while agents are the worker machines that actually run the builds. Webhooks skip that timer completely. They arrive as HTTP POSTs and put a build straight into the queue. There is one catch that catches everybody exactly once. The triggers block lives inside the Jenkinsfile, so Jenkins learns your schedule only by reading that file, and it reads that file only when the job runs. On a brand-new job the triggers sit there doing nothing until the first manual build registers them.
Reading a cron line, and why you should write H
A Jenkins cron string is five fields separated by spaces: minute (0-59), hour (0-23), day of month (1-31), month (1-12), and day of week (0-7, where both 0 and 7 mean Sunday). A * means 'every'. So 0 2 * * * reads as '2:00 AM, every day'. Hardcoding that minute becomes a trap once you have a few dozen jobs. If fifty nightly builds all say minute 0, they go off together at 02:00 like fifty alarm clocks in one room, and the agents choke. The H symbol fixes that. H stands for 'hash'. cron('H 2 * * *') still runs once inside the 2 AM hour, but at a minute Jenkins works out by hashing the job's name, so those fifty jobs scatter across 02:00 to 02:59 and each one keeps the same minute from run to run. Use H anywhere you would otherwise pin a number. H/15 * * * * means 'every 15 minutes, offset by the hash' rather than exactly on the quarter hour. Aliases exist too, @daily, @midnight and @weekly, and Jenkins prints the next few fire times right under the field so you can sanity-check what you typed.
pipeline {agent anytriggers {githubPush() // build on a GitHub webhook (needs the GitHub plugin)cron('H 2 * * *') // nightly, hashed minute inside the 02:00 hourpollSCM('H/15 * * * *') // fallback ONLY: poll git ~every 15 min where hooks can't reach}stages {stage('Build') {steps {sh 'make build'}}}}
Started by timerObtained Jenkinsfile from git https://github.com/acme/payments-api.git[Pipeline] Start of Pipeline[Pipeline] nodeRunning on built-in in /var/jenkins_home/workspace/payments-api[Pipeline] stage[Pipeline] { (Build)[Pipeline] sh+ make buildgcc -O2 -o app main.c[Pipeline] }[Pipeline] End of PipelineFinished: SUCCESS
'Started by timer' at the top of a console log is Jenkins naming the cause, which is its word for whichever trigger fired this run. A pollSCM run that found a change prints 'Started by an SCM change' instead. If polling finds nothing, no build appears at all, which is the whole point of it. Keep pollSCM as the fallback for firewalled controllers and nothing else. Once a webhook works, delete the poll line, because running both means Jenkins is being poked on every push and still asking the repository on a timer.
Wiring the webhook
For a single pipeline job, githubPush() in the triggers block is what wires it to GitHub. It comes from the GitHub plugin, which you install below. In an older Freestyle job the same switch is a checkbox with the baffling label 'GitHub hook trigger for GITScm polling'. That name is a leftover from history. When the hook arrives Jenkins does still run a lightweight git poll, but only of that one job, and only because the hook nudged it, never on a timer. Underneath, GitHub sends an HTTP POST carrying a JSON (JavaScript Object Notation, a plain-text data format) payload to https://your-jenkins/github-webhook/, and the trailing slash matters. The payload names the repository and the pushed ref, and the plugin matches it against every job whose git remote points at that repository. That endpoint takes no login by design, which is why GitHub signs each delivery with an HMAC (hash-based message authentication code, a fingerprint of the request body computed with a secret only the two ends know). Set that shared secret in both GitHub and Jenkins and Jenkins throws away any POST whose signature does not check out. Skip it and anyone who can reach the URL can queue builds on your controller. A queued build runs real pipeline steps on an agent, with whatever credentials the job binds, so an unverified endpoint is remote code execution sitting there waiting to happen.
# The webhook receiver answers 200 to a POST (this is where GitHub delivers)curl -s -o /dev/null -w '%{http_code}\n' -X POST https://jenkins.example.com/github-webhook/# Make a real change and let GitHub deliver the hookgit commit -am 'fix: null check in charge handler'git push origin main
200[main 9f3c1a2] fix: null check in charge handlerTo github.com:acme/payments-api.git3b1e0f4..9f3c1a2 main -> main# tail -f /var/log/jenkins/jenkins.logINFO o.j.p.github.webhook.subscriber.DefaultPushGHEventSubscriber#onEvent:Received PushEvent for https://github.com/acme/payments-api from 140.82.115.1INFO o.j.p.github.webhook.subscriber.DefaultPushGHEventSubscriber#onEvent:Poked payments-apiINFO com.cloudbees.jenkins.GitHubPushTrigger#run:SCM changes detected in payments-api. Triggering #48# Console Output — build #48Started by GitHub push by aliceFinished: SUCCESS
Multibranch jobs scan, they do not poll
A multibranch pipeline is one job that reads a whole repository and creates a child pipeline for every branch containing a Jenkinsfile. Feature branches and pull requests then build themselves, with no hand-made job per branch. Multibranch jobs do not use the triggers block for source-control events. The branch source owns that wiring instead. When a webhook arrives, Jenkins re-indexes only the branch that changed and builds it: event-driven, no timer anywhere. The multibranch version of polling is the option called 'Periodically if not otherwise run', a folder-level scan that re-lists branches on a schedule and brings back the same lag and the same API cost. Point an organization folder at your GitHub org and Jenkins will register the webhooks for you through GitHub's API, so a newly created repository is wired the moment it shows up. That is why manageHooks and a stored credential turn up in the config below.
jenkins-plugin-cli --plugins \github:1.40.1 \github-branch-source:1811.v9d194e77c709 \workflow-multibranch:800.v90f7ac4e6c8b
Loading plugins from --plugins argumentDownloading github:1.40.1Downloading github-branch-source:1811.v9d194e77c709Downloading workflow-multibranch:800.v90f7ac4e6c8bDone
unclassified:gitHubPluginConfig:hookUrl: "https://jenkins.example.com/github-webhook/"hookSecretConfigs:- credentialsId: "github-webhook-secret" # HMAC secret used to verify each deliveryconfigs:- name: "acme"apiUrl: "https://api.github.com"credentialsId: "github-app-creds"manageHooks: true # let Jenkins register the webhook itself
java -jar jenkins-cli.jar -s https://jenkins.example.com/ -auth @/root/.jenkins-cli reload-jcasc-configurationecho $?
0
Your pipeline now starts when it should: on every push, on a nightly schedule that does not stampede, and by hand when you want it. Next up is Build & test stages, which is about what a run should actually do once one of these triggers fires. Compile the code, run the test suite, and fail fast so a bad commit never reaches the stages that come after.
Try this
Work through “Multibranch jobs scan, they do not poll” yourself on a sandbox you can throw away, following the commands above in order. Then break one step deliberately and re-run, so you have seen the failure before it finds you.
Takeaway
The trap worth remembering here: a new trigger stays asleep until the job runs once. Check that on your own systems before you need to, because it is cheaper to find on a quiet afternoon than during an incident.