Monitor bash scripts: trap EXIT to ping on success or failure
Define a _finish function that checks $? and
either curls the success URL or POSTs to /fail,
then add trap '_finish' EXIT. Works with set -e:
any failing command exits the script, the trap fires, and the
exit-code check sends the right ping. Absence detection catches
SIGKILL and scripts that never start. Free, no monitor cap.
Your assistant can do this for you
Monitor my Bash scripts with LastPing
On this page
The snippets
Create a monitor in the console, set the schedule plus a grace window, copy the ping URL, and paste the trap header at the top of your script.
Minimal trap: exit code only
Works with any bash script. set -euo pipefail makes
every non-zero command exit immediately so the trap always fires on
the first error:
#!/usr/bin/env bash
set -euo pipefail
PING="${LASTPING_URL:-https://ping.lastping.dev/<your-monitor-id>}"
_finish() {
local ec=$?
if [ "$ec" -eq 0 ]; then
curl -fsS --retry 3 --max-time 10 "$PING" >/dev/null
else
curl -fsS --retry 3 --max-time 10 \
-X POST \
--data "script exited with code $ec" \
"$PING/fail" >/dev/null || true
fi
}
trap '_finish' EXIT
# ── your job starts here ──────────────────────────────────────
echo "Starting backup..."
rsync -a /data/ /backup/
echo "Done."
Capture stderr for the fail body
Without set -e, redirect stderr to a temp file.
If the script exits non-zero, POST the contents of that file to
/fail so the alert body contains the actual error
output, not just an exit code:
#!/usr/bin/env bash
set -uo pipefail # -e omitted so we control the exit
PING="${LASTPING_URL:-https://ping.lastping.dev/<your-monitor-id>}"
ERR_LOG=$(mktemp)
_finish() {
local ec=$?
if [ "$ec" -eq 0 ]; then
curl -fsS --retry 3 --max-time 10 "$PING" >/dev/null
else
# POST stderr (up to 10,000 bytes) to /fail
curl -fsS --retry 3 --max-time 10 \
-X POST \
--data-binary "@${ERR_LOG}" \
"$PING/fail" >/dev/null || true
fi
rm -f "$ERR_LOG"
}
trap '_finish' EXIT
# capture stderr for the fail body
exec 2>"$ERR_LOG"
# ── your job ──────────────────────────────────────────────────
some_command_that_might_fail || exit 1
Optional: /start ping for overrun detection
A /start ping arms the hung-job alarm: if no success
ping arrives inside the grace window an incident opens even if the
script just hangs rather than crashing:
PING="${LASTPING_URL:-https://ping.lastping.dev/<your-monitor-id>}"
RID="$$-$(date +%s)" # PID + unix timestamp
curl -fsS --retry 3 --max-time 10 "$PING/start?rid=$RID" >/dev/null
_finish() {
local ec=$?
if [ "$ec" -eq 0 ]; then
curl -fsS --retry 3 --max-time 10 "$PING?rid=$RID" >/dev/null
else
curl -fsS --retry 3 --max-time 10 \
-X POST --data "exited $ec" \
"$PING/fail" >/dev/null || true
fi
}
trap '_finish' EXIT
Why SIGKILL can't be trapped
trap catches EXIT, ERR,
TERM, and INT, but not
SIGKILL. An OOM-killed process sends no ping. That is
exactly what absence-detection handles: the missing ping itself
opens the incident after the grace window. See the
cron monitoring guide for the
scheduler side and the Python guide
for a language-level comparison. Also available:
LastPing MCP server for AI-agent provisioning.
What this catches
The trap reports what it can; the silence reports the rest.
- A failing command (set -e): bash exits immediately on any non-zero command, the trap fires, and the exit code goes to
/fail. - A non-zero explicit exit:
exit 1triggers the EXIT trap; the body can include the captured stderr output. - SIGTERM: the EXIT trap fires on graceful shutdown signals, giving you a chance to report before the process ends.
- SIGKILL and OOM kills: untrapable; handled by absence detection: no ping reaches LastPing within the schedule+grace window → incident.
- The script never ran: missing cron entry, missing executable, dead host. Absence → incident.
Questions people ask
Front-loaded answers: the most important fact first.
-
How do I monitor a bash cron script?
Set
PINGto your LastPing URL, define a_finishfunction that checks$?and either curls the success URL or POSTs to/fail, then addtrap '_finish' EXITbefore your job code.set -ecauses bash to exit on any non-zero command, so the trap fires on the first failure. -
Does trap EXIT work with set -e?
Yes. With
set -eany failing command exits the script immediately; bash still runs the EXIT trap when the script exits, regardless of the cause. The$?inside the trap reflects the exit code that triggered the exit, and the trap handler checks it to determine whether to send the success or failure ping. -
Can I send the actual error message, not just the exit code?
Yes. Without
set -e(drop-efrom the shebang line so you control exits manually), redirect stderr to a temp file withexec 2>$ERR_LOGbefore your job code. In the_finishtrap, if the exit code is non-zero, POST the contents of that file to the/failendpoint usingcurl --data-binary @$ERR_LOG. Up to 10,000 bytes are stored with the ping event. -
What if the script is killed with SIGKILL?
SIGKILL cannot be caught or trapped: bash does not run EXIT handlers when killed with SIGKILL. Absence-detection monitoring handles this: the missing ping itself opens an incident after the grace window, without any trap code running.
-
Is bash cron monitoring free?
Yes. LastPing is free for individuals with no monitor cap. See how it compares to Healthchecks.io and Cronitor.
SIGKILL can't be trapped: absence detection is the safety net.
Five lines now beat a silent cron job later. First alert wired up in about a minute, and LastPing monitors itself the same way.