Skip to main content

Cron on Linux: Setting Up the Task Scheduler on VPS

VDS / VPS · 19.04.2026 · 6 min read
Illustration for “Cron on Linux: Setting Up the Task Scheduler on VPS”

Cron is the task scheduling daemon on Unix systems that runs commands on a schedule. It is used for backups, sending reports, renewing SSL certificates and any other automation.

Cron entry syntax

# ┌───────────── minutes (0-59)
# │ ┌─────────── hours (0-23)
# │ │ ┌───────── day of month (1-31)
# │ │ │ ┌─────── month (1-12)
# │ │ │ │ ┌───── day of week (0=Sun, 6=Sat)
# │ │ │ │ │
# * * * * * command

Schedule examples

ExpressionWhen it runs
* * * * *Every minute
0 * * * *Every hour (at minute 00)
0 3 * * *Every day at 03:00
0 3 * * 1Every Monday at 03:00
0 0 1 * *The first day of every month
*/15 * * * *Every 15 minutes
0 2,14 * * *At 02:00 and 14:00
@rebootOn every reboot
@dailyOnce a day (= 0 0 * * *)

Managing crontab

># Edit the crontab of the current user
crontab -e

# View the current crontab
crontab -l

# Remove the crontab
crontab -r

# Crontab for another user (root)
sudo crontab -u www-data -e

Practical examples

># Database backup every 6 hours
0 */6 * * * mysqldump -u root -pPASSWORD mydb > /backup/db_$(date +%Y%m%d_%H%M).sql

# Renewing Let's Encrypt certificates
0 3 * * * certbot renew --quiet

# Cleaning up logs older than 30 days
0 4 * * * find /var/log/myapp -name "*.log" -mtime +30 -delete

# Running a Python script and logging its output
*/30 * * * * /usr/bin/python3 /opt/scripts/sync.py >> /var/log/sync.log 2>&1

# Restarting a service at 05:00 every Sunday
0 5 * * 0 systemctl restart myapp

Environment variables in cron

># Cron has a minimal environment — $PATH may not contain the paths you need
# Always use full paths to commands
/usr/bin/php, /usr/local/bin/wp etc.

# Or set PATH explicitly at the top of the crontab:
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=admin@example.com  # send errors to email
💡 Debugging cron: Add 2>&1 >> /tmp/cron_debug.log to the command to capture all output. If cron stays silent about an obvious error, the problem is most likely PATH or file permissions.

Crontab syntax

# Format: MIN HOUR DOM MON DOW COMMAND
# MIN    = 0-59
# HOUR   = 0-23
# DOM    = 1-31 (day of month)
# MON    = 1-12
# DOW    = 0-7  (day of week, 0 and 7 = Sunday)

# Every 5 minutes
*/5 * * * * /usr/local/bin/check.sh

# Every day at 3:30
30 3 * * * /usr/local/bin/backup.sh

# Every Monday at 2:00
0 2 * * 1 /usr/local/bin/weekly-report.sh

# First day of every month at midnight
0 0 1 * * /usr/local/bin/monthly-cleanup.sh

Logging cron jobs

# Redirect output to a log
0 3 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

# Suppress output (no logs)
0 3 * * * /usr/local/bin/backup.sh > /dev/null 2>&1

Special values

ValueEquivalent
@rebootAt system startup
@daily0 0 * * *
@weekly0 0 * * 0
@monthly0 0 1 * *
💡 For critical tasks use systemd timers — they have a built-in retry mechanism and journald integration for logs.

Common cron job mistakes

Most "cron isn't working" complaints are actually cron working exactly as configured — just with an environment the script's author didn't expect.

/bin/sh: 1: wp: not found

Cron runs commands with a minimal PATH that often doesn't include the directory where the needed utility lives (like /usr/local/bin for WP-CLI). Use the full path to the binary — find it with which wp in a normal shell and put that result in the crontab.

You (deploy) are not allowed to use this program (crontab)

The user isn't listed in /etc/cron.allow, while that file exists — when cron.allow is present, only the users listed in it can use crontab at all, regardless of sudo rights. Add the username to the file, or use sudo crontab -u username -e as an administrator.

flock: 456: Resource temporarily unavailable

This isn't a failure, it's protection against overlapping runs: the previous instance of the job hasn't finished yet and holds the lock file, so the new run exits immediately. If this happens regularly, the problem is that the job takes longer than the schedule interval, not the lock itself — figure out why the previous run doesn't finish in time, don't remove the flock.

cron vs systemd timers compared

Parametercronsystemd timer
LoggingManual, to a fileBuilt into journald
Retry on failureNoYes (Restart=, OnFailure=)
Schedule precisionTo the minuteTo the second
Dependencies on other servicesNoYes (After=, Requires=)

For simple periodic tasks, cron stays simpler to set up. For tasks with dependencies on other services, or that need automatic retry on failure, see creating a systemd unit file.

When cron doesn't fit

Cron's minimum interval is one minute; tasks that need to run every few seconds require a dedicated daemon or a systemd timer with OnUnitActiveSec=. Cron itself doesn't protect against running the same job in parallel — if the previous instance is still running when a new one starts on schedule, they'll run at the same time, competing for the same resources, unless you explicitly wrap the command in flock as shown above. For tasks where delivery of the result is critical (say, a report that must go out), cron gives you no built-in retry mechanism — on failure, the job simply doesn't run again until the next scheduled time.

How to verify the result

A job appearing in the schedule doesn't mean it actually ran — check that it really executed separately:

# Confirm cron actually ran the job
grep CRON /var/log/syslog | grep "backup.sh" | tail -5

# Check the exit code of the last run
echo $?

# On systems with journald
journalctl -u cron --since "1 hour ago" | grep backup

A log entry existing only confirms that cron launched the command — not that it finished successfully. Explicitly log the return code inside the script itself, or add MAILTO to the crontab to get an email on a non-zero exit code.

Was this article helpful?
← Back to Knowledge Base Ask Support