Editing a crontab is one of the most common sysadmin tasks in a developer’s week and one of the most common sources of silent breakage in a production system — because the default editor is a coin flip, because the crontab runs as the user with no environment, because the crontab does not survive a crontab -r typo, and because the crontab is the wrong tool for most new jobs in 2026. The naive answer is “run crontab -e and edit.” The working answer is the six commands a real developer uses, the three commands that delete the entire crontab without confirmation, the seven syntax mistakes that turn a working schedule into a non-running one, and the three modern alternatives (systemd timers, Kubernetes CronJob, a hosted cron service) that have made crontab the right answer for a developer who already has a Linux server and the wrong answer for almost everything else.
The reason “edit crontab” is its own question and not just “cron” is that the edit step is the one that breaks. The crontab is one of the few system files that is intentionally not edited directly, that is intentionally loaded into a per-user binary spool, and that is intentionally validated on save. The crontab is also one of the few system files that gives the developer a no-questions-asked delete command (crontab -r) and a no-questions-asked “I meant to delete that” recovery story (there is none). The crontab is a powerful tool, and the crontab is a tool that punishes the developer who treats it carelessly.
Table of contents
- The short version
- The six commands that matter
- The seven syntax mistakes that turn a working schedule into a non-running one
- The three commands you should never run
- The three modern alternatives that have made crontab the wrong tool for most new jobs
- How this fits the rest of the deploy
- FAQ
The short version
A crontab is the per-user file that lists the scheduled commands the cron daemon runs on behalf of that user. The crontab is at /var/spool/cron/crontabs/<username> (the binary spool) and at /etc/crontab (the system-wide one). The crontab is edited with crontab -e (the right answer), not by opening the spool file directly (the wrong answer), and the crontab is read on save, not on cron restart, and the crontab is the source of the “why is my cron not running” bug 80% of the time.
The six commands that matter
crontab -e (edit). The command opens the current user’s crontab in the editor specified by the EDITOR or VISUAL environment variable (nano by default on most systems, vi on minimal containers, vim on most developer machines), validates the syntax on save, and installs the new crontab atomically. The pattern is the right answer for any crontab edit, the pattern is the only safe way to edit a crontab, and the pattern is the lever that turns “I edited the crontab and the syntax is valid” into “I edited the crontab and it is loaded.” Run export EDITOR=nano (or vim, or code --wait) before running crontab -e to pick a sane default editor.
crontab -l (list). The command prints the current user’s crontab to stdout. The pattern is the right answer for “what is in my crontab right now,” the pattern is the right answer for diffing against a backup, and the pattern is the lever that turns “I have no idea what is in there” into “I know exactly what is in there.” Use crontab -l | grep -v '^#' to skip the comments.
crontab -r (remove). The command deletes the current user’s crontab without asking for confirmation. The pattern is the wrong answer for almost every real use case, the pattern is the one that catches the developer who typed crontab -r when they meant crontab -e, and the pattern is the lever that turns “I have a crontab” into “I have an empty crontab.” Use crontab -r -i (if supported) to get a confirmation prompt, or pipe the empty crontab: crontab -l | crontab - to overwrite with the current contents (a no-op that does not delete).
crontab <file> (install from a file). The command reads a crontab-formatted file from stdin or from a file path and installs it as the current user’s crontab. The pattern is the right answer for “I have a crontab in a file in my dotfiles repo,” the pattern is the right answer for “I want to test a crontab before installing it,” and the pattern is the lever that turns “I am editing a live crontab” into “I am editing a file and then installing it.” Use crontab crontab.backup to back up, crontab new.crontab to install from a file, and crontab - to read from stdin (e.g. crontab -l | crontab - to reinstall the current crontab).
crontab -u <user> -e (edit another user’s crontab, root only). The command opens the specified user’s crontab in the editor. The pattern requires root, the pattern is the right answer for a server admin who is debugging another user’s scheduled job, and the pattern is the right answer for a developer who has multiple service accounts (e.g. www-data, app) with their own crontabs. The pattern is the lever that turns “I cannot find the crontab for the app user” into “I am editing the app user’s crontab.” Replace -e with -l or -r to list or remove another user’s crontab.
crontab -T <file> (test, BSD / macOS only). Some BSD-derived crontab implementations support a -T flag to test a crontab file for syntax errors before installing. The pattern is not on Linux (where the validation happens on save in the editor), the pattern is the right answer on macOS for “I want to test without installing,” and the pattern is the lever that turns “I am about to install a broken crontab” into “I tested the crontab and it is valid.” On Linux, the editor’s save-time validation does the same job.
The six are the floor. There is also crontab -V (version), crontab -h (help), and the system-wide crontab at /etc/crontab (which has a user field in each line: * * * * * root /path/to/command).
The seven syntax mistakes that turn a working schedule into a non-running one
A short, opinionated list of the seven mistakes that have actually broken real cron jobs. None of them are dramatic. They are the boring ones.
Missing the absolute path. A crontab runs with a very minimal PATH (usually /usr/bin:/bin), and the developer who writes node script.js (relying on the developer’s shell PATH) is going to see “command not found” in the cron logs. The fix is to use the absolute path (/usr/local/bin/node /home/me/script.js) or to set PATH= at the top of the crontab, and the fix is the lever that turns “the command works in my shell but not in cron” into “the command works in both.”
Missing the absolute path to the script. A crontab that runs python script.py from a working directory that is not the script’s directory is going to fail to find the script, and the developer is going to see “No such file or directory” in the logs. The fix is to cd to the script’s directory first (* * * * * cd /home/me/app && python script.py) or to use the absolute path, and the fix is the lever that turns “the script runs in my shell but not in cron” into “the script runs in both.”
Missing the newline at the end of the file. A crontab that does not end with a newline is a crontab that the cron daemon may not load (it reads line-by-line, and the last line without a newline is silently dropped). The fix is to make sure the file ends with a newline, and the fix is the lever that turns “my last job is not running” into “my last job is running.”
Misreading the five fields. A crontab line is minute hour day-of-month month day-of-week command, and the developer who swaps the hour and the minute is going to see the job run on the wrong schedule. The fix is to use crontab.guru (a crontab expression visualizer that explains any crontab in English) or to add a comment above each line that explains the schedule in plain English, and the fix is the lever that turns “I think it runs at 3am but it runs at 3 minutes past every hour” into “I know it runs at 3am.”
Using * * * * * for “every minute” and then complaining about the log spam. A crontab that runs every minute is a crontab that is going to fill the logs, the database, and the developer’s inbox. The fix is to use a sensible schedule (*/15 * * * * for every 15 minutes, 0 * * * * for every hour, 0 0 * * * for every day) or to use a backoff schedule, and the fix is the lever that turns “my inbox is full of cron errors” into “my inbox is full of real errors.”
The environment variables are not set. A crontab that runs python manage.py migrate is a crontab that needs DATABASE_URL set, and the developer who sets DATABASE_URL in ~/.bashrc is going to be confused when the cron job fails with “no such database.” The fix is to set the env var in the crontab (DATABASE_URL=postgres://... * * * * cd /app && python manage.py migrate) or to source the env file (* * * * * set -a; source /app/.env; set +a; cd /app && python manage.py migrate), and the fix is the lever that turns “the job works in my shell but not in cron” into “the job works in both.”
The script outputs to stdout/stderr and the output is emailed. A crontab that produces a lot of stdout (or stderr) is a crontab that is going to email the developer (or, worse, fill /var/mail/<user> until the disk is full). The fix is to redirect output to a log file (>> /var/log/myapp/cron.log 2>&1) or to /dev/null if the developer does not care, and the fix is the lever that turns “my disk is full of cron output” into “my disk has the output I want.”
The three commands you should never run (or, the three ways to delete a crontab by accident)
A short, opinionated list of the three commands that delete a crontab (or parts of it) without confirmation. A developer who reads this list once is a developer who is going to recognize the typo before they hit Enter.
crontab -r. The command deletes the current user’s crontab. The pattern is the right answer for “I want to start over,” the pattern is the wrong answer for “I want to edit one line and then accidentally typed -r instead of -e,” and the pattern is the lever that turns “I have a crontab” into “I have an empty crontab.” Use crontab -r -i (if supported) to get a confirmation prompt, or alias crontab='crontab -i' in ~/.bashrc to make -i the default.
echo "" | crontab -. The command installs an empty crontab (effectively the same as crontab -r). The pattern is the right answer for “I want to clear the crontab in a script,” the pattern is the wrong answer for “I want to test a one-line cron job and I piped the wrong file,” and the pattern is the lever that turns “I have a crontab” into “I have an empty crontab.” Always back up first: crontab -l > /tmp/cron.backup && echo "" | crontab -.
crontab -u <user> -r (run as root). The command deletes the specified user’s crontab. The pattern is the right answer for “I am a sysadmin cleaning up a departed user’s account,” the pattern is the wrong answer for “I am a sysadmin who typed the wrong username,” and the pattern is the lever that turns ”crontab -u <user> -l first), and always back up (crontab -u <user> -l > /tmp/cron.<user>.backup).
The three are the floor. There is also rm -rf /var/spool/cron/ (deletes every user’s crontab, requires root, and is the lever that turns “everyone has a crontab” into “nobody has a crontab”), and systemctl stop cron followed by editing the spool directly and systemctl start cron (the lever that turns “I bypassed the validation” into “I do not know what cron is doing right now”).
The three modern alternatives that have made crontab the wrong tool for most new jobs
A short, opinionated list of the three alternatives that have made crontab the wrong tool for most new jobs in 2026. None of them are replacements for every crontab use case. They are replacements for the use cases crontab was never great at.
systemd timers (the modern Linux default). A systemd timer is a unit file (/etc/systemd/system/myjob.timer + /etc/systemd/system/myjob.service) that schedules a service to run on a calendar (OnCalendar=*-*-* 03:00:00) or interval (OnUnitActiveSec=1h). The timer has the same cron-like syntax as crontab, plus real-time scheduling, plus dependencies (Requires=, After=), plus logging (journalctl -u myjob.service), plus retry policies, plus the ability to skip runs if the system was off (Persistent=true). The pattern is the right answer for a Linux server that already uses systemd, the pattern is the right answer for a job that needs to run reliably, and the pattern is the lever that turns “I have a crontab with no logs” into “I have a systemd timer with full journal logs.”
Kubernetes CronJob (the container default). A Kubernetes CronJob is a resource (apiVersion: batch/v1, kind: CronJob) that schedules a Job to run on a cron schedule. The job runs in a container, the job inherits the cluster’s observability (logs, metrics, alerts), the job has a history limit (successfulJobsHistoryLimit, failedJobsHistoryLimit), the job has a concurrency policy (Allow, Forbid, Replace), and the job can be suspended (suspend: true) without deleting the schedule. The pattern is the right answer for a containerized workload, the pattern is the right answer for a job that needs to scale, and the pattern is the lever that turns “I have a crontab on a single server” into “I have a CronJob that runs anywhere the cluster runs.”
A hosted cron service (the platform default). A hosted cron service (e.g. a platform’s built-in scheduler, a serverless function with a timer trigger, a third-party service like EasyCron or cron-job.org) is a service that runs the developer’s job on a schedule, in a managed environment, with logs, retries, and alerting built in. The pattern is the right answer for a developer who does not want to operate a server, the pattern is the right answer for a job that needs to be reliable, and the pattern is the lever that turns “I have a crontab on a server I have to keep alive” into “I have a job that runs whether or not my server is up.”
The three are the floor. There is also anacron (the right answer for a laptop or a desktop that is not always on), at (the right answer for a one-shot job in the future), and launchd (the macOS default, which uses XML plists instead of crontab syntax).
How this fits the rest of the deploy
A crontab rarely lives in isolation. The crontab is usually part of a stack (a Linux server, a Kubernetes cluster, a deploy platform, a database backup schedule) that runs the scheduled jobs the project needs. The platform that handles the cron should make the rest of the stack feel like part of the same conversation.
The services layer is the part of the platform that runs the long-lived API the cron triggers. The database layer is the part that holds the data the cron reads and writes. The environment variables are the part that holds the secrets the cron needs. The crontab is the schedule; the platform is what runs the job.
A project on a platform where the cron, the service, the database, the storage, and the deploy logs are all in the same place is a project the team is going to be able to operate. A project on a platform where each piece is in a different console is a project the team is going to spend the first hour just opening the right tab.
For a team that wants to see the full cost of the project before it commits, the RunxBuild hosting calculator shows the line items together. The service, the database, the storage, the worker, the scheduled job, the bandwidth — each one is a separate number, and the team’s mental model for the platform is the sum of those numbers.
FAQ
How do I edit a crontab safely?
Run crontab -e from the user’s shell (not as root unless you need to edit root’s crontab). The command opens the current crontab in $EDITOR (or nano by default), validates the syntax on save, and installs the new crontab atomically. Always back up first with crontab -l > /tmp/cron.backup so you can restore with crontab /tmp/cron.backup if you make a mistake.
Where is the crontab file stored?
The per-user crontab is at /var/spool/cron/crontabs/<username> (a binary file managed by the crontab command, not a text file you should edit directly). The system-wide crontab is at /etc/crontab (a text file with a user field in each line: * * * * * root /path/to/command). There are also drop-in directories at /etc/cron.d/, /etc/cron.daily/, /etc/cron.hourly/, /etc/cron.weekly/, and /etc/cron.monthly/ for scripts that run on the named schedule.
Do I need to restart cron after editing the crontab?
No. The cron daemon re-reads the crontab on save (the crontab -e command sends a SIGHUP to the cron daemon to trigger a reload). The new crontab is loaded within a second or two of saving.
How do I list all cron jobs on a system?
Run crontab -l for the current user, sudo crontab -u <user> -l for another user, cat /etc/crontab for the system crontab, and ls /etc/cron.d/ /etc/cron.daily/ /etc/cron.hourly/ /etc/cron.weekly/ /etc/cron.monthly/ for the system drop-ins. There is also systemctl list-timers for systemd timers, which is the right answer on a modern Linux server.
How do I run a cron job as a different user?
Use the system crontab at /etc/crontab, which has a user field: * * * * * <user> /path/to/command. Or, as root, run crontab -u <user> -e to edit the user’s crontab directly. The pattern is the right answer for a service account (e.g. www-data, app) that needs its own scheduled jobs.
How do I debug a cron job that is not running?
Check (in order): (1) the cron daemon is running (systemctl status cron), (2) the crontab is loaded (crontab -l), (3) the absolute paths are correct (no ~, no relative paths, no $PATH reliance), (4) the script is executable (chmod +x), (5) the environment is set (env vars in the crontab, not in ~/.bashrc), (6) the output is captured (redirect to a log file), (7) the schedule is right (use crontab.guru to verify), and (8) the system clock is right (timedatectl).
What is the difference between crontab and cron?
cron is the daemon that runs scheduled jobs. crontab is the command that edits the per-user file the daemon reads. The two are usually used interchangeably, but they are different: cron is the runtime, crontab is the config.
Can I edit /etc/crontab directly?
Yes, the system crontab at /etc/crontab is a text file and can be edited directly with sudo nano /etc/crontab. The per-user crontabs at /var/spool/cron/crontabs/ should not be edited directly — they are binary files managed by the crontab command, and direct edits will not be loaded and may be overwritten on the next crontab -e.