March 29, 2026 · 10 min read

Monitor GitHub Actions Scheduled Workflows: Dead Man's Switch Guide 2026

GitHub Actions scheduled workflows can silently stop running—no notification, no failed run, no trace in the Actions tab. If your nightly builds, data syncs, or cleanup jobs run on a schedule trigger, you have a blind spot. Here's how to fix it with a dead man's switch and one extra line in your workflow YAML.

The Silent Failure Problem with GitHub Actions Schedules

The schedule trigger in GitHub Actions uses cron syntax to run workflows on a recurring basis. It looks simple enough:

on:
  schedule:
    - cron: '0 3 * * *'  # Every day at 3:00 AM UTC

But there are three ways this can fail without telling you:

  1. Automatic disabling after 60 days of inactivity. GitHub automatically disables scheduled workflows on repositories that have had no commit activity in the past 60 days. When this happens, the workflow simply vanishes from the schedule. No email. No notification. No entry in the Actions tab showing a skipped run. It just stops. This is documented in GitHub's own docs, but most developers don't discover it until their backup script hasn't run in two months.
  2. Best-effort scheduling with no SLA. GitHub's cron scheduler is best-effort. During periods of heavy load—especially at the top of the hour when thousands of workflows are scheduled for 0 * * * *—runs can be delayed by minutes or even dropped entirely. GitHub does not guarantee that every scheduled run will execute. There is no retry mechanism and no record that a run was skipped.
  3. Workflow errors that don't propagate. Even when the workflow does run, a failure in a critical step may not reach you. If you haven't configured email notifications for failed runs (or if GitHub's notification system delays them), a broken nightly job can go unnoticed for days. And if the workflow itself fails to start due to a YAML syntax error introduced by a recent commit, it simply won't appear in the run history.

The common thread is that GitHub Actions has no built-in alerting for missed scheduled runs. It can tell you when a run fails. It cannot tell you when a run never happened.

What Is a Dead Man's Switch (and Why It Solves This)

A dead man's switch is a safety mechanism that triggers when it stops receiving a signal. In the context of cron monitoring, it works like this:

  1. You set up a monitor that expects a "ping" at a regular interval (every hour, every day, every week)
  2. Your scheduled task sends that ping when it completes successfully
  3. If the ping doesn't arrive within the expected window (plus a grace period), the monitor fires an alert

This is the inverse of traditional uptime monitoring. Instead of asking "is this service responding?", a dead man's switch asks "did this thing happen on schedule?" It's the only reliable way to detect the absence of an event—which is exactly the failure mode of GitHub Actions scheduled workflows.

The pattern has been standard in infrastructure monitoring for decades. Services like Dead Man's Snitch, Cronitor, and Healthchecks.io all implement it. CronPeek is the simplest and most affordable option, built specifically for developers who need reliable dead man's switch cron monitoring without paying enterprise prices.

Setting Up CronPeek Monitoring for GitHub Actions

The setup takes about two minutes. You'll create a monitor in CronPeek, then add a single step to your GitHub Actions workflow.

Step 1: Create a CronPeek Monitor

Use the CronPeek API to create a monitor. Set the interval to match your workflow's cron schedule, and add a grace_period to account for GitHub's scheduling variance.

# Create a monitor for a daily workflow (runs every 24 hours)
curl -X POST https://cronpeek.web.app/api/v1/monitors \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "nightly-data-sync",
    "interval": 86400,
    "grace_period": 1800
  }'

# Response:
# {
#   "id": "mon_abc123",
#   "ping_url": "https://cronpeek.web.app/api/v1/ping/mon_abc123",
#   "status": "waiting",
#   ...
# }

The interval is in seconds. 86400 = 24 hours. The grace_period of 1800 seconds (30 minutes) gives GitHub's scheduler some slack before CronPeek fires an alert. For hourly workflows, set interval to 3600 and grace_period to 600 (10 minutes).

Step 2: Add a Ping Step to Your Workflow YAML

Add a final step to your workflow job that pings the CronPeek monitor URL. Use if: success() so the ping only fires when all previous steps complete successfully.

- name: Ping CronPeek
  if: success()
  run: curl -fsS --retry 3 https://cronpeek.web.app/api/v1/ping/mon_abc123

The flags: -f fails on HTTP errors, -sS suppresses progress but shows errors, and --retry 3 handles transient network issues. This is the same pattern you'd use for any scheduled task monitoring API.

Step 3: Configure Alert Channels

By default, CronPeek sends email alerts when a monitor misses its expected ping. You can also configure webhook alerts to push notifications to Slack, Discord, PagerDuty, or any HTTP endpoint:

# Add a webhook alert to your monitor
curl -X PATCH https://cronpeek.web.app/api/v1/monitors/mon_abc123 \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "alert_webhook": "https://hooks.slack.com/services/T00/B00/xxxx"
  }'

Complete GitHub Actions YAML Example

Here's a full workflow file for a nightly data synchronization job with CronPeek monitoring built in. This covers the success path, failure notification, and the dead man's switch ping.

name: Nightly Data Sync
on:
  schedule:
    - cron: '0 3 * * *'  # Every day at 3:00 AM UTC
  workflow_dispatch:        # Allow manual trigger for testing

jobs:
  sync:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install dependencies
        run: npm ci

      - name: Run data sync
        run: node scripts/sync-data.js
        env:
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
          API_KEY: ${{ secrets.EXTERNAL_API_KEY }}

      - name: Verify sync results
        run: node scripts/verify-sync.js

      # Dead man's switch — ping CronPeek on success
      - name: Ping CronPeek (success)
        if: success()
        run: curl -fsS --retry 3 https://cronpeek.web.app/api/v1/ping/mon_abc123

      # Optional: notify on failure via a separate channel
      - name: Notify failure
        if: failure()
        run: |
          curl -X POST "${{ secrets.SLACK_WEBHOOK }}" \
            -H "Content-Type: application/json" \
            -d '{"text":"Nightly data sync FAILED. Check: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"}'

