GUIDE · BASH · FREE

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.

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 KB) 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 exitexit 1 triggers 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.

  • Does trap EXIT work with set -e?

    Yes. set -e exits the script on any non-zero command; 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.

  • Can I send the actual error message, not just the exit code?

    Yes — redirect stderr to a temp file with exec 2>$ERR_LOG (drop -e from the shebang line so you control exits manually), then POST the file contents to /fail using curl --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 — bash does not run EXIT handlers. Absence-detection monitoring handles this: the missing ping opens an incident after the grace window without any trap code running.

  • Is this free?

    Yes. LastPing is free and fully hosted with no monitor cap and no paid tier. See how it compares to Healthchecks.io and Cronitor.

FREE · FULLY HOSTED · NO PAID TIER

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.