One nightly cron job stopped working weeks ago and nobody noticed; another crontab line was "fixed" with `> /dev/null 2>&1` after it flooded an operator's mailbox. What does cron do with whatever a scheduled command prints, and how should a scheduled script's output be handled instead?
answer
- output is the alert, not the exit code
- nothing printed means nothing mailed
- MAILTO and the missing mail transport
- 2>&1 to /dev/null hides errors too
- quiet on success, log to a file
basics
~20 scron mails any output a job produces to the crontab owner, or to MAILTO if set, and mails nothing when the job prints nothing. Appending > /dev/null 2>&1 discards errors too, which is why failures go unnoticed.
solid answer
~50 scron captures a job's stdout and stderr; if there is any output it mails it to the crontab owner, or to the address in `MAILTO`, and `MAILTO=""` disables mail for the entries that follow. That mechanism is fragile: it needs a working local mail transport, and on a minimal server or container there usually is none, so cron just logs that it discarded the output. Because a chatty job then mails on every successful run, people silence it with `> /dev/null 2>&1` — which throws away the error text as well, so the job fails silently forever. The better shape is a script that is quiet on success and prints only real problems, with output appended to a log file, and failure reported to whatever actually pages you rather than to a mail spool. Note that cron mails output, not exit status, so a job that fails silently with a non-zero code produces no mail at all.
code
bash · 8 lines#!/usr/bin/env bash
# Quiet on success, loud on failure, with a durable log.
log=/var/log/backup.log
if ! /opt/app/do-backup --quiet >>"$log" 2>&1; then
printf 'backup failed on %s, see %s\n' "$(hostname)" "$log" >&2
exit 1
figo deeper
Remember that cron mails whatever the job prints to the crontab owner, and that redirecting both streams to /dev/null makes errors disappear along with the noise.
Explain the MAILTO variable and its empty-string form, and be able to say why the output-is-the-alert design breaks on a host with no local mail transport.
Show the operational fix: scripts quiet on success, output appended to a real log, and failures routed to actual alerting rather than a local mail spool.
Own the distinction between a job that failed and a job that never ran, and argue for heartbeat or dead-man's-switch monitoring across the fleet, since no output-based mechanism can detect absence.
## What cron actually does When cron runs an entry it attaches the command's standard output and standard error to a pipe rather than to a terminal, since there is no terminal. If anything at all comes out of that pipe, cron composes a mail message containing the output and sends it to the crontab's owner. If nothing comes out, no mail is sent. That is the whole notification design: **output is the alert**. Two variables in the crontab steer it. `MAILTO=address` sends the mail somewhere else, and it applies to every entry below it in the file, so you can set different destinations for different blocks. `MAILTO=""` disables mail entirely for the entries that follow. Crucially, cron mails **output**, not **exit status**. A job that exits 1 while printing nothing generates no mail. Most cron implementations do log the run to syslog or the journal, and some record the exit status there, but that is a log line nobody reads, not a notification. ## Why the mail so often never arrives Mail delivery is not cron's job — it hands the message to a local mail transport agent. Desktop and traditional server installs have one; minimal cloud images, containers and hardened hosts frequently do not. In that case the output is simply dropped, and the cron daemon typically logs a note that no mail transport is installed and the output was discarded. Teams then get the worst outcome available: a mechanism they believe is alerting them, that has never delivered anything. Even where mail works, it goes to a local spool file on that host, not to a rotation, a chat channel or an on-call system. It is a 1980s design that survives because it is the default. ## The `> /dev/null 2>&1` reflex A job that prints a progress line on every successful run mails the owner every night. The usual response is to append `> /dev/null 2>&1` to the crontab line. That redirects standard output to the bit bucket and then points standard error at the same place, so **both** streams are discarded. The mailbox goes quiet, and so does the failure that arrives three weeks later. This is the single most common way a scheduled job becomes invisible. ## The shape that works **Make the script quiet on success.** The convention that makes cron's design usable is silence when there is nothing wrong. A script that prints nothing on a clean run needs no redirection at all, and any output it produces is by definition worth reading. **Keep a durable log anyway.** Diagnosis needs history, and mail is not history: ```bash 0 3 * * * /opt/app/backup.sh >> /var/log/backup.log 2>&1 ``` This keeps everything, forfeits mail, and needs its own rotation — an acceptable trade when the script reports failures through a channel that actually reaches a human. **Report failures deliberately.** Do not rely on the mail path being intact. Have the script call your alerting on a failing exit: ```bash if ! /opt/app/backup.sh >>"$log" 2>&1; then /usr/local/bin/notify-oncall "backup failed, see $log on $(hostname)" fi ``` **Or keep the output but only on failure.** The `chronic` wrapper from moreutils runs a command, swallows its output when it succeeds, and prints everything it captured when it fails — which turns a chatty command into a cron entry that mails only when something is wrong, without editing the command itself. ## Answering "did it even run?" When a job appears not to have run, distinguish three states: cron never started it, it started and failed, or it ran fine and did nothing visible. The cron daemon's own log records the first, an exit status or your log file records the second. A run marker the script itself writes — a touched file or a heartbeat ping — is what lets you assert the third, and a monitoring check on the freshness of that marker is far more reliable than waiting for a message that stopped arriving.
- Does cron mail anything when a job exits non-zero but prints nothing?No. Mail is triggered by output on stdout or stderr, not by exit status. A silent failing job produces no mail at all. Most cron daemons log the run, and some record the status there, but if you want a non-zero exit to notify anyone the script has to say something or call your alerting itself.
- If you must keep both a log file and cron's mail behaviour, how do you get them?Send the routine output to the log and leave stderr alone: `>>/var/log/job.log` with no `2>&1`. Then successful chatter is filed away and anything on stderr still triggers mail. Alternatively wrap the command in moreutils' `chronic`, which buffers all output and prints it only if the command fails.
- Why is a heartbeat file or a monitoring ping better than relying on cron mail?Mail only tells you about runs that happened and produced output; it cannot tell you that a job never started because cron was down, the crontab was wiped, or the host was off. A freshness check on a marker the job writes detects absence, which is the failure mode mail is structurally blind to.
saying these in an interview costs you the question
- Believes cron mails a notification whenever a job exits non-zero
- Adds > /dev/null 2>&1 to silence a job and calls it fixed
- Assumes cron mail is delivered on a host with no mail transport
- Thinks MAILTO="" disables the job rather than its mail
- Treats a chatty successful run as normal instead of noise