Jenkins has a particular way of being down while looking up. The controller answers on 8080, the dashboard loads, and nothing has built in six hours because the only agent with the docker label dropped its connection on Sunday. Or the controller was put into quiet mode before a plugin upgrade, the upgrade got postponed, and every job since has sat in the queue with "Jenkins is preparing for shutdown." Or the disk filled, which the Metrics plugin noticed and nobody read. A single uptime check on the hostname catches none of these, because the hostname is the one thing Jenkins almost never fails at.

This guide monitors Jenkins in layers, from the outside in: is the controller serving, is it healthy by its own account, are agents taking builds, is the scheduler firing, is it stuck in quiet mode, and did the backup run. Each layer is one CronAlert monitor. The HTTP checks run on the free plan; the heartbeats need Pro.

Layer 1: the controller is serving (the login page)

With security enabled, nearly every Jenkins URL redirects an anonymous request to /login. Uptime monitors follow redirects, so a monitor on the root, on a job page, or on /api/json all end up measuring the sign-in page anyway. Point the monitor there deliberately:

URL:              https://jenkins.example.com/login
Method:           GET
Expected status:  200
Keyword (Pro):    contains "j_username"

Two behaviors make this a better check than it looks. While Jenkins is starting, before plugins load, it serves a "Please wait while Jenkins is getting ready to work" page with a 503, so a controller that is restarting in a loop reads as down rather than as a slow success. And the keyword, the name of the username field in the real form, proves that what answered was Jenkins and not your reverse proxy's maintenance page or a 200 from a misrouted virtual host. Monitoring the reverse proxy separately tells you which of the two broke when it does.

If the login page itself is behind single sign-on, the redirect lands on your identity provider and the monitor measures that instead. Use the Metrics plugin endpoints in the next layer, which take their own key and do not go through the login flow.

Layer 2: Jenkins' own health checks (Metrics plugin)

The Metrics plugin ships a set of health checks that Jenkins runs about itself: free disk space in JENKINS_HOME, free temporary space, whether every plugin loaded without failing, and whether any threads are deadlocked. It exposes them over HTTP behind an access key you create under Manage Jenkins → System → Metrics. Give the key only the Health Check and Ping permissions, then monitor:

URL:              https://jenkins.example.com/metrics/healthcheck
Expected status:  200
Custom headers:   { "Authorization": "Jenkins-Metrics-Key <access-key>" }
Keyword (Pro):    not_contains "\"healthy\":false"

The endpoint returns JSON with one entry per check and an HTTP status that goes non-200 when any check fails, so the status code alone is a usable signal on the free plan. The keyword is belt and braces: it fires on the first unhealthy entry even if a future plugin version changes the status mapping. The plugin also accepts the key in the URL path as /metrics/<key>/healthcheck; prefer the header, so the key is not sitting in a monitor URL that shows up in alert emails and status pages.

Treat this alert as the early one. Disk at the threshold means tomorrow's builds fail; a plugin that failed to load means some feature is quietly missing. The sign-in monitor will still be green for both. /metrics/ping, which returns pong with a 200, is a lighter alternative for a 1-minute check if you want the healthcheck on a slower cadence, but with CronAlert's intervals set from the plan, one monitor on /metrics/healthcheck is usually enough.

Layer 3: agents are taking builds (a heartbeat per label)

This is the layer the hostname check cannot see and the one that costs you the most hours. The reflex is to poll the computer API:

URL:              https://jenkins.example.com/computer/api/json?tree=computer[displayName,offline,temporarilyOffline]
Expected status:  200
Custom headers:   { "Authorization": "Basic <base64 user:api-token>" }
Keyword (Pro):    not_contains "\"offline\":true"

It works, with two caveats. The credential is a Jenkins user with Overall/Read and an API token, which is why it goes in a header rather than the URL (the headers guide covers minting one). And offline is also true for agents someone deliberately marked offline, so every planned maintenance on any agent trips it, and a monitor gets one assertion, so you cannot also exclude those.

The better check does not ask Jenkins whether the agent is online. It asks the agent to prove it. Create a tiny pipeline job pinned to the label you care about, triggered on a schedule, whose only step is to ping a CronAlert heartbeat:

pipeline {
  agent { label 'docker' }
  triggers { cron('H/15 * * * *') }
  options { timeout(time: 2, unit: 'MINUTES') }
  stages {
    stage('heartbeat') {
      steps {
        sh 'curl -fsS --retry 3 https://cronalert.com/api/heartbeat/<token>'
      }
    }
  }
}

Create the heartbeat monitor in CronAlert with an expected interval of 15 minutes and a grace period of about 15 minutes; H/15 spreads the start minute, so the gap between two runs can be a little over the interval and the grace absorbs it. The ping arrives only if the cron trigger fired, the queue dispatched the build, an executor on a docker-labelled agent accepted it, the agent could run a shell, and the agent has outbound network. Any break in that chain and the heartbeat goes overdue. One job per label whose absence would hurt, which for most teams is two or three. The same pattern is what self-hosted GitLab runners and Gitea Actions runners need, for the same reason: a CI pool that is "online" and not executing is the common failure, and only a build can tell you.

Layer 4: the jobs that matter ran

The heartbeat pipeline proves the machinery works. For the specific nightly build, integration suite, or release job that you actually care about, add a ping to the job itself, in the success branch so a failing build does not report as healthy:

