crontab and System cron
cron Basics covered the five-field time syntax of a crontab line. The next question is more practical: how do you actually install such a line, where can you inspect or remove it, and how does an ordinary user's crontab differ from cron running at the system level? Missing this distinction is one of the most common sources of confusion in practice — a job appears to be "not in cron," when in fact it was written under a different user's account entirely.
What You Will Learn
By the end of this article you will be able to:
- use the
crontabcommand's-e,-l,-r, and-uflags; - explain where the user crontab is stored, and why editing it directly is discouraged;
- distinguish
/etc/crontaband/etc/cron.d/— system-level crontabs — from a per-user crontab; - explain the
/etc/cron.daily/-style directories and howrun-partsexecutes them; - control who is allowed to use cron at all with
cron.allowandcron.deny.
The User Crontab: the crontab Command
Each user manages their own crontab through the crontab command rather than by opening a file directly, because the command notifies the cron daemon that the schedule changed as soon as it saves.
This opens the editor named in the $EDITOR (or $VISUAL) environment variable. If neither is set, the system typically falls back to nano or offers a selection menu:
Add the following line and save:
Verify:
crontab -l prints the current user's active crontab to the screen; there's no need to know where the file lives on disk, because it's never opened directly.
Where the User Crontab Lives
Each user gets a separate file, with 600 permissions — readable only by its owner (and root). Editing this file directly is discouraged: crontab -e validates the file's syntax before saving, while editing the file directly bypasses that check entirely, and a malformed crontab can be silently ignored by the daemon.
Managing Another User's Crontab
The -u flag lets an administrator, via sudo, view or edit another user's crontab. This is common for service accounts (www-data, postgres, and similar) that can't log in interactively themselves.
Clearing a Crontab
-r removes all of the current user's cron jobs, immediately, without confirmation.
crontab -r is not reversible
There is no selective removal — the entire file disappears at once. To remove a single job, open crontab -e and delete that line by hand, or comment it out with a leading # to disable it temporarily. Before running -r, especially on a production server, save the current state first: crontab -l > ~/crontab-backup-$(date +%F).txt.
System-Level cron: /etc/crontab
Alongside per-user crontabs, there is a single shared schedule file for the whole system:
# /etc/crontab: system-wide crontab
# Unlike any other crontab you don't have to run the `crontab'
# command to install the new version when you edit this file
# and files in /etc/cron.d.
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
17 * * * * root cd / && run-parts --report /etc/cron.hourly
25 6 * * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
The key difference is visible immediately — there is a sixth column. In a user crontab, the command follows the time fields directly; in /etc/crontab, the time fields are followed by which user the command runs as (root in the example above), and only then the command itself.
| Property | User crontab (crontab -e) |
/etc/crontab |
|---|---|---|
| Who edits it | Each user, their own | Root only |
| Number of columns | 5 time fields + command | 5 time fields + user + command |
| Runs as which user | Always the owner | Whichever user the line names |
| How it's installed | crontab -e reloads it automatically |
Saving the file is enough, no separate command |
| Typical use | Personal or project-specific jobs | System-level jobs, often installed by packages |
/etc/cron.d/: Separate Files for Packages
Rather than editing /etc/crontab directly, a separate file is usually added to /etc/cron.d/ — this lets a package install or upgrade its own schedule without touching a shared file:
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
30 3 * * * root /usr/local/bin/backup-check.sh
Files in /etc/cron.d/ use the same six-column format as /etc/crontab, but they don't go through crontab -e — creating or editing the file directly is enough, since the cron daemon watches this directory continuously.
/etc/cron.daily/ and Similar Directories
Rather than writing an exact time, many system scripts (logrotate, and the package manager's automatic cleanup tasks, for example) are placed into ready-made directories instead:
Every file in these directories is an executable script. The mechanism that actually runs them at the right time — visible in the /etc/crontab example above — is run-parts:
run-parts --test shows which scripts would be run, without actually running them. --report (seen in the /etc/crontab example above) makes it write a short report about each script's result.
These directories are convenient for jobs that only need an approximate time ("once a day, in the morning") rather than an exact minute — dropping a script into /etc/cron.daily/ is simpler than writing a full line in /etc/cron.d/ when precision doesn't matter.
The silent filename trap: no dots, no extension
Both /etc/cron.d/ (read directly by cron) and the run-parts directories (/etc/cron.daily/ and its siblings) ignore any filename containing a . — and more generally any name with characters outside [A-Za-z0-9_-]. A file dropped in as backup-check.sh or backup.check is skipped with no error logged anywhere; the identical file renamed backup-check runs. This is the single most common "my /etc/cron.d/ job just doesn't run and nothing appears in the log" cause — and it is exactly why the drop-in file created above is named backup-check, not backup-check.sh. Scripts placed in the run-parts directories additionally must be executable (chmod +x) and owned by root; a script that is present but not executable is likewise skipped in silence.
anacron
On many modern systems these directories are managed by anacron, which is designed for machines that aren't always powered on (a laptop, for instance) and runs a missed job the next time the system starts up. On a server that is always running, this distinction rarely matters in practice.
Who Is Allowed to Use cron: cron.allow and cron.deny
On a multi-user server, it isn't always desirable for every account to be able to schedule its own jobs. This is controlled by two optional files:
The logic works like this:
- if
/etc/cron.allowexists — only the users listed in it can usecrontab, everyone else is refused; - if
/etc/cron.allowdoesn't exist but/etc/cron.denydoes — everyone except the users listed can use it; - if neither exists (the default on Ubuntu) — every user can use cron.
This is a simple way to keep temporary or guest accounts, for example, from adding automated jobs of their own.
Practical Scenario: A Scheduled Task Under a Service Account
Problem. The application directory owned by www-data needs a cleanup job every morning at 4:00, but www-data can't log in interactively, so crontab -e can't be run as that user directly.
Solution — edit another user's crontab with sudo -u-style access:
Verify:
Conclusion. The job works correctly against files owned by www-data because it runs as that exact user — had the command instead been placed in a personal crontab, find ... -delete might well have lacked permission to touch www-data's files.
Common Mistakes
- Confusing
sudo crontab -ewithcrontab -e.sudo crontab -eedits root's crontab, not the current user's. To edit another user's crontab, name it explicitly with-u(sudo crontab -u <username> -e). - Editing
/var/spool/cron/crontabs/directly. That file is managed by thecrontabcommand. Opening it in a text editor directly — even with correct syntax — can leave the cron daemon unaware that the file changed, since the daemon typically expects changes to arrive through thecrontabcommand. - Adding a line to
/etc/crontabwithout the user column. If a line in/etc/crontabor/etc/cron.d/only has the five time fields, as in a user crontab, cron interprets the sixth column — expected to be a username — as part of the command instead, and the job doesn't run as intended.
Exercises
- Using
crontab -e, add a job under your own user account that runsdate >> ~/cron-test.logevery day at 6:00. Verify it withcrontab -l, then remove it withcrontab -e. - List the scripts currently present in
/etc/cron.daily/and compare that list against the output ofrun-parts --test /etc/cron.daily. - Check whether
cron.allowandcron.denyexist on your system, and explain in writing what the default behavior is when neither is present. - For a hypothetical
deployservice account, write the command that would open its crontab for editing (without actually running it).
Summary
crontab exists at three levels: a personal per-user crontab (crontab -e), system-level schedules in /etc/crontab and /etc/cron.d/, and ready-made directories like /etc/cron.daily/. Each differs in who edits it, which user it runs as, and what it's typically used for. If a job "isn't in cron," the first thing to check is the simplest one — whose crontab it was actually added to. Next, cron Environment and Logs covers the opposite problem: the job is registered correctly but doesn't behave as expected, because cron's environment differs from an interactive terminal's.