Half the "cron never ran" tickets turn out to be a command that works fine by hand but fails inside cron's stripped-down environment. This walks through building a per-user job that runs once a day, records both output and errors, and can be verified without waiting for the scheduled time. The examples target Debian's Vixie Cron package, installed here as cron 3.0pl1-184ubuntu2.
Allow about fifteen minutes. You need a shell and permission to edit your own crontab; the example writes under your home directory and needs no sudo.
Cron runs commands in a small, non-interactive environment, so a command that works fine in an interactive terminal can fail simply because its directory is not on cron's PATH. Check the daemon package and the absolute path of whatever you want to schedule:
$ dpkg-query -W -f='${Package} ${Version}\n' cron
cron 3.0pl1-184ubuntu2
$ command -v date
/usr/bin/date
Tip: Always use absolute paths in the crontab command. If your real job is a script, make it executable and use its full path, such as /home/REPLACE_ME/bin/nightly-report: never leave a placeholder path unchanged.
Create a small script in your home directory. This gives you a visible result and keeps the first test well away from a production command:
$ mkdir -p "$HOME/cron-test"
$ cat > "$HOME/cron-test/heartbeat.sh" <<'EOF'
#!/bin/sh
printf 'cron heartbeat: %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
EOF
$ chmod 700 "$HOME/cron-test/heartbeat.sh"
$ "$HOME/cron-test/heartbeat.sh"
cron heartbeat: 2026-09-22T12:00:00Z
Your timestamp will differ; what matters is that the script exits successfully and prints one line. chmod locks the new test file to owner-only access. To remove this test later, run rm -rf -- "$HOME/cron-test", but only after checking the directory holds nothing you want to keep: that deletion is irreversible.
Open your own crontab with crontab -e. This launches your configured editor and updates the spool safely; never edit files under /var/spool/cron/crontabs directly. Add these lines:
SHELL=/bin/sh
MAILTO=""
15 2 * * * /home/REPLACE_ME/cron-test/heartbeat.sh >> /home/REPLACE_ME/cron-test/heartbeat.log 2>&1
/home/REPLACE_ME with the output of printf '%s\n' "$HOME".SHELL selects the shell for the command.MAILTO="" disables cron mail for this test; the log file is the deliberate record instead.One trap worth knowing: a line starting with # is a comment, but an end-of-line comment tacked onto a command is not discarded. It becomes part of the command.
Save and close the editor, then print the installed crontab:
$ crontab -l
SHELL=/bin/sh
MAILTO=""
15 2 * * * /home/REPLACE_ME/cron-test/heartbeat.sh >> /home/REPLACE_ME/cron-test/heartbeat.log 2>&1
The path should show your real home directory, not the placeholder. If crontab -e reports a syntax error, fix the line in the editor and save again; keep the command on one physical line, since a cron command field is limited to 998 characters and a wrapped line is not a continuation.
Checkpoint: If crontab -l prints the three lines and returns to the prompt, the entry is installed for your account. Cron loads changed crontabs automatically; restarting the daemon is not required.
Waiting until 02:15 is unnecessary. Run the command portion manually with the same absolute paths, then inspect the log:
$ /home/REPLACE_ME/cron-test/heartbeat.sh >> /home/REPLACE_ME/cron-test/heartbeat.log 2>&1
$ tail -n 1 /home/REPLACE_ME/cron-test/heartbeat.log
cron heartbeat: 2026-09-22T12:04:00Z
A successful manual run proves the script and redirection work, but not that cron's clock or daemon is healthy. After the next scheduled minute, check the log again. If it has not grown, inspect the cron facility in your system log, for example with journalctl -u cron on a system using systemd; reading logs may need membership of a log-reading group or elevated privileges.
Cron checks entries once per minute. It does not retry a failed command and it does not queue jobs, so make the command idempotent or add your own locking before scheduling anything that must not overlap. Also watch for these:
/etc/crontab and files under /etc/cron.d, there is an extra username field after the five time fields.% becomes a newline, and everything after the first one is sent to standard input. Escape a literal percent as \%.@reboot means cron daemon startup, which can happen before another service your command needs is ready. It is not a general "system fully up" guarantee./etc/crontab and /etc/cron.d must be owned by root and never writable by group or other. Editing them needs elevated privileges and can run commands as another account.Warning: Never put secrets directly in a crontab. It is a plain configuration file readable according to its permissions, and command lines can turn up in process or diagnostic output. Read secrets from a suitably protected file or a dedicated secret mechanism instead.
When you are finished, remove only the test entry with crontab -e, save, and verify with crontab -l. This changes your account's schedule and is reversible by adding the line back:
$ crontab -e
$ crontab -l
SHELL=/bin/sh
MAILTO=""
# the test entry is no longer present
If you want to keep the script but pause it, prefix the schedule line with #. To clean up the files too, inspect them first, then remove the specific test directory:
$ find "$HOME/cron-test" -maxdepth 1 -type f -printf '%f\n'
heartbeat.log
heartbeat.sh
$ rm -rf -- "$HOME/cron-test"
Warning: That final command cannot be undone through cron. Use it only once the listing shows exactly the files you intended to discard.
crontab -l shows the intended schedule for the intended user.