Why if: success() matters: The CronPeek ping should only fire when the job actually completes without errors. If a previous step fails, the ping is skipped, and CronPeek will alert you when the expected interval passes. This is the entire point of the dead man's switch pattern—silence means failure.

Handling Failure Notifications Separately

The dead man's switch catches the case where the workflow never runs at all—the silent failure scenario. But you also want fast alerts when the workflow runs and fails. The two mechanisms are complementary:

Together, they cover both failure modes. The failure step gives you a fast alert within seconds. The dead man's switch is your safety net for everything else.

Here's a reusable failure notification step using a simple curl to a webhook:

- name: Alert on failure
  if: failure()
  run: |
    curl -fsS --retry 2 \
      -X POST "${{ secrets.ALERT_WEBHOOK }}" \
      -H "Content-Type: application/json" \
      -d '{
        "monitor": "nightly-data-sync",
        "status": "failed",
        "run_url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}",
        "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"
      }'

Advanced: Monitoring Multiple Workflows

Most repositories have more than one scheduled workflow. You might have a nightly backup, an hourly cache refresh, a weekly report, and a daily cleanup job. Each one needs its own CronPeek monitor with an appropriate interval.

One Monitor Per Workflow

Create a separate monitor for each scheduled workflow. This gives you independent alerting—if your backup stops but your cache refresh keeps running, you'll know exactly which one failed.

# Create monitors for each workflow
for workflow in "nightly-backup:86400:3600" "hourly-cache:3600:600" "weekly-report:604800:7200"; do
  IFS=':' read -r name interval grace <<< "$workflow"
  curl -X POST https://cronpeek.web.app/api/v1/monitors \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d "{\"name\": \"$name\", \"interval\": $interval, \"grace_period\": $grace}"
done

Choosing the Right Grace Period

Grace periods prevent false alarms. GitHub Actions scheduling is not precise—a workflow scheduled for 3:00 AM might not start until 3:15 AM during peak load. Set your grace period based on the workflow's frequency:

If you're seeing false alarms, increase the grace period. If you need faster detection, decrease it. The tradeoff is alert speed versus noise.

Webhook Alerts for Teams

For team setups, webhook alerts let you route CronPeek notifications to Slack channels, Discord servers, or your own incident management system. A single webhook endpoint can fan out to multiple destinations:

# Example: webhook payload CronPeek sends on a missed ping
{
  "event": "monitor.missed",
  "monitor": {
    "id": "mon_abc123",
    "name": "nightly-backup",
    "last_ping": "2026-03-28T03:12:44Z",
    "expected_by": "2026-03-29T03:42:44Z"
  },
  "alert_at": "2026-03-29T04:12:44Z"
}

You can pipe this into a Slack message, a PagerDuty incident, a Telegram notification, or an email via a simple serverless function. If you already use OGPeek for your social previews or StackPeek for tech stack detection, CronPeek fits naturally into the same API-first workflow.

Using Secrets for the Monitor ID

Don't hardcode your CronPeek monitor ID in your workflow file. Store it as a GitHub Actions secret so it's not exposed in your repository:

# In your workflow YAML:
- name: Ping CronPeek
  if: success()
  run: curl -fsS --retry 3 https://cronpeek.web.app/api/v1/ping/${{ secrets.CRONPEEK_MONITOR_ID }}

Add the secret in your repository settings under Settings → Secrets and variables → Actions. This keeps your monitor URL private and makes it easy to rotate if needed.

Pricing: CronPeek vs. the Alternatives

Free tier included — no credit card required

If you're monitoring GitHub Actions scheduled workflows, you probably have other scheduled tasks too—database backups, AWS Lambda functions, Kubernetes CronJobs. Here's how the pricing stacks up across services:

Plan CronPeek Cronitor Dead Man's Snitch
Free tier 5 monitors None None
10 monitors $9/mo $20/mo $29/mo
50 monitors $9/mo $100+/mo $199/mo
Unlimited $29/mo Custom $249+/mo

For a solo developer or small team monitoring 5–10 GitHub Actions workflows, CronPeek's free tier covers you completely. If you're running more scheduled tasks across multiple repositories, the $9/mo Pro plan gives you 50 monitors—enough for most production setups. That's 11x cheaper than Cronitor and 22x cheaper than Dead Man's Snitch for the same number of monitors.

Quick Checklist: Monitoring GitHub Actions Schedules

Here's a summary of everything you need to do to stop GitHub Actions scheduled workflows from failing silently:

  1. Sign up for CronPeek at cronpeek.web.app (free, no credit card)
  2. Create a monitor for each scheduled workflow with the matching interval
  3. Add the ping step to your workflow YAML with if: success()
  4. Store the monitor ID as a GitHub Actions secret
  5. Add a failure step for immediate alerts on workflow errors
  6. Set a grace period that accounts for GitHub's scheduling variance
  7. Configure webhook alerts for Slack, Discord, or your incident tool
  8. Test it—trigger the workflow manually with workflow_dispatch and confirm the ping arrives

That's it. Two minutes of setup, and you'll never lose a nightly build, a data sync, or a backup to GitHub's silent scheduling failures again.

Start monitoring GitHub Actions for free

5 monitors on the free tier. No credit card. Set up your first monitor in under 2 minutes and see the live demo.

Start monitoring free →

More from CronPeek

More developer APIs from the Peek Suite