SMS, Telegram or Bale? Choosing Alert Channels Without Alert Fatigue
Which downtime alerts should go by SMS and which to Telegram or Bale, how to route by urgency, and how to keep the important alerts from drowning in noise.
A scheduled job that runs perfectly from your terminal can fail the moment cron starts it. Most of those failures are introduced while the crontab is being edited: a typo in a time field, a relative path, a variable that only exists in your login shell, or simply the wrong user's file.
This guide walks through editing a crontab the safe way, explains why jobs break after an edit, and shows how to confirm that a change actually works instead of hoping it does.
crontab -e - it checks the file before installing it.crontab -l before every change.PATH and SHELL yourself.noc724 checks your websites, servers and APIs around the clock and alerts you by SMS, Telegram or Bale the moment something breaks.
A crontab ("cron table") is a per-user list of scheduled commands. Each line pairs a schedule with a command, and the cron daemon reads the installed tables and starts the commands on time.
There are two ways to change one:
crontab -e opens a temporary copy of your table in an editor. When you save and quit, the crontab tool checks the syntax and installs the new version./var/spool/cron/) skips that check, can leave wrong ownership or permissions behind, and is the easiest way to make cron silently ignore a table.Use crontab -e for user crontabs. System-wide jobs belong in /etc/crontab or /etc/cron.d/, which you edit as root with a normal editor - note that those files have an extra user field on every line.
There is also a difference between editing and *replacing*: crontab jobs.txt swaps the whole table for the contents of a file. That is handy for automation, and dangerous when typed by hand into the wrong account.
crontab -e doesn't have an editor of its own. It looks at the VISUAL variable, then EDITOR, and falls back to a system default (often vi, sometimes nano). Check what you have:
echo "VISUAL=$VISUAL EDITOR=$EDITOR"EDITOR=nano crontab -eAdd the variable to your shell's start-up file (~/.bashrc for Bash, ~/.zshrc for Zsh) and open a new shell:
echo 'export EDITOR=nano' >> ~/.bashrcOn Debian-based systems select-editor stores your choice in ~/.selected_editor, and crontab -e uses it from then on.
Slim images often ship without nano or even vi. Install one, or generate the file elsewhere and install it with crontab file - just keep a copy of the old table first.
Every job line has five time fields followed by the command:
# ┌ minute (0-59)
# │ ┌ hour (0-23)
# │ │ ┌ day of month (1-31)
# │ │ │ ┌ month (1-12)
# │ │ │ │ ┌ day of week (0-7, 0 and 7 are Sunday)
# │ │ │ │ │
30 2 * * * /usr/local/bin/backup.sh| Operator | Meaning | Example |
|---|---|---|
* | every value | * * * * * - every minute |
, | a list | 0 9,17 * * * - 09:00 and 17:00 |
- | a range | 0 9-17 * * 1-5 - hourly, office hours, weekdays |
/ | a step | */15 * * * * - every 15 minutes |
When both day fields are restricted, cron runs the job when either matches, not when both do. 0 0 1 * 1 runs on the 1st of every month *and* on every Monday.
Most cron implementations accept shortcuts such as @reboot, @hourly, @daily, @weekly and @monthly. They are easier to read, but check that your cron supports them before relying on them.
If you'd rather not count fields by hand, build the expression with the cron expression generator and paste the result.
crontab -l > ~/crontab-backup-$(date +%F).txtIf something goes wrong, crontab ~/crontab-backup-2026-10-18.txt puts the old version back.
Run it exactly as cron will: full paths, from a neutral directory, ideally with a stripped environment.
cd / && env -i HOME="$HOME" SHELL=/bin/sh PATH=/usr/bin:/bin /usr/local/bin/backup.shIf it fails here, it will fail under cron too.
After saving, crontab prints installing new crontab. If it reports an error, it offers to re-open the editor - accept, and fix the line instead of discarding your work. Then list the table to see what is really installed:
crontab -lSchedule a near-term run (for example two minutes from now), confirm it happened in the log, then set the real schedule.
A time field holds an impossible value (60 minutes, month 13), a field is missing, or a stray character slipped in. The error names the line - fix it when crontab offers to re-edit.
Many cron versions ignore a final line that doesn't end with a newline. Leave an empty line at the end of the file.
In a crontab, an unescaped % ends the command and turns the rest into standard input. A date +%F inside a job must be written as date +\%F.
sudo crontab -e edits root's crontab, not yours. Jobs that need your files, keys or permissions then run as the wrong user. Use crontab -u name -e when you really mean another account, and check with crontab -u name -l.
If /etc/cron.allow exists, only the users listed in it may have crontabs; otherwise anyone not in /etc/cron.deny may. A "you are not allowed" message means the user is missing from one of these lists.
Cron does not load your .bashrc or .profile. PATH is usually just /usr/bin:/bin, SHELL is /bin/sh, and variables you exported in your terminal are not there. Commands found in /usr/local/bin, a virtualenv or a version manager are simply "not found".
Jobs start in the user's home directory. ./run.sh or python script.py works in your terminal because you happen to be in the right folder; under cron it doesn't. Use absolute paths for the program and for every file it touches.
Arrays, [[ ]], source and other Bash features fail under /bin/sh. Set SHELL=/bin/bash at the top of the crontab, or start the job with /bin/bash /path/script.sh.
Without redirection, cron tries to mail the output to the user; on most servers no mail is set up, so errors vanish. Redirect both streams:
*/10 * * * * /opt/app/sync.sh >> /var/log/app-sync.log 2>&1Variable lines go above the jobs that need them and apply to every line below:
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=""
APP_ENV=production
0 3 * * * /opt/app/bin/nightly-report >> /var/log/nightly-report.log 2>&1Cron does not expand variables in these assignments - PATH=$PATH:/opt/bin stores the literal text. Write the full value. Once a job needs more than a couple of variables, move the setup into a wrapper script and keep the crontab line short.
Configuration tools and deploy scripts should never open an editor. Two safe patterns:
crontab /srv/deploy/app.crontabThe file in your repository becomes the single source of truth, and every change is reviewed like code.
( crontab -l 2>/dev/null | grep -v 'nightly-report' ; echo '0 3 * * * /opt/app/bin/nightly-report' ) | crontab -The grep -v removes an older copy of the same job first, so running the script twice doesn't create duplicates.
A job that sometimes takes longer than its interval can end up running twice at once. flock lets only one copy run:
*/5 * * * * flock -n /tmp/sync.lock /opt/app/sync.sh >> /var/log/app-sync.log 2>&1An installed crontab only proves the schedule was accepted. It says nothing about whether the job started, finished, or did its work. Logs help after the fact, but nobody reads them at 3 a.m.
Heartbeat monitoring closes that gap: the job calls a unique URL when it finishes, and if the call doesn't arrive on time you get an alert.
0 3 * * * /opt/app/backup.sh && curl -fsS -m 10 https://YOUR-HEARTBEAT-URL > /dev/nullThe && means the ping is sent only when the backup succeeded, so a failed run and a run that never started both raise an alert. noc724's cron job monitoring works this way and sends the alert by SMS, Telegram or Bale.
Editing a crontab safely comes down to a short routine: back up, test the command the way cron will run it, edit with crontab -e, read back what was installed, and watch the first run. Most "it worked before I edited it" problems are environment problems - paths, shells and variables - rather than cron itself. And because cron never tells you when a job silently stops, pair every important job with a heartbeat check.
Run crontab -e. It opens your user's table in the editor set by VISUAL or EDITOR and installs it when you save.
crontab -l lists your table; sudo crontab -u name -l lists another user's. System jobs live in /etc/crontab and /etc/cron.d/.
No. crontab -e notifies the daemon; it picks up the new table within a minute. Files in /etc/cron.d/ are also re-read automatically on most systems.
crontab -r deletes your whole table without asking. Take a backup with crontab -l first - and note how close -r is to -e on the keyboard.
Usually because of the environment: a short PATH, /bin/sh instead of Bash, missing variables or relative paths. Use absolute paths and set the variables in the crontab or in a wrapper script.
Written by
The noc724 team
Uptime monitoring engineers
We build and run noc724, an uptime monitoring service that checks websites, servers and APIs from several cities in Iran. These guides come from the problems we and our users run into while keeping services online.
Contact the teamExpert on:Cron Monitoring
Which downtime alerts should go by SMS and which to Telegram or Bale, how to route by urgency, and how to keep the important alerts from drowning in noise.
Why uptime monitors raise false alarms - network blips, timeouts, rate limits, content changes - and the settings that remove them while keeping real alerts fast.
How to set up a simple on-call rotation and escalation policy so every critical alert is answered, without burning out a small team.