GitHub Actions monitoring

GitHub shows you the runs that failed but never the runs that did not start, so watch a scheduled workflow from outside: the last step reports success, and a monitor alerts you when that report goes stale.

The Actions tab is a good record of what ran and no record of what did not. When a scheduled workflow fails, GitHub emails the user who last modified the cron line in the workflow file, which may be someone who left in March. When the workflow never starts, nobody hears anything, because there is no run to fail.

GitHub Actions scheduled workflow monitoring

Three behaviours from GitHub's own docs make a schedule less reliable than the YAML suggests:

  • In a public repository, scheduled workflows are disabled after 60 days with no repository activity. The nightly backup of a finished side project is exactly the workflow this hits.
  • The schedule event can be delayed during high load, and the start of every hour counts as high load. Under enough load, queued runs are dropped, not just delayed.
  • Schedules run only on the default branch, no more often than every 5 minutes, and in UTC unless the entry sets the timezone key GitHub added in March 2026.

So the question to monitor is not "did a run fail". It is "did a successful run happen recently enough".

GitHub Actions cron: report in on success

Put the report at the end of the job. Steps stop at the first failure, so the last step only runs when everything before it passed. The odd minute keeps the run off the top of the hour.

.github/workflows/nightly-sync.yml
name: nightly-sync
on:
  schedule:
    - cron: '17 3 * * *' # 03:17 UTC
  workflow_dispatch:

jobs:
  sync:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v7
      - run: ./scripts/sync.sh
      - name: Report success
        run: >
          curl -fsS -m 10 -X POST
          -H "Authorization: Bearer ${{ secrets.HEARTBEAT_TOKEN }}"
          https://yourapp.com/internal/heartbeat/nightly-sync

Why the check lives in your app

A Logdash push monitor looks like the obvious target and is the wrong one here. Push monitors are a Pro feature, they expect a ping inside every 15-second window, and they flip to down on the first empty one. There is no grace setting. A workflow that runs every 5 minutes at best would flap after every run. So turn it around: the workflow tells your app, your app keeps the mark for 26 hours, and an ordinary HTTP monitor asks whether the mark is still there. That runs on the free plan. The 26 hours is a daily schedule plus two hours of slack for GitHub delays; set it to your own interval plus the lateness you can live with.

server.ts
import express from 'express';
import { Redis } from 'ioredis';

const app = express();
const redis = new Redis(process.env.REDIS_URL!);
const KEY = 'heartbeat:nightly-sync';
const TTL = 26 * 60 * 60; // one day plus slack for GitHub delays

app.post('/internal/heartbeat/nightly-sync', async (req, res) => {
  if (req.get('authorization') !== `Bearer ${process.env.HEARTBEAT_TOKEN}`) {
    res.sendStatus(401);
    return;
  }
  await redis.set(KEY, Date.now(), 'EX', TTL);
  res.sendStatus(204);
});

// Logdash checks this. 503 means no successful run in 26 hours.
app.get('/health/nightly-sync', async (_req, res) => {
  res.sendStatus((await redis.exists(KEY)) ? 200 : 503);
});

app.listen(3000);
  1. Ship the two routes Deploy them, add HEARTBEAT_TOKEN as a repository secret, then run the workflow once by hand so the key exists before the first check.
  2. Point a monitor at it Create a service in Logdash with an HTTP monitor on /health/nightly-sync and connect a Telegram channel. Checks run every 5 minutes on the free plan and every minute on Builder.
  3. Let it go stale Delete the key in Redis. The route answers 503, the monitor flips to down on the next check, and a Telegram alert names the monitor and the 503.

Logdash vs Healthchecks.io for scheduled workflows

FeatureLogdashHealthchecks.io
Code in your appTwo routes and a Redis keyNone, the workflow curls their URL
Schedule awarenessExpressed as the key TTLCron expression, time zone and grace time per check
Catches the 60-day disableYes, the key expiresYes, the ping stops
HTTP checks on the app the workflow feedsSame service, with response timeNot offered, it only listens for pings
Free planFive services, checks every 5 minutes20 checks
Alert channelsTelegram and webhookEmail, Slack, Telegram and many more

When Healthchecks.io is the better pick

  • You do not want to add routes to an app just to watch a workflow. A curl to their ping URL is the whole integration.
  • You run many scheduled workflows. One check per workflow with its own cron expression beats one route per workflow.
  • The workflow does not touch an app you run, for example it cuts a release or prunes a bucket.
  • You want email or Slack alerts. Logdash sends Telegram and webhooks only.
Is there a GitHub Actions monitoring dashboard?
Yes, under Insights: Actions Usage Metrics and Actions Performance Metrics, the latter on every GitHub Cloud plan since March 2025. They show run times, queue times and failure rates per workflow. They count runs that happened, so a schedule that silently stopped never shows up there.
How does GitHub Actions runner monitoring work?
GitHub-hosted runners are GitHub's problem. A self-hosted runner installed with ./svc.sh install is a long-running service, which suits a Logdash push monitor: a loop on the host that pings every 10 seconds while the actions.runner service is active. GitHub marks a dead runner Offline in settings, which is a page you have to open.
Why did my GitHub Actions cron not run?
Usually one of four reasons: the repo is public and had no activity for 60 days, so the schedule was disabled; the run was delayed or dropped under load; the workflow is not on the default branch; or the entry has no timezone key, so it runs in UTC, and you wrote local time. Re-enable a disabled workflow from the Actions tab.
Does GitHub Actions scheduled workflow monitoring need a paid plan?
Not with the setup above. It uses an HTTP monitor, which the free plan includes with checks every 5 minutes. Push monitors are Pro only at $15 a month, and they need a ping every 15 seconds, which a scheduled workflow cannot send.

Point it at your own URL and watch it for real.