Cron Jobs Explained: Schedule Tasks on Linux and WordPress
Founder and Tech Writer, CodeNexon
Key Takeaways
- ▪A crontab line has five time fields, minute, hour, day, month and weekday, then the command.
- ▪Cron runs with a minimal PATH. Use full paths for every command.
- ▪Cron uses the server's time zone, often UTC on cloud servers.
- ▪Replace WP-Cron with a real cron job so scheduled posts publish on time.
In this guide
- The crontab line, field by field
- Common schedules
- Setting up a cron job on a VPS
- The environment problem, and how to avoid it
- Capturing output and errors
- Time zones
- Preventing overlapping runs
- Cron on shared hosting
- WordPress and WP-Cron
- Real examples for a small website
- How to know a job actually ran
- Troubleshooting
- A quick checklist for every new job
- Frequently asked questions
- Sources
A cron job is a command your server runs automatically on a schedule, such as a backup every night at 3 a.m. or a cleanup script every Monday. You define it with one line in a file called the crontab: five time fields followed by the command, for example 0 3 * * * /usr/local/bin/backup.sh. Cron is built into Linux and is available on most shared hosting through the control panel. The two things people most often get wrong are the time zone and the fact that cron runs with a minimal environment, so commands that work in your terminal can fail silently in cron.
This guide explains the schedule syntax with real examples, how to set up jobs on a VPS and on shared hosting, how WordPress handles scheduled tasks, and how to make sure a job is actually running.
The crontab line, field by field
Every cron job is one line with six parts:
┌───────── minute (0 to 59)
│ ┌─────── hour (0 to 23)
│ │ ┌───── day of month (1 to 31)
│ │ │ ┌─── month (1 to 12)
│ │ │ │ ┌─ day of week (0 to 7, where 0 and 7 are both Sunday)
│ │ │ │ │
0 3 * * * /usr/local/bin/backup.shThat line runs /usr/local/bin/backup.sh at minute 0 of hour 3, every day of every month, whatever the weekday. In other words, at 3:00 a.m. daily.
Special characters
| Character | Meaning | Example | Reads as |
|---|---|---|---|
* | Every value | * * * * * | Every minute |
, | A list | 0 9,17 * * * | At 9:00 and 17:00 |
- | A range | 0 9 * * 1-5 | 9:00 on weekdays |
/ | A step | */15 * * * * | Every 15 minutes |
Common schedules
| Schedule | Expression |
|---|---|
| Every 5 minutes | */5 * * * * |
| Every 15 minutes | */15 * * * * |
| Every hour, on the hour | 0 * * * * |
| Every day at 3:00 a.m. | 0 3 * * * |
| Every day at 2:30 p.m. | 30 14 * * * |
| Weekdays at 8:00 a.m. | 0 8 * * 1-5 |
| Every Sunday at midnight | 0 0 * * 0 |
| First day of every month at 6:00 a.m. | 0 6 1 * * |
| Every 6 hours | 0 */6 * * * |
| Twice a day, 9 a.m. and 9 p.m. | 0 9,21 * * * |
| January 1 at midnight | 0 0 1 1 * |
Most cron versions on Linux also accept shortcuts:
| Shortcut | Same as |
|---|---|
@hourly | 0 * * * * |
@daily | 0 0 * * * |
@weekly | 0 0 * * 0 |
@monthly | 0 0 1 * * |
@reboot | Once, when the server starts |
A trap: day of month and day of week together
If you set both the day-of-month and day-of-week fields, cron runs the job when either matches, not both. 0 9 13 * 5 runs at 9:00 on the 13th of every month and every Friday, not only on Friday the 13th. Keep one of the two fields as * unless you want that behavior.
Setting up a cron job on a VPS
Edit your user's crontab
crontab -eThe first time, it asks which editor to use. Choose nano if you are unsure. Add your line at the bottom, save and exit. Cron reads the change immediately. There is nothing to restart.
List your current jobs:
crontab -lJobs in your crontab run as your user. For tasks that need administrator rights, edit root's crontab with sudo crontab -e, but only when the task genuinely needs it.
System-wide jobs
Files in /etc/cron.d/ hold system jobs, and they include an extra field naming the user to run as:
0 3 * * * deploy /usr/local/bin/backup.shThe folders /etc/cron.daily/, /etc/cron.weekly/ and /etc/cron.monthly/ run any executable script placed in them at a set time. That is how system packages schedule log rotation and updates.
The environment problem, and how to avoid it
This is the most common reason a job works in your terminal but fails in cron. Cron runs commands with a minimal environment:
PATHis short, typically just/usr/bin:/bin. Commands installed elsewhere, such as/usr/local/bin/wpfor WP-CLI or a Node.js binary, are not found.- Your shell profile is not loaded, so aliases and variables you set there do not exist.
- The working directory is your home folder, not the folder your script expects.
Three habits fix almost every case.
1. Use full paths for everything.
0 3 * * * /usr/local/bin/wp db export /home/deploy/backups/db.sql --path=/var/www/example.comFind a command's full path with which wp.
2. Set PATH at the top of the crontab if you prefer short commands:
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
0 3 * * * wp db export /home/deploy/backups/db.sql --path=/var/www/example.com3. Change directory in the command when a script relies on relative paths:
0 3 * * * cd /var/www/example.com && /usr/bin/php artisan schedule:runPercent signs need escaping
In a crontab line, % means "new line" unless escaped. A date stamp such as $(date +%F) must be written $(date +\%F):
0 3 * * * /usr/bin/mysqldump mydb > /home/deploy/backups/db-$(date +\%F).sqlBetter still, put the command in a script file. Scripts do not have this problem and are easier to test.
Capturing output and errors
By default, cron tries to email any output to the user. On most servers, local mail is not set up, so the output vanishes and you never see the error.
Send output to a log file instead:
0 3 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1>>appends normal output to the log.2>&1sends error output to the same place.
For jobs that run every few minutes and produce nothing useful, discard the output:
*/5 * * * * /usr/local/bin/check.sh > /dev/null 2>&1Only do that once you know the job works. Discarding errors from a new job hides exactly the information you need.
Time zones
Cron uses the server's system time zone. Many cloud servers are set to UTC, so 0 3 * * * runs at 3:00 UTC: 11 p.m. the previous evening in New York during daylight saving time.
Check the server's time zone:
timedatectlEither schedule in UTC and convert in your head, or change the server's time zone. Using UTC on servers avoids surprises around daylight saving changes, when local clocks skip or repeat an hour. A job scheduled for 2:30 a.m. local time can be skipped or run twice on those nights. How to Set Up a VPS covers setting the time zone.
Preventing overlapping runs
If a job runs every 5 minutes and one run takes 7 minutes, the next one starts while the first is still going. Two copies of a backup or import can corrupt files or overload the server.
Use flock to allow only one copy at a time:
*/5 * * * * /usr/bin/flock -n /tmp/import.lock /usr/local/bin/import.shThe -n flag makes the new run skip immediately if the previous one still holds the lock.
Cron on shared hosting
Most shared hosts let you add cron jobs without SSH.
- cPanel: open Cron Jobs, choose a common setting or type the five fields, and enter the command. cPanel also lets you set an email address for output.
- Other control panels: look for "Scheduled tasks" or "Cron jobs" under the advanced or developer section.
Shared hosts often limit how frequently jobs may run, commonly no more than every 5 or 15 minutes, and may stop jobs that use too much CPU. Check your plan's terms if a frequent job is important. How to Choose a Web Hosting Provider lists the limits worth asking about.
WordPress and WP-Cron
WordPress has its own scheduler, WP-Cron, which runs scheduled posts, plugin tasks, update checks and some backups. It is not a real cron. It runs when someone visits the site: each page load checks whether any task is due.
That causes two problems:
- On quiet sites, tasks run late or bunch together, because nobody visits to trigger them. A post scheduled for 9:00 can appear at 9:47.
- On busy sites, the check runs on many requests, adding load.
The fix is to turn off the visit-based trigger and call WP-Cron from real cron instead. Add this to wp-config.php:
define( 'DISABLE_WP_CRON', true );Then add a real cron job that triggers it every 15 minutes. With WP-CLI on a VPS:
*/15 * * * * /usr/local/bin/wp cron event run --due-now --path=/var/www/example.com > /dev/null 2>&1On shared hosting without WP-CLI, request the cron URL instead:
*/15 * * * * wget -q -O - https://example.com/wp-cron.php?doing_wp_cron > /dev/null 2>&1Our guide to why WordPress sites slow down covers WP-Cron alongside the other common causes.
Real examples for a small website
Nightly database backup with 14 days kept
Create /usr/local/bin/backup.sh:
#!/bin/bash
set -e
DEST=/home/deploy/backups
STAMP=$(date +%F)
mkdir -p "$DEST"
/usr/local/bin/wp db export "$DEST/db-$STAMP.sql" --path=/var/www/example.com
find "$DEST" -name 'db-*.sql' -mtime +14 -deleteMake it executable with chmod +x /usr/local/bin/backup.sh, then schedule it:
0 3 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1Add a step to copy the file off the server too. How to Back Up a WordPress Site explains why a backup on the same server is not enough, and Amazon S3 vs Cloudflare R2 vs Backblaze B2 compares where to keep it.
Renewing SSL certificates
Certbot installed through snap sets up its own timer, so you do not need cron for it. If you installed it another way, a twice-daily check is standard:
0 */12 * * * /usr/bin/certbot renew --quietHow to Get a Free SSL Certificate With Let's Encrypt covers renewal in detail.
Clearing old files
Delete temporary uploads older than 7 days, every night at 4:15:
15 4 * * * /usr/bin/find /var/www/example.com/tmp -type f -mtime +7 -deleteHow to know a job actually ran
A silent cron failure is one of the most common ways backups stop without anyone noticing.
- Check the log file you are writing to, and confirm entries appear on schedule.
- Check the system log for cron activity:
grep CRON /var/log/syslog | tail -20On systems without that file, use journalctl -u cron --since today.
- Use a heartbeat monitor. Add a request to a monitoring service at the end of the script. If the request does not arrive on time, the service alerts you. Website Uptime Monitoring explains heartbeat checks.
0 3 * * * /usr/local/bin/backup.sh && curl -fsS https://hc.example.com/ping/abc123 > /dev/nullThe && means the ping only happens if the backup succeeded, so a failed backup triggers the alert.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Works in terminal, fails in cron | Short PATH or missing environment | Use full paths, or set PATH in the crontab |
| Runs at the wrong hour | Server time zone is UTC | Convert the time or change the server's time zone |
| Command cut off or strange output | Unescaped % | Escape as \%, or move the command into a script |
| Nothing happens at all | Script not executable, or a typo in the schedule | chmod +x the script, and check the line with a cron expression checker |
| Two copies running at once | Run takes longer than the interval | Wrap the command in flock -n |
| No error output anywhere | Output sent to mail that is not configured | Redirect to a log file with >> file 2>&1 |
| Scheduled WordPress posts appear late | WP-Cron relies on visits | Disable WP-Cron and trigger it with real cron |
A quick checklist for every new job
- Run the command by hand first, from your home folder, to confirm it works.
- Use full paths for the command and every file it touches.
- Write output to a log file with
>> file 2>&1. - Check the server's time zone before choosing the hour.
- Wrap frequent jobs in
flock -nso runs never overlap. - Add a heartbeat ping for jobs that matter, such as backups.
Frequently asked questions
What is a cron job?
A cron job is a command that a Linux or Unix server runs automatically on a schedule you define, such as every 15 minutes or every night at 3 a.m. Each job is one line in a crontab file: five time fields followed by the command to run.
What does * * * * * mean in cron?
Five asterisks mean every minute of every hour of every day. The fields are minute, hour, day of month, month and day of week, and an asterisk means every value. So * * * * * runs the command once a minute.
How do I run a cron job every 5 minutes?
Use */5 * * * * followed by the command. The */5 in the minute field means every fifth minute. Similarly, */15 runs every 15 minutes and 0 */6 * * * runs every 6 hours.
Why does my cron job work manually but not in cron?
Cron runs with a minimal environment: a short PATH, no shell profile and your home folder as the working directory. Use full paths for every command and file, set PATH at the top of the crontab, or cd into the right folder first.
What time zone does cron use?
Cron uses the server's system time zone. Many cloud servers default to UTC, so a job set for 0 3 * * * runs at 3:00 UTC. Check the time zone with timedatectl and convert your schedule, or change the server's time zone.
Should I disable WP-Cron in WordPress?
On most sites, yes, as long as you replace it with a real cron job. WP-Cron runs only when someone visits, so tasks run late on quiet sites and add load on busy ones. Disable it in wp-config.php and trigger it from cron every 15 minutes.
How can I tell if a cron job ran?
Write the job's output to a log file and check it, search the system log for CRON entries, or add a ping to a heartbeat monitoring service at the end of the job so you are alerted when it does not run.
Sources
Update history
- First published.