Skip to content
Get started

Cron job timeout: kill a hung run before it overlaps

A cron job with no timeout can hang indefinitely — waiting on a network call, a lock, or a stuck process — and if it runs long enough it overlaps the next scheduled run, stacking up duplicate processes. Wrap the job in the `timeout` command so a run that exceeds a limit is killed automatically. A job killed by timeout exits with a signal code (124 for the timeout itself, or 137 if it had to escalate to SIGKILL), which withholds the success ping and lets a monitor alert you that the run hung.

Why does a hung cron job cause overlapping runs?

cron starts a new run every time the schedule fires, regardless of whether the previous run finished. If a nightly job normally takes 10 minutes but one night hangs for hours, the next night's run starts on top of it — and now two copies are competing for the same database rows, files, or locks. Left unchecked, a hang can pile up many overlapping processes.

How do I add a timeout to a cron job?

Wrap the command in the coreutils `timeout` command. It runs the command and kills it if it exceeds the given duration:

crontab entry with a 30-minute cap
# timeout sends SIGTERM after 30m; --kill-after escalates to SIGKILL if the
# job ignores TERM for another 30s. Exit 124 = the timeout fired.
0 3 * * * timeout --kill-after=30s 30m /usr/local/bin/nightly.sh

To also prevent overlap when a run legitimately runs long, take a lock with `flock` so a second run can't start while the first holds it:

0 3 * * * flock -n /tmp/nightly.lock timeout 30m /usr/local/bin/nightly.sh

What exit code does a timed-out job return?

The `timeout` command exits 124 when the time limit was reached and the command was killed. If you used --kill-after and it had to send SIGKILL, the exit reflects that signal (137 = 128 + 9). Either way it is non-zero, so a success-only ping is withheld and the monitor treats the run as failed.

How do I get alerted when a job hangs?

Ping the monitor only on a clean completion, so a run killed by timeout sends nothing and alerts as a miss:

0 3 * * * timeout 30m /usr/local/bin/nightly.sh && curl -fsS -m 10 --retry 3 "https://ping.cronshield.com/<your-check-id>"
Set the monitor's expected window to your normal run time plus a small margin — well under the schedule interval — so a hang surfaces before the next run would overlap. PING_URL is a placeholder for the endpoint you get on a monitor.

Catch this failure automatically

The free tier gives you a heartbeat endpoint and an email alert when an expected ping doesn't arrive. Paid tiers add the log-aware diagnosis — the last log line and a likely cause in the alert. The heartbeat receiver ships in an upcoming release; see the plans to learn what each tier adds.

Frequently asked questions

What exit code does the timeout command return?
The coreutils `timeout` command exits 124 when the command runs past the limit and is killed. With --kill-after, if it escalates to SIGKILL the status reflects the signal instead (137). Both are non-zero, so a success-only heartbeat is correctly withheld.
How do I stop cron jobs from overlapping?
Wrap the command in `flock -n <lockfile>` so a second run can't acquire the lock while the first still holds it, and add a `timeout` so a hung run is eventually killed and the lock released. Together they prevent both indefinite hangs and stacked duplicate runs.