Run a command with a timeout
Kill a command if it has not finished within N seconds — the standard hedge against hung network calls, runaway scripts, and tests that should never block forever.
How to run a command with a timeout in each shell
timeout 10s curl https://example.comGNU `timeout` exits 124 on timeout, 125 on internal error, 126/127 on exec failure, otherwise the command's rc. `-s SIGNAL` to send something other than SIGTERM; `-k 5s` to KILL 5s after the initial signal if the command ignored it.
timeout 10s curl https://example.comtimeout 10s curl https://example.com$job = Start-Job { curl https://example.com }; if (Wait-Job $job -Timeout 10) { Receive-Job $job } else { Stop-Job $job; "timed out" }`Start-Job` runs in a SEPARATE pwsh process — slow start (~200 ms), and the child cannot see parent functions/variables without `-InitializationScript`. For native external commands without job overhead: `$p = Start-Process -PassThru -NoNewWindow curl "..."; if (-not $p.WaitForExit(10000)) { $p.Kill(); "timed out" }`.
start /b /wait powershell -NoProfile -Command "$p = Start-Process -PassThru -NoNewWindow curl -ArgumentList 'https://example.com'; if (-not $p.WaitForExit(10000)) { $p.Kill(); exit 124 }"cmd has no native timeout primitive (the `timeout.exe` shipped on Windows is a SLEEP, not a wait-with-cancel). Shell out to pwsh. The Win32 `WaitForSingleObject` API is the underlying mechanism.
Equivalents listed for Bash, Zsh, Fish, PowerShell, cmd.exe.
Gotchas & notes
- **GNU `timeout` is missing from macOS by default**: the canonical `coreutils` package via Homebrew installs it as `gtimeout` (`brew install coreutils`). Portable fallback that works on every Unix without extra installs: `perl -e 'alarm shift; exec @ARGV' 10 curl https://example.com` — perl is in the base install on macOS, all Linux distros, and the BSDs; `alarm(10)` sends `SIGALRM` after 10s; `exec` replaces the perl process with the target command so the killed PID is the target, not perl. Same idea in python: `python3 -c 'import signal,subprocess,sys; signal.alarm(int(sys.argv[1])); subprocess.run(sys.argv[2:])' 10 curl …`.
- **The `-k` (kill-after) flag matters more than people think**: `timeout 10s cmd` sends SIGTERM at 10s — if `cmd` traps SIGTERM and runs a slow cleanup (or worse, ignores it), it can hang past the supposed timeout. `timeout -k 5s 10s cmd` sends SIGTERM at 10s and SIGKILL at 15s. For "hard cap, no negotiation" scripts (CI watchdogs): `timeout --signal=KILL 10s cmd` (skip the polite phase). Exit code 124 is the canonical "we timed out" sentinel — wrap in `if ! timeout 10s cmd; then [ $? -eq 124 ] && echo "timed out" || echo "cmd failed"; fi`.
- **pwsh has THREE common timeout patterns, all different**: (1) `Wait-Process -Id $p.Id -Timeout 10` throws `TimeoutException` (catchable) — best when you already have a PID; (2) `Start-Job` + `Wait-Job -Timeout 10` returns `$null` on timeout (the job stays running until you `Stop-Job`) — best when you can't shell out cleanly; (3) `Start-Process -PassThru` + `.WaitForExit(10000)` returns boolean (true = exited, false = timeout) — best for native external commands. For pwsh 7 + the `ThreadJob` module, `Start-ThreadJob` is much faster (no separate process) but the wait/timeout pattern is identical. Don't mix patterns — they each have different cleanup obligations.
- **bash `timeout` ALTERNATIVE for inline expressions** (no `timeout` binary needed): `(cmd) & PID=$!; (sleep 10; kill $PID 2>/dev/null) & WATCHDOG=$!; wait $PID; kill $WATCHDOG 2>/dev/null`. Less readable but works on ancient systems. Important: WAIT propagates the child's exit code, but you must distinguish "killed by watchdog" (rc 143 = 128+15) from "exited normally". Modern bash 4.3+: `wait -n $PID; if [ $? -eq 143 ]; then echo timed out; fi`.
- **Network-specific timeouts ARE NEARLY ALWAYS BETTER**: most tools have a built-in `--max-time` / `--connect-timeout` flag that fires inside the syscall, not from a parent process — `curl --max-time 10` sends SIGTERM equivalent precisely when the request crosses 10s WALL clock; `wget --timeout=10`; `ping -W 2 -c 5`; `nc -w 10 host port`; `ssh -o ConnectTimeout=10`. Wrap with shell `timeout` only when the tool itself offers nothing. Anti-pattern: `timeout 10s curl` (the curl can still hang in TLS handshake and only get killed at 10s — fine, but `curl --connect-timeout 5 --max-time 10` is cleaner and exposes the failure mode in curl's rc).
Related commands
Related tasks
- Run a command in the background— Launch a long-running command without blocking the current shell session.
- Wait for a process to finish— Block until a given process exits — useful for sequencing, dependency-management scripts, and orchestrated shutdowns.
- Kill a process by name— Terminate every running process whose executable matches a given name.
- Retry a command on failure— Re-run a command until it succeeds or a retry cap is hit — useful for flaky network calls and CI race conditions.