Skip to content
🎉 Launch sale:50% off everything over $22 — automatically applied at checkout· ends Aug 2Shop the sale →
DevOps AI ToolKit
Newsletter
All guides
AI for Automation By James Joyner IV · · 8 min read Last reviewed Jul 2026

coreutils timeout Error: 'Terminated (exit status 124)' — Cause, Fix, and Troubleshooting Guide

Quick answer

Fix exit status 124 from the coreutils timeout command — what 124, 125/126/127, and 137 mean, why a job runs past its limit, and how to diagnose and fix it.

  • #automation
  • #troubleshooting
  • #coreutils-timeout
Free toolkit

Fixing errors like this? Get 500 free DevOps AI prompts

500 copy-paste AI prompts for the stack you actually run — one PDF, free.

Overview

The coreutils timeout command runs another command with a deadline. If the command is still running when the deadline passes, timeout sends it SIGTERM, the command dies, and timeout exits with status 124 to signal “the time limit was reached.” In a wrapper or CI log it looks like the job was killed mid-run:

$ timeout 30s /opt/jobs/myjob.sh
/opt/jobs/myjob.sh: line 1: 20933 Terminated  ./do-work
$ echo $?
124

Exit code 124 is the tell: it is not the job’s own error code, it is timeout reporting that it had to kill the command. CI systems that only check $? != 0 will mark the step failed with no obvious reason unless you know to read 124 as “timed out.”

timeout uses a small, fixed set of exit codes, and each means something specific:

124  the command timed out (SIGTERM was sent after the duration elapsed)
125  the timeout command itself failed (bad option, could not run)
126  the command was found but could not be invoked (e.g. not executable)
127  the command was not found
137  the command was killed with SIGKILL (128 + 9) — see --kill-after

If a job ignores SIGTERM, timeout --kill-after follows up with SIGKILL, and you see 137 instead of 124.

Symptoms

  • A step exits with status exactly 124 and the child prints Terminated.
  • The failure happens at a consistent elapsed time (the timeout value), not at a consistent point in the job’s logic.
  • Occasionally 137 instead of 124 when --kill-after escalates to SIGKILL.
  • The job “passes locally” (fast machine, warm cache) but times out in CI (slower runner, cold cache).
  • No stack trace or application error — the process is simply gone.
timeout 5s sleep 30; echo "exit=$?"
exit=124

Common Root Causes

1. The job is genuinely slower than the limit

The workload grew, the machine is slower, or a cache was cold, so the real runtime now exceeds the timeout value.

# How long does it actually take with no limit?
/usr/bin/time -v /opt/jobs/myjob.sh 2>&1 | grep -i 'Elapsed'
	Elapsed (wall clock) time (h:mm:ss or m:ss): 0:47.30

47 seconds under a 30-second limit will always be killed.

2. The job hangs on I/O, a lock, or a network call

The command is not slow, it is stuck — waiting on a lock, a dead endpoint, or a read that never returns — so it never finishes and always hits the limit.

3. A deadlock or infinite wait

Two resources waiting on each other, or a read with no input, blocks forever until timeout intervenes.

4. The limit is set too tight

The timeout was chosen for a best-case run and does not tolerate normal variance, so it fires on ordinary slow runs.

5. The child ignores or traps SIGTERM

The command catches SIGTERM and keeps running, so plain timeout cannot stop it and reports based on how it eventually dies. Without --kill-after, timeout itself may appear to hang.

How to Diagnose

First confirm the 124 came from timeout and not the application, and see the kill happen with verbose mode:

timeout -v 30s /opt/jobs/myjob.sh; echo "exit=$?"
timeout: sending signal TERM to command '/opt/jobs/myjob.sh'
exit=124

-v prints exactly which signal timeout sent and when, removing any doubt about who killed the job.

Measure true runtime against the limit — the single most useful data point:

/usr/bin/time -v /opt/jobs/myjob.sh 2>&1 | grep -iE 'Elapsed|Maximum resident'
	Elapsed (wall clock) time (h:mm:ss or m:ss): 0:47.30

If the elapsed time is under the limit but it still times out intermittently, the job is hanging, not slow. Find where it blocks with strace:

timeout 30s strace -f -T -e trace=network,file /opt/jobs/myjob.sh 2>&1 | tail -20
connect(5, {sa_family=AF_INET, sin_port=htons(8080), sin_addr=inet_addr("10.0.4.21")}, 16) = -1 EINPROGRESS
poll([{fd=5, events=POLLOUT}], 1, -1     <-- blocked here, poll with infinite timeout

A poll(..., -1) (infinite) on a socket that never becomes ready is a hang on a network call — the job would run forever without timeout.

Check whether the child traps SIGTERM, which would explain a job that ignores the first signal:

grep -n "trap.*TERM\|trap.*SIGTERM" /opt/jobs/myjob.sh || echo "no SIGTERM trap"

Fixes

Raise the limit to fit real runtime plus headroom

If the job is legitimately slower than the limit, set the timeout above measured p95 runtime with margin:

# Job runs in ~47s worst case; give it 90s
timeout 90s /opt/jobs/myjob.sh

Escalate to SIGKILL for jobs that ignore SIGTERM

Use --kill-after so a command that traps or ignores SIGTERM is force-killed with SIGKILL a grace period later. Expect exit 137 when this fires:

timeout --kill-after=10s 60s /opt/jobs/myjob.sh; echo "exit=$?"
exit=137

Send a different signal your job handles cleanly

Some jobs shut down gracefully on SIGINT or a custom signal. Use --signal so the job can flush and exit cleanly before the deadline:

timeout --signal=INT 60s /opt/jobs/myjob.sh

Fix the underlying slowness or hang

Add a real timeout to the network calls or lock waits inside the job so it fails fast on its own rather than relying on the outer timeout to reap it. Make the job idempotent so a kill-and-retry cannot corrupt state, and address deadlocks directly.

Distinguish 124 from timeout’s own errors

If you get 125, 126, or 127, the problem is not a slow job — timeout could not run the command at all. Check the path and permissions:

timeout 30s /opt/jobs/myjob.sh; echo $?
127

127 means the command was not found; fix the path rather than raising the limit.

What to Watch Out For

  • Exit 124 is timeout’s code, not your job’s. Do not chase an application bug when the job never actually errored — it was killed. Grep logs for 124 and the Terminated line to classify the failure.
  • 137 means SIGKILL, not SIGTERM. If you see 137, --kill-after escalated (or the OOM killer struck at 128+9). A job that only dies on SIGKILL did not get a chance to clean up — verify no partial writes were left behind.
  • A killed job may leave state half-written. timeout interrupts at an arbitrary point. Wrap non-idempotent work in a lock or transaction so a mid-run kill and retry converges instead of corrupting data.
  • Raising the limit hides a hang. If the true runtime is well under the limit but it still times out, the job is stuck, not slow — a bigger timeout just makes the stall last longer before it fails. Find the blocked syscall with strace.
  • timeout without --kill-after can appear to hang if the child traps SIGTERM. Always pair a signal-trapping job with --kill-after so escalation is guaranteed.
Free download · 368-page PDF

Fixed it? Get 500 Automation & DevOps AI prompts — free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.

Did this fix your issue?

Free download · 368-page PDF

Get 500 Battle-Tested DevOps AI Prompts — Free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.