Tools: Cron Jobs Explained: Scheduling Automated Tasks on Linux

Tools: Cron Jobs Explained: Scheduling Automated Tasks on Linux

What You'll Need

Table of Contents

Understanding Cron: The Basics

Crontab Syntax Breakdown

Creating and Managing Your First Cron Job

Real-World Examples and Use Cases

Debugging and Monitoring Cron Jobs I'll be honest—when I first encountered cron jobs, I thought they were some obscure DevOps sorcery. They're actually one of the simplest yet most powerful tools in Linux. A cron job is just a scheduled task that runs automatically at specific times or intervals without you touching a keyboard. It's been the backbone of server automation since 1975, and it still works beautifully today. Think of cron as your personal assistant that never sleeps. You give it instructions once, and it executes them endlessly—every minute, every hour, every month, or whenever you specify. Whether you're backing up databases, clearing log files, sending status reports, or pulling crypto prices for alerts, cron handles it silently in the background. The beauty of cron is its simplicity. Unlike more complex automation platforms, it requires no UI, no cloud connection, and no monthly subscription. If you've got a Linux server—whether it's a Hetzner VPS or a DigitalOcean droplet—you've already got cron built in. However, if you're managing dozens of workflows across multiple systems, you might eventually want to explore solutions like n8n Cloud, which centralizes your automation logic. But let's not get ahead of ourselves. Cron is perfect for many use cases, and understanding it is fundamental. The magic happens in a file called a crontab (cron table). Each line in your crontab represents one scheduled task, and each line follows this exact format: Let me break this down with actual examples so you see how it works: 0 2 * * * /home/user/backup.sh This runs /home/user/backup.sh at 2:00 AM every single day. The first 0 means minute 0 (top of the hour), 2 is 2 AM, and the three asterisks mean "every day, every month, any day of week." */15 * * * * /usr/bin/python3 /home/user/check_status.py The */15 means "every 15 minutes." This Python script runs four times per hour, all day, every day. 0 0 1 * * /home/user/monthly_report.sh This runs on the first day of every month at midnight. Perfect for monthly tasks. 30 3 * * 1 /home/user/weekly_backup.sh This runs every Monday at 3:30 AM. The 1 represents Monday (0=Sunday, 1=Monday, etc.). Here's a quick reference table for common time expressions: These shortcuts make crontabs more readable. @hourly is much cleaner than 0 * * * *. Let's get practical. I'm going to walk you through creating a real cron job from scratch. Step 1: Open Your Crontab On your Linux server, open the crontab editor: This opens your personal crontab in your default editor (usually nano or vim). If it's your first time, you'll see a blank file with some commented instructions at the top. Step 2: Write Your Script First, let's create the script you want to run. I'll create a simple backup script. Open a new file: Save the file (Ctrl+O, Enter, Ctrl+X in nano), then make it executable: Step 3: Add the Cron Job Now open your crontab again: Add this line at the bottom: This runs your backup script every day at 3:00 AM. Step 4: Verify It's Scheduled List your cron jobs to confirm they were added: You should see your new job listed. That's it—you're done. The script will run automatically from now on. 💡 Fast-Track Your Project: Don't want to configure this yourself? I build custom n8n pipelines and bots. Message me with code SYS3-DEVTO. Let me show you some practical cron jobs I've actually used in production environments. These patterns solve real problems. Database Backups Every 6 Hours This backs up your MySQL database every 6 hours. The backticks escape the date command so it's evaluated at runtime. Clean Up Old Log Files Runs at 2 AM daily, deleting nginx logs older than 30 days. Essential for preventing disk space issues. Sync Files to Remote Server Syncs your data directory to a remote server at 4:30 AM every day. Great for distributed backups. Here's what check_uptime.sh might look like: This checks every 5 minutes and sends an email if the site is down. Generate Daily Reports Run a Python script at 8 AM to generate reports. Here's a sample script: Restart Services If Down Checks every 10 minutes and restarts nginx if it's not running. If you're automating data collection across multiple systems, you might eventually want to centralize your workflows. Check out our guide on How to Set Up a VPS for Automation (Hetzner vs Contabo vs Railway) to understand hosting options that work well with automation systems. Here's where most people struggle. A cron job runs silently, and when something breaks, you won't know unless you look at the logs. Check If Cron Daemon Is Running View Cron Logs (Ubuntu/Debian) This shows all cron activity. You'll see lines like: Capture Output and Errors Modify your crontab to save output: Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to ? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Code Block

Copy

┌───────────── minute (0 - 59) │ ┌───────────── hour (0 - 23) │ │ ┌───────────── day of month (1 - 31) │ │ │ ┌───────────── month (1 - 12) │ │ │ │ ┌───────────── day of week (0 - 7) (0 and 7 are Sunday) │ │ │ │ │ │ │ │ │ │ * * * * * command-to-execute ┌───────────── minute (0 - 59) │ ┌───────────── hour (0 - 23) │ │ ┌───────────── day of month (1 - 31) │ │ │ ┌───────────── month (1 - 12) │ │ │ │ ┌───────────── day of week (0 - 7) (0 and 7 are Sunday) │ │ │ │ │ │ │ │ │ │ * * * * * command-to-execute ┌───────────── minute (0 - 59) │ ┌───────────── hour (0 - 23) │ │ ┌───────────── day of month (1 - 31) │ │ │ ┌───────────── month (1 - 12) │ │ │ │ ┌───────────── day of week (0 - 7) (0 and 7 are Sunday) │ │ │ │ │ │ │ │ │ │ * * * * * command-to-execute 0 2 * * * /home/user/backup.sh /home/user/backup.sh */15 * * * * /usr/bin/python3 /home/user/check_status.py 0 0 1 * * /home/user/monthly_report.sh 30 3 * * 1 /home/user/weekly_backup.sh @yearly 0 0 1 1 * (once per year on January 1st) @monthly 0 0 1 * * (once per month on the 1st) @weekly 0 0 * * 0 (once per week on Sunday at midnight) @daily 0 0 * * * (once per day at midnight) @hourly 0 * * * * (once per hour) @reboot - (when the system boots up) @yearly 0 0 1 1 * (once per year on January 1st) @monthly 0 0 1 * * (once per month on the 1st) @weekly 0 0 * * 0 (once per week on Sunday at midnight) @daily 0 0 * * * (once per day at midnight) @hourly 0 * * * * (once per hour) @reboot - (when the system boots up) @yearly 0 0 1 1 * (once per year on January 1st) @monthly 0 0 1 * * (once per month on the 1st) @weekly 0 0 * * 0 (once per week on Sunday at midnight) @daily 0 0 * * * (once per day at midnight) @hourly 0 * * * * (once per hour) @reboot - (when the system boots up) crontab -e nano /home/user/backup_logs.sh nano /home/user/backup_logs.sh nano /home/user/backup_logs.sh #!/bin/bash BACKUP_DIR="/backups" SOURCE_DIR="/var/log" DATE=$(date +%Y%m%d_%H%M%S) mkdir -p $BACKUP_DIR tar -czf $BACKUP_DIR/logs_backup_$DATE.tar.gz $SOURCE_DIR find $BACKUP_DIR -name "logs_backup_*.tar.gz" -mtime +7 -delete echo "Backup completed at $(date)" >> /var/log/backup.log #!/bin/bash BACKUP_DIR="/backups" SOURCE_DIR="/var/log" DATE=$(date +%Y%m%d_%H%M%S) mkdir -p $BACKUP_DIR tar -czf $BACKUP_DIR/logs_backup_$DATE.tar.gz $SOURCE_DIR find $BACKUP_DIR -name "logs_backup_*.tar.gz" -mtime +7 -delete echo "Backup completed at $(date)" >> /var/log/backup.log #!/bin/bash BACKUP_DIR="/backups" SOURCE_DIR="/var/log" DATE=$(date +%Y%m%d_%H%M%S) mkdir -p $BACKUP_DIR tar -czf $BACKUP_DIR/logs_backup_$DATE.tar.gz $SOURCE_DIR find $BACKUP_DIR -name "logs_backup_*.tar.gz" -mtime +7 -delete echo "Backup completed at $(date)" >> /var/log/backup.log chmod +x /home/user/backup_logs.sh chmod +x /home/user/backup_logs.sh chmod +x /home/user/backup_logs.sh crontab -e 0 3 * * * /home/user/backup_logs.sh 0 3 * * * /home/user/backup_logs.sh 0 3 * * * /home/user/backup_logs.sh crontab -l 0 */6 * * * /usr/bin/mysqldump -u root -pYourPassword database_name > /backups/db_$(date +\%Y\%m\%d_\%H\%M\%S).sql 0 */6 * * * /usr/bin/mysqldump -u root -pYourPassword database_name > /backups/db_$(date +\%Y\%m\%d_\%H\%M\%S).sql 0 */6 * * * /usr/bin/mysqldump -u root -pYourPassword database_name > /backups/db_$(date +\%Y\%m\%d_\%H\%M\%S).sql 0 2 * * * find /var/log/nginx -name "*.log" -mtime +30 -delete 0 2 * * * find /var/log/nginx -name "*.log" -mtime +30 -delete 0 2 * * * find /var/log/nginx -name "*.log" -mtime +30 -delete 30 4 * * * rsync -avz --delete /home/user/data/ [email protected]:/backup/data/ 30 4 * * * rsync -avz --delete /home/user/data/ [email protected]:/backup/data/ 30 4 * * * rsync -avz --delete /home/user/data/ [email protected]:/backup/data/ */5 * * * * /home/user/check_uptime.sh */5 * * * * /home/user/check_uptime.sh */5 * * * * /home/user/check_uptime.sh check_uptime.sh #!/bin/bash WEBSITE="https://example.com" LOGFILE="/var/log/uptime_check.log" if curl -s --max-time 5 "$WEBSITE" > /dev/null; then echo "$(date) - $WEBSITE is UP" >> $LOGFILE else echo "$(date) - $WEBSITE is DOWN - sending alert" >> $LOGFILE echo "Website down!" | mail -s "Alert: $WEBSITE Down" [email protected] fi #!/bin/bash WEBSITE="https://example.com" LOGFILE="/var/log/uptime_check.log" if curl -s --max-time 5 "$WEBSITE" > /dev/null; then echo "$(date) - $WEBSITE is UP" >> $LOGFILE else echo "$(date) - $WEBSITE is DOWN - sending alert" >> $LOGFILE echo "Website down!" | mail -s "Alert: $WEBSITE Down" [email protected] fi #!/bin/bash WEBSITE="https://example.com" LOGFILE="/var/log/uptime_check.log" if curl -s --max-time 5 "$WEBSITE" > /dev/null; then echo "$(date) - $WEBSITE is UP" >> $LOGFILE else echo "$(date) - $WEBSITE is DOWN - sending alert" >> $LOGFILE echo "Website down!" | mail -s "Alert: $WEBSITE Down" [email protected] fi 0 8 * * * /usr/bin/python3 /home/user/generate_report.py 0 8 * * * /usr/bin/python3 /home/user/generate_report.py 0 8 * * * /usr/bin/python3 /home/user/generate_report.py #!/usr/bin/env python3 import subprocess from datetime import datetime report_date = datetime.now().strftime("%Y-%m-%d") output_file = f"/reports/report_{report_date}.txt" with open(output_file, "w") as f: f.write(f"Daily Report for {report_date}\n") f.write("=" * 40 + "\n") disk_usage = subprocess.check_output(["df", "-h"], text=True) f.write("Disk Usage:\n") f.write(disk_usage) f.write("\n") memory = subprocess.check_output(["free", "-h"], text=True) f.write("Memory Status:\n") f.write(memory) print(f"Report generated: {output_file}") #!/usr/bin/env python3 import subprocess from datetime import datetime report_date = datetime.now().strftime("%Y-%m-%d") output_file = f"/reports/report_{report_date}.txt" with open(output_file, "w") as f: f.write(f"Daily Report for {report_date}\n") f.write("=" * 40 + "\n") disk_usage = subprocess.check_output(["df", "-h"], text=True) f.write("Disk Usage:\n") f.write(disk_usage) f.write("\n") memory = subprocess.check_output(["free", "-h"], text=True) f.write("Memory Status:\n") f.write(memory) print(f"Report generated: {output_file}") #!/usr/bin/env python3 import subprocess from datetime import datetime report_date = datetime.now().strftime("%Y-%m-%d") output_file = f"/reports/report_{report_date}.txt" with open(output_file, "w") as f: f.write(f"Daily Report for {report_date}\n") f.write("=" * 40 + "\n") disk_usage = subprocess.check_output(["df", "-h"], text=True) f.write("Disk Usage:\n") f.write(disk_usage) f.write("\n") memory = subprocess.check_output(["free", "-h"], text=True) f.write("Memory Status:\n") f.write(memory) print(f"Report generated: {output_file}") */10 * * * * /home/user/check_service.sh */10 * * * * /home/user/check_service.sh */10 * * * * /home/user/check_service.sh #!/bin/bash SERVICE_NAME="nginx" if ! systemctl is-active --quiet $SERVICE_NAME; then echo "$(date) - $SERVICE_NAME was down, restarting..." >> /var/log/service_restarts.log systemctl restart $SERVICE_NAME fi #!/bin/bash SERVICE_NAME="nginx" if ! systemctl is-active --quiet $SERVICE_NAME; then echo "$(date) - $SERVICE_NAME was down, restarting..." >> /var/log/service_restarts.log systemctl restart $SERVICE_NAME fi #!/bin/bash SERVICE_NAME="nginx" if ! systemctl is-active --quiet $SERVICE_NAME; then echo "$(date) - $SERVICE_NAME was down, restarting..." >> /var/log/service_restarts.log systemctl restart $SERVICE_NAME fi sudo service cron status sudo service cron status sudo service cron status sudo systemctl status cron sudo systemctl status cron sudo systemctl status cron sudo systemctl start cron sudo systemctl enable cron sudo systemctl start cron sudo systemctl enable cron sudo systemctl start cron sudo systemctl enable cron sudo grep CRON /var/log/syslog sudo grep CRON /var/log/syslog sudo grep CRON /var/log/syslog sudo journalctl -u cron --no-pager sudo journalctl -u cron --no-pager sudo journalctl -u cron --no-pager Nov 15 03:00:01 myserver CRON[12345]: (user) CMD (/home/user/backup_logs.sh) Nov 15 03:00:01 myserver CRON[12345]: (user) CMD (/home/user/backup_logs.sh) Nov 15 03:00:01 myserver CRON[12345]: (user) CMD (/home/user/backup_logs.sh) 0 3 * * * /home/user/backup_ 0 3 * * * /home/user/backup_ 0 3 * * * /home/user/backup_ - n8n Cloud or self-hosted n8n (optional, for automation integration) - Hetzner VPS, Contabo VPS, or DigitalOcean for running cron jobs - Namecheap if you need a domain for your automation - Linux server access (Ubuntu 20.04 LTS or later recommended) - Basic command-line familiarity - Understanding Cron: The Basics - Crontab Syntax Breakdown - Creating and Managing Your First Cron Job - Real-World Examples and Use Cases - Debugging and Monitoring Cron Jobs - Getting Started