Cron Jobs on VPS: Scheduling, Logging, Debugging, and Best Practices for Reliability

Cron Jobs on VPS: Scheduling, Logging, Debugging, and Best Practices for Reliability

Cron is the standard Linux task scheduler — it runs commands at specified times without human intervention. Cron jobs that silently fail are one of the most common VPS administration headaches: the task appears configured but either runs incorrectly or doesn’t run at all. This guide covers setup, debugging, logging, and reliability best practices.

Crontab Syntax Reference

# ┌──────────── minute (0–59)
# │ ┌────────── hour (0–23)
# │ │ ┌──────── day of month (1–31)
# │ │ │ ┌────── month (1–12)
# │ │ │ │ ┌──── day of week (0–6, Sunday=0)
# * * * * * command_to_run

# Common examples:
0 3 * * *     # Every day at 3:00 AM
*/15 * * * *  # Every 15 minutes
0 */6 * * *   # Every 6 hours
0 9 * * 1-5  # Weekdays at 9:00 AM
0 0 1 * *    # First day of every month at midnight
@reboot       # Once on system startup

Managing Crontabs

crontab -e                    # Edit current user's crontab
crontab -l                    # List current user's crontab
crontab -r                    # Remove all crontab entries (use carefully!)
sudo crontab -u www-data -e   # Edit another user's crontab
ls /etc/cron.d/               # Per-application system cron files
ls /etc/cron.daily/           # Scripts run daily by the system

The #1 Cron Failure: PATH Differences

Cron runs with a minimal environment and does not source your ~/.bashrc, ~/.profile, or shell startup files. Programs in your PATH when using SSH may not be in cron’s PATH.

# BAD: relies on user's PATH — will silently fail in cron
0 3 * * * node /var/www/myapp/scripts/backup.js

# GOOD: use absolute paths
0 3 * * * /usr/local/bin/node /var/www/myapp/scripts/backup.js

# Find the full path to any command:
which node      # → /usr/local/bin/node
which python3   # → /usr/bin/python3
which wp        # → /usr/local/bin/wp

# Set PATH explicitly at the top of crontab
PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
HOME=/root

Capture All Output to Log Files

# Append stdout and stderr to log file (keeps history)
0 3 * * * /usr/local/bin/node /var/www/myapp/backup.js >> /var/log/backup.log 2>&1

# Include timestamp in each log entry
0 3 * * * echo "[$(date '+%Y-%m-%d %H:%M:%S')] Starting backup" >> /var/log/backup.log \
          && /usr/local/bin/node /var/www/myapp/backup.js >> /var/log/backup.log 2>&1

# Suppress all output (silent mode)
0 3 * * * /usr/local/bin/node /var/www/myapp/backup.js > /dev/null 2>&1

Debugging Failing Cron Jobs

# Step 1: Check cron daemon is running
sudo systemctl status cron

# Step 2: Check cron logs
sudo grep CRON /var/log/syslog | tail -50
sudo journalctl -u cron --since "1 hour ago"

# Step 3: Verify the job actually ran
sudo grep "backup" /var/log/syslog

# Step 4: Simulate cron's minimal environment (key debugging step!)
env -i HOME=/home/deploy USER=deploy PATH=/usr/local/bin:/usr/bin:/bin \
  /bin/bash -c "your_command_here"
# If this fails but your normal shell works → it's a PATH issue

Wrapper Scripts for Reliability

sudo nano /usr/local/bin/backup-wrapper.sh
#!/bin/bash
set -euo pipefail   # Exit on any error, undefined variable, or pipe failure

LOGFILE="/var/log/backup.log"
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')

echo "[$TIMESTAMP] Backup started" >> "$LOGFILE"
export PATH="/usr/local/bin:/usr/bin:/bin"

if /usr/local/bin/node /var/www/myapp/backup.js >> "$LOGFILE" 2>&1; then
    echo "[$TIMESTAMP] Backup completed successfully" >> "$LOGFILE"
else
    EXIT_CODE=$?
    echo "[$TIMESTAMP] ERROR: Backup failed (exit code $EXIT_CODE)" >> "$LOGFILE"
    exit 1
fi
chmod +x /usr/local/bin/backup-wrapper.sh

# Clean, readable crontab entry:
0 3 * * * /usr/local/bin/backup-wrapper.sh

Prevent Overlapping Runs with flock

If a cron job takes longer than its interval, multiple instances run simultaneously and conflict. flock prevents this:

# flock exits immediately if lock is already held (-n = non-blocking)
*/15 * * * * flock -n /tmp/mybackup.lock /usr/local/bin/backup-wrapper.sh

Log Rotation for Cron Log Files

sudo nano /etc/logrotate.d/cron-logs
/var/log/backup.log {
    weekly
    rotate 4
    compress
    missingok
    notifempty
}

systemd Timers: The Modern Alternative

systemd timers have dependency management, can catch up on missed runs after downtime, and log to journald:

# /etc/systemd/system/mybackup.service
[Unit]
Description=My Application Backup
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup-wrapper.sh
User=deploy
# /etc/systemd/system/mybackup.timer
[Unit]
Description=Run mybackup daily at 3 AM

[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true          # Runs if missed due to downtime
AccuracySec=1min

[Install]
WantedBy=timers.target
sudo systemctl enable --now mybackup.timer
sudo systemctl list-timers   # View all active timers

Monitor with Healthchecks.io

Healthchecks.io alerts you when a cron job doesn’t run — the most dangerous silent failure mode. Free for up to 20 checks:

0 3 * * * curl -fsS https://hc-ping.com/YOUR-UUID/start > /dev/null; \
          /usr/local/bin/backup-wrapper.sh \
          && curl -fsS https://hc-ping.com/YOUR-UUID > /dev/null \
          || curl -fsS https://hc-ping.com/YOUR-UUID/fail > /dev/null

Getting Started

Cron is available on all Ubuntu VPS plans at VPS.DO and requires no additional installation. The most impactful reliability improvements: always use absolute paths, redirect output to log files, and monitor with Healthchecks.io. For complex scheduling with dependencies or catch-up behavior, systemd timers are the better choice.

Conclusion

Reliable cron jobs require four things: absolute paths (not relying on user PATH), output redirection to logs (no silent failures), flock for overlap prevention, and external monitoring for missed schedule detection. With these in place, scheduled tasks run predictably and any failure is immediately visible — rather than going undetected until you notice the backup hasn’t run in three weeks.

Fast • Reliable • Affordable VPS - DO It Now!

Get top VPS hosting with VPS.DO’s fast, low-cost plans. Try risk-free with our 7-day no-questions-asked refund and start today!