20.2 crontab -e, -l, -r: Managing User Crontabs
Alright, let’s get our hands dirty with the actual management of your crontab. This is where you, the user, get to tell cron what to do and when. Forget about poking around in /etc/ directories for a moment; your personal crontab is your own sandbox, and the crontab command is your shovel.
The key thing to engrave into your brain right now: You do NOT edit the crontab file directly. I know, I know, your text editor is begging for action. Resist. The system keeps a master database of user crontabs, usually in /var/spool/cron/ or /usr/lib/cron/tabs/, but touching those files manually is a one-way ticket to permission-denied town and potential disaster. The crontab command is the only sanctioned, safe way to interact with your scheduled tasks. It handles the locking, the syntax checking, and the installation for you. Use it.
The Trinity of Command: -e, -l, -r
Your entire interaction with your personal crontab boils down to three flags. It’s almost elegant, if you ignore the part where you’ll inevitably mess up the timing syntax.
crontab -e: This is your “edit” command. It’s your front door. It opens your user’s crontab in your default editor ($EDITORenvironment variable) or, if that’s not set, it’ll probably fall back tovi. Pray it’s noted. If this is your first time, it’ll likely be an empty file. You write your cron jobs, save, and exit. Thecrontabcommand then performs a basic syntax check on your file. If it’s happy, it installs the new version. If not, it’ll usually yell at you and ask if you want to retry or quit. Always retry. Don’t force-install a broken crontab; it will be rejected by cron anyway and you’ll be left with no schedules running.crontab -l: This “lists” your current crontab to stdout. It’s a quick and dirty way to see what you’ve actually got scheduled without opening an editor. Think of it as a sanity check. Is that script you thought you set up last week actually in there?crontab -lwill tell you the cold, hard truth.crontab -r: This “removes” your entire crontab. This is the danger command. It doesn’t move it to trash. It doesn’t ask “Are you sure?” (unless you alias it to do so). It just nukes it from orbit. Poof. Gone. There is no undo. I once watched a sysadmin fat-finger this on a production machine at 3 AM. The silence that followed was deafening. Use this with the same caution you’d use a flamethrower in a library.
Actually Editing Your Crontab (-e)
You type crontab -e. The editor opens. Now what? You add lines. Each line is either a comment (starting with #) or a job line. A job line is a glorious, punctuation-heavy mess of five time fields followed by the command to run.
# This is a comment. It will be ignored by cron, but saved for your future, forgetful self.
# Run my backup script every day at 2:15 AM
15 2 * * * /home/you/scripts/backup.sh
# This is a terrible idea. We'll get to why.
* * * * * /usr/bin/curl -s http://localhost/health_check > /dev/null 2>&1
# Send yourself a motivational message every Friday at 4:30 PM. Because you deserve it.
30 16 * * 5 /usr/bin/echo "It's Friday! The weekend is here!" | mail -s "Cheer up!" you@example.com
The command portion isn’t just the program name; it’s the entire command line as if you were typing it from a shell. This is a crucial distinction. Cron provides a minimal environment—a bare-bones PATH, no fancy environment variables you set in your .bashrc, and no connected terminal. This leads us directly to the most common pitfall.
The Environment Pitfall (And How to Avoid It)
Cron does not run in your cozy, customized shell environment. It runs in a clean, sparse, almost sterile environment. Your $PATH is probably just /usr/bin:/bin. It doesn’t know about nvm, rbenv, pyenv, or that cool script you installed in ~/bin/.
This is why 90% of “my cron job didn’t run” problems exist. Your script works fine on the command line because your PATH includes /usr/local/bin/ and you’ve sourced your .bashrc. Cron has none of that.
The solution is twofold:
- Use absolute paths for everything. Not
python3, but/usr/bin/python3. Notmy_script.sh, but/home/you/scripts/my_script.sh. - Set your own environment at the top of your crontab. You can define variables that will be used for every job.
# Set the PATH to something useful. This is your first line of defense.
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# Set the MAILTO variable to get emailed output (if your system is set up for it)
MAILTO=you@example.com
# Define any other custom variables your scripts need
MY_APP_HOME=/home/you/my_app
# Now your jobs can use these
0 * * * * /usr/local/bin/python3 /home/you/scripts/nightly_import.py
The Output Problem (And the /dev/null Abyss)
Unless you tell it otherwise, cron will try to email you the stdout and stderr of any job that runs. If your system isn’t running a mail server (most aren’t by default these days), this output goes into a void and cron might eventually get grumpy.
You have three choices:
- Actually capture the output for logging by redirecting it to a file yourself.
0 * * * * /home/you/script.sh >> /home/you/cron.log 2>&1 - Silence it completely if you’re sure you don’t want it.
0 * * * * /home/you/script.sh > /dev/null 2>&1(The2>&1sends stderr to the same place as stdout). - Configure
MAILTOand set up a local mail system if you want the classic behavior.
My advice? Redirect output to a log file for anything non-trivial. For tiny, “fire-and-forget” jobs, silencing them is fine. Never let the output just vanish into the system’s failed email queue; that’s how you forget a job is failing silently for months.