post {
  success {
    sh 'curl -fsS --retry 3 https://cronalert.com/api/heartbeat/<nightly-token>'
  }
}

For a 2 a.m. nightly, set the heartbeat's expected interval to 24 hours and the grace to the longest the build legitimately runs plus some slack, say 2 hours. The alert then means one of: the trigger didn't fire, the build never got an executor, or the build failed. Jenkins would have emailed you about the third. It says nothing about the first two, and those are the ones this catches. Batch job monitoring covers choosing intervals for long and variable jobs, and the grace-period guide covers the trade-off between catching lateness and false alarms.

Layer 5: quiet mode and prepare-for-shutdown

Quiet mode is the state Jenkins enters when someone clicks Prepare for Shutdown or an upgrade is pending a restart: running builds finish, new builds queue, nothing starts. It is meant to last minutes. When the person who clicked it gets pulled into something else, it lasts the weekend, and every monitor above except the heartbeats stays green, because the controller is serving, healthy, and the agents are online. The heartbeat pipeline will catch it, since its builds queue and never run, and that alone is a reason to have it. For a direct signal, monitor the flag:

URL:              https://jenkins.example.com/api/json?tree=quietingDown
Expected status:  200
Custom headers:   { "Authorization": "Basic <base64 user:api-token>" }
Keyword (Pro):    not_contains "\"quietingDown\":true"

Put a maintenance window on this monitor and the heartbeat monitors for planned upgrade windows so the quiet mode you meant to enter does not page you.

Backups: the job that only counts if it ran

Jenkins state is files in JENKINS_HOME: job configs, credentials, plugin versions, build history. A backup that stopped running is discovered on the day the disk dies. Whether you use the ThinBackup plugin, a cron on the host that tars the directory, or a volume snapshot, end it with a heartbeat ping and set a 24-hour interval with a 2-hour grace:

0 3 * * * tar -czf /backups/jenkins-$(date +\%F).tgz -C /var/lib jenkins \
  --exclude=jenkins/workspace --exclude=jenkins/caches \
  && curl -fsS --retry 3 https://cronalert.com/api/heartbeat/<backup-token>

The && matters: the ping only fires if the tar succeeded. Back up secrets/ and credentials.xml together with the job configs, since one without the other restores to a controller that cannot decrypt anything. Monitoring scheduled backups goes deeper on verifying the artifact and not only the exit code.

Reading the alerts

AlertWhat brokeFirst thing to check
/login down, everything else downController process, host, or the proxy in frontsystemctl status jenkins, the host's disk and memory, proxy logs
/login 503Jenkins restarting or stuck startingController log for a plugin failing to load
/login green, healthcheck redDisk, temp space, a failed plugin, or a deadlockThe healthy:false entry in the healthcheck JSON
Heartbeat pipeline overdue, everything else greenAgent for that label offline, or controller in quiet modeManage Jenkins → Nodes, then the queue page
Nightly heartbeat overdue, pipeline heartbeats fineThat job: trigger, failure, or stuckThe job's last build and console
Quiet-mode monitor redSomeone clicked Prepare for Shutdown and leftCancel shutdown on the manage page
Backup heartbeat overdueBackup didn't run or failedHost cron log, free space on the backup target

Alert channels in CronAlert are team-wide, so every monitor above alerts the same channels. If the CI team and the platform team want different pages, put the Jenkins monitors in their own team; alert routing with teams explains the pattern.

Frequently asked questions

Our Jenkins is only on the VPN. Can CronAlert reach it?

Not directly; checks come from the public internet. Either publish it through a Cloudflare Tunnel with an Access service token in the monitor's headers, or skip the inbound checks and rely on the heartbeats, which are outbound from Jenkins and work from any network with egress. The heartbeat pipeline plus the backup heartbeat covers more real failures than the login check does anyway.

Does this work for CloudBees CI?

The same endpoints exist, the Metrics plugin is available, and the pipeline syntax is identical. On an operations center with several controllers, give each controller its own set of monitors.

Will a 1-minute check on /login show up in my access logs or audit trail?

In the access log, yes; set a User-Agent header on the monitor so you can filter it. It does not create a Jenkins session or an audit event, since it never signs in.

Can CronAlert trigger a Jenkins job when something is down?

Not directly. A webhook alert channel can POST to a receiver you control, which can call Jenkins' remote-trigger URL; webhook alert integrations covers the relay pattern.

Which plan do I need?

The login, healthcheck, agent API, and quiet-mode monitors are HTTP checks with custom headers and an expected status code, which are on every plan, at 3-minute intervals on Free and 1-minute on Pro. Keyword assertions and heartbeats are Pro ($5/mo, or $4/mo billed annually).

Monitor the builds, not the port

The controller answering is the least informative fact about a Jenkins. Whether the agents are executing, whether the scheduler fired, whether the backup ran, and whether anyone left it in quiet mode are the facts that decide if your team ships this week, and each is one small monitor. Create a free account, add the login and healthcheck monitors, and upgrade to Pro for the heartbeat pipeline before the next agent silently drops off. Related reading: uptime monitoring in CI/CD pipelines, the same scheduled-workflow heartbeat on GitHub Actions, monitoring Docker and self-hosted apps if Jenkins runs in a container, monitoring the server underneath, monitoring systemd timers for the host's other scheduled jobs, and choosing between HTTP, TCP, and heartbeat monitors.