Where to find cron logs and how to log job output

Updated November 6, 2026

Cron keeps two different things, and people often look for one in the place of the other. The cron log records that a command was started. The job output, everything the command printed, goes somewhere else, or nowhere at all. This guide shows where to find both on common systems and how to keep the output so you can read it later.

Where the cron log is

SystemWhere to look
Ubuntu, Debian with rsysloggrep CRON /var/log/syslog
Debian 12 and other systems with only journaldjournalctl -u cron --since today
RHEL, Rocky, AlmaLinux, Fedora, CentOS/var/log/cron or journalctl -u crond
Alpine (BusyBox crond)/var/log/messages, if syslog is running
macOSlog show --last 1h --predicate 'process == "cron"'

A typical line looks like this:

Nov  6 03:17:01 web1 CRON[48211]: (deploy) CMD (/srv/backup.sh)

It tells you that cron started /srv/backup.sh as the user deploy at 03:17. It doesn’t tell you whether the script worked. If there is no such line at the expected time, the problem is the schedule, the crontab or the daemon itself; the checklist for a cron job that doesn’t run goes through those in order.

Where the output goes by default

Cron mails anything a job prints to the crontab’s owner, through the local mail system. On a modern server there usually isn’t one, so the output is thrown away. Debian and Ubuntu say so in the log:

CRON[48211]: (CRON) info (No MTA installed, discarding output)

You can point the mail somewhere else with MAILTO=you@example.com at the top of the crontab, but only if the server can actually send email. MAILTO="" turns the mail off.

Save the output to a file

The simplest fix is to redirect both output streams to a log file:

17 3 * * * /srv/backup.sh >> /var/log/backup.log 2>&1

Add timestamps

A log without times is hard to read when a job runs every few minutes. Put the redirect and a start line inside the script itself:

#!/bin/sh
exec >> /var/log/backup.log 2>&1
echo "$(date '+%F %T') start"

pg_dump -Fc app > /backups/app-$(date +%F).dump
status=$?

echo "$(date '+%F %T') done, exit $status"
exit $status

Writing the date inside the script also avoids a classic trap: in a crontab line, % means a new line, so date +%F has to be written as date +\%F there.

Or send it to syslog

To keep job output with the rest of the system logs, pipe it to logger:

17 3 * * * /srv/backup.sh 2>&1 | logger -t backup

Read it with journalctl -t backup or grep backup /var/log/syslog. One catch: the exit status of this line is the status of logger, not of the backup, so anything that checks it sees success.

Don’t let the file grow forever

A job that runs every minute can fill a disk with its log. Add a logrotate rule:

# /etc/logrotate.d/backup
/var/log/backup.log {
    weekly
    rotate 8
    compress
    missingok
    notifempty
}

Logs tell you what happened, not what didn’t

Even a perfect log only helps when someone reads it. If the job stops running, nothing is written at all, and an empty log looks the same as a quiet one. The usual answer is a heartbeat: the job pings a URL after every run, and you get an alert when a ping doesn’t arrive on time or reports a failure.

With the heartcron CLI it is one change to the crontab line. It reports the start, the exit code and the last part of the output, so the output of a failed run is in the alert:

17 3 * * * heartcron run nightly-backup -- /srv/backup.sh >> /var/log/backup.log 2>&1

Without the CLI, capture the exit code first and then send it with the output:

out=$(/srv/backup.sh 2>&1); code=$?
printf '%s' "$out" | tail -c 100000 | curl -fsS -m 10 --data-binary @- https://ping.heartcron.com/<uuid>/$code

Create the check with the same schedule as the crontab line; the cron expression explainer shows the next runs if you want to double-check it. Every signal is in the docs.

More guides