Prices dated and sourced
CNCodeNexon

Hosting, WordPress and Software Guides

Cron Jobs Explained: Schedule Tasks on Linux and WordPress

By

Founder and Tech Writer, CodeNexon

Updated • 9 min read

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
  1. The crontab line, field by field
  2. Common schedules
  3. Setting up a cron job on a VPS
  4. The environment problem, and how to avoid it
  5. Capturing output and errors
  6. Time zones
  7. Preventing overlapping runs
  8. Cron on shared hosting
  9. WordPress and WP-Cron
  10. Real examples for a small website
  11. How to know a job actually ran
  12. Troubleshooting
  13. A quick checklist for every new job
  14. Frequently asked questions
  15. 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.sh

That 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

CharacterMeaningExampleReads as
*Every value* * * * *Every minute
,A list0 9,17 * * *At 9:00 and 17:00
-A range0 9 * * 1-59:00 on weekdays
/A step*/15 * * * *Every 15 minutes

Common schedules

ScheduleExpression
Every 5 minutes*/5 * * * *
Every 15 minutes*/15 * * * *
Every hour, on the hour0 * * * *
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 midnight0 0 * * 0
First day of every month at 6:00 a.m.0 6 1 * *
Every 6 hours0 */6 * * *
Twice a day, 9 a.m. and 9 p.m.0 9,21 * * *
January 1 at midnight0 0 1 1 *

Most cron versions on Linux also accept shortcuts:

ShortcutSame as
@hourly0 * * * *
@daily0 0 * * *
@weekly0 0 * * 0
@monthly0 0 1 * *
@rebootOnce, 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 -e

The 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 -l

Jobs 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.sh

The 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:

  • PATH is short, typically just /usr/bin:/bin. Commands installed elsewhere, such as /usr/local/bin/wp for 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.com

Find 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.com

3. Change directory in the command when a script relies on relative paths:

0 3 * * * cd /var/www/example.com && /usr/bin/php artisan schedule:run

Percent 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).sql

Better 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>&1 sends 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>&1

Only 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:

timedatectl

Either 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.sh

The -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>&1

On 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>&1

Our 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 -delete

Make 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>&1

Add 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 --quiet

How 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 -delete

How to know a job actually ran

A silent cron failure is one of the most common ways backups stop without anyone noticing.

  1. Check the log file you are writing to, and confirm entries appear on schedule.
  2. Check the system log for cron activity:
grep CRON /var/log/syslog | tail -20

On systems without that file, use journalctl -u cron --since today.

  1. 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/null

The && means the ping only happens if the backup succeeded, so a failed backup triggers the alert.

Troubleshooting

SymptomLikely causeFix
Works in terminal, fails in cronShort PATH or missing environmentUse full paths, or set PATH in the crontab
Runs at the wrong hourServer time zone is UTCConvert the time or change the server's time zone
Command cut off or strange outputUnescaped %Escape as \%, or move the command into a script
Nothing happens at allScript not executable, or a typo in the schedulechmod +x the script, and check the line with a cron expression checker
Two copies running at onceRun takes longer than the intervalWrap the command in flock -n
No error output anywhereOutput sent to mail that is not configuredRedirect to a log file with >> file 2>&1
Scheduled WordPress posts appear lateWP-Cron relies on visitsDisable WP-Cron and trigger it with real cron

A quick checklist for every new job

  1. Run the command by hand first, from your home folder, to confirm it works.
  2. Use full paths for the command and every file it touches.
  3. Write output to a log file with >> file 2>&1.
  4. Check the server's time zone before choosing the hour.
  5. Wrap frequent jobs in flock -n so runs never overlap.
  6. 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.

About the author

Shubham Handore founded CodeNexon in 2024 and writes its guides on hosting, WordPress and the software small teams use to run a business online.

View author profile
CronLinuxWordPressAutomation