Many developers accidentally use this problematic syntax when attempting to run a job every 4 hours:
* */4 * * * /cmd.sh
This actually makes the job run every minute of every 4th hour (hours divisible by 4), rather than running once every 4 hours as intended.
Here are the proper ways to schedule a cron job every 4 hours:
0 */4 * * * /path/to/command
or more explicitly:
0 0,4,8,12,16,20 * * * /path/to/command
The asterisk (*) in the minute field means "every minute" while */4 in the hour field means "every 4 hours". The combination executes the job every minute during the specified hours.
Always check these aspects when troubleshooting:
- Cron service running status:
sudo service cron status
- User permissions for the cron job
- File permissions for the script being executed
- Cron logs:
grep CRON /var/log/syslog
Here's a full example with error handling:
# Run backup script every 4 hours
0 */4 * * * /usr/bin/flock -n /tmp/backup.lock /bin/bash /scripts/backup.sh >> /var/log/backup.log 2>&1
The flock
prevents overlapping executions if a previous run hasn't finished.
When troubleshooting, consider these environmental factors:
# Specify full paths and environment
0 */4 * * * . /home/user/.profile; /usr/bin/python3 /scripts/process_data.py
Many Linux administrators encounter this exact issue when trying to schedule jobs at 4-hour intervals. The incorrect syntax:
* */4 * * * /cmd.sh
This actually runs your command every minute during active hours, not every 4 hours as intended. The asterisk in the minutes field is the culprit.
The correct format should specify both minute and hour fields precisely:
0 */4 * * * /path/to/your/command
This will execute at minute 0 of every 4th hour (e.g., 00:00, 04:00, 08:00, etc.).
For more complex scheduling needs, consider these variations:
# Every 4 hours offset by 30 minutes
30 */4 * * * /path/to/command
# Specific hours listing (more readable)
0 0,4,8,12,16,20 * * * /path/to/command
To test your crontab without waiting:
# Check cron logs (location varies by distro)
grep CRON /var/log/syslog
# or
journalctl -u cron.service
# Verify environment variables are set
* * * * * env > /tmp/cronenv
- Always use absolute paths in scripts
- Redirect output to log files for debugging
- Set proper permissions on scripts (755 recommended)
- Consider using systemd timers for complex schedules
Here's a complete working example for database backups:
0 */4 * * * /usr/bin/mysqldump -u root -pPASSWORD dbname > /backups/db-$(date +\%Y\%m\%d-\%H\%M).sql 2>&1
Note the escaped percent signs and output redirection.