Cron
Script and command line3 steps
Monitor cron jobs and shell scripts with push
A wrapper script checks the exit code. If something goes wrong, the last output reaches you as an alert.
On this page
How do I get notified when a cron job fails?
Run the job through a small wrapper script. It runs the command, and if the exit code is not 0, it sends the job name, hostname and the last 4000 bytes of output with curl to https://api.pagerdeck.com/v1/push. The job's exit code is preserved, so cron and your logs see the same as before.
- Requirement
- sh, curl and jq
- Trigger
- Exit code other than 0
- Content
- The last 4000 bytes of output, field limit 8 KiB
- Endpoint
- POST https://api.pagerdeck.com/v1/push
Prerequisites
- A PagerDeck account with at least one paired device, so alerts have somewhere to land.
- A source in the dashboard with its credentials, see the first step below.
- sh, curl and jq on the machine that runs the job.
How to set it up
Create a source
Create a source with the slug cron and copy the ingest key.
Save the wrapper script
Save it as /usr/local/bin/run-or-alert and make it executable with chmod +x. jq puts the title and output safely into JSON, even with quotes and line breaks.
bash #!/bin/sh # /usr/local/bin/run-or-alert <name> <command> [args...] name="$1"; shift output=$("$@" 2>&1) status=$? if [ "$status" -ne 0 ]; then printf '%s' "$output" | tail -c 4000 | jq -Rs --arg title "$name failed (exit $status) on $(hostname)" \ '{title: $title, body: ., severity: "error", tags: ["cron"]}' | curl -sS -X POST https://api.pagerdeck.com/v1/push \ -H "Authorization: Bearer $PAGERDECK_KEY" \ -H "Content-Type: application/json" \ --data-binary @- >/dev/null fi exit "$status"Update the crontab
The key is set as a variable in the user's crontab, which only that user can read. Put run-or-alert with a name for the job in front of the actual command.
text PAGERDECK_KEY=src_live_REPLACE_ME 30 2 * * * /usr/local/bin/run-or-alert nightly-backup /usr/local/bin/backup.sh
Payload
PagerDeck reads these fields on POST /v1/push. Only title is required, unknown fields are ignored.
{
"title": "Disk almost full on web-01",
"body": "/ is 94% used",
"severity": "error",
"tags": [
"prod",
"disk"
],
"dedup_key": "web-01:disk",
"url": "https://status.example.com/web-01",
"ttl": "30m"
}- title
- Required, at most 250 bytes.
- body
- Body text, at most 8 KiB.
- severity
- info, warning, error or critical. Without it the source default applies.
- tags
- At most 20, 64 bytes each.
- dedup_key
- Keeps an incident together until it is resolved.
- url
- A link for the app, http and https only.
- ttl
- Delivery deadline and automatic resolve, for example 30m.
Test it
Send a test alert straight to the API after setting PAGERDECK_KEY to your key. If it shows up in the app under your source, key and source are right and any problem sits in the sender configuration.
curl -sS -X POST https://api.pagerdeck.com/v1/push \
-H "Authorization: Bearer $PAGERDECK_KEY" \
-H "Content-Type: application/json" \
-d '{"title":"PagerDeck test","body":"If you read this, the key works.","severity":"warning"}'What the rules do with it
The sender proposes an urgency, your rule chain decides the alert level. This rule raises errors from the source cron to critical and repeats until you acknowledge. Use the slug of your own source.
{
"enabled": true,
"stop": false,
"when": {
"source": [
"cron"
],
"severity_at_least": "error"
},
"then": {
"level": "critical",
"repeat": {
"every": "5m",
"until": "ack",
"max": 6
}
}
}- Set the volume
- By source, title pattern, tags and time of day you can set a level from silent to critical.
- Repeat until acknowledged
- A rule repeats an alert at a fixed interval until you acknowledge it.
- Escalate
- If nobody reacts, PagerDeck notifies another address after a deadline.
What you should know
- A job that never runs does not report anything. PagerDeck does not check for heartbeats. To catch missed runs, combine the script with a push monitor, for example in Uptime Kuma.
- The output goes into the alert text and therefore into the notification and the history. Do not print passwords or tokens in your jobs.
- The script needs outbound HTTPS to api.pagerdeck.com.
Troubleshooting
When the API answers with a status code, it usually names the cause already.
- 202
- Accepted and stored. This is not an error, delivery follows.
- 400
- The input is invalid, for example a title over 250 bytes or malformed JSON. The message field gives the reason.
- 401
- The key is missing, unknown or expired. Check the Authorization: Bearer header and that the key was copied completely.
- 402
- The account may not send. Nothing is wrong with the connection, check the plan and billing in the dashboard.
- 413
- The request is larger than 64 KiB. Limit the amount per request.
- 429
- Sent too fast or quota used up. The Retry-After header says when to continue.
Frequently asked questions
- Does this also work with systemd timers?
- Yes. Put run-or-alert in front of the command in ExecStart, or use OnFailure= with a separate unit that makes the call to PagerDeck.
- Does it work without jq?
- Yes, if you escape the payload yourself. jq is the safe route, because a job's output almost always contains quotes or line breaks.
- How do I avoid duplicate alerts when a job fails every hour?
- Set a dedup_key in the payload, such as the job name. While the incident is open, each further failure updates it without ringing again.
No more silent failures
One script, one line in the crontab. 14 days, no payment method needed.
Last reviewed: October 2026. PagerDeck, a product of Gonzi Tech UG (haftungsbeschränkt)
Product and company names mentioned are trademarks of their respective owners. PagerDeck is not affiliated with, sponsored or endorsed by them. Details in the imprint.