Linux: Task Automation — cron / systemd / journalctl

Everything on a server should be automated — scheduled backups, log cleanup, health checks. Linux provides automation solutions ranging from simple (cron) to modern (systemd timers).

📋 Prerequisites: You should already know

1. What You'll Learn


2. A Story of "Never Waking Up at 2 AM to Run a Script Again"

(1) The Pain Point: Manual Daily Backups

Xiaoming's backup script needed to be run manually every day. He set an alarm — waking up at 2 AM to run ./backup.sh. After a week, he was exhausted.

(2) cron Set Him Free

Bob taught him crontab:

BASH
crontab -e
# Add:
0 2 * * * /home/alice/scripts/backup.sh > /dev/null 2>&1

From then on, the system automatically ran the backup every day at 2 AM. Xiaoming only needed to check the logs occasionally.

(3) The Benefit: Let Machines Do What Machines Should Do

After learning cron, Xiaoming automated everything at once: daily backups, weekly reports, log cleanup, health check pings. His daily ops time dropped from 2 hours to 10 minutes.


3. Knowledge Points

(1) crontab — Scheduled Tasks

cron syntax: minute hour day month weekday command

TEXT
 min  hour  day  month  weekday  command
(0-59)(0-23)(1-31)(1-12)(0-7)
                      (0=Sunday 7=Sunday)
TEXT
# Special syntax
@reboot     # Run on reboot
@daily      # Every day at 0:00 (same as 0 0 * * *)
@hourly     # Every hour (same as 0 * * * *)
@weekly     # Every Monday at 0:00
@monthly    # 1st of every month at 0:00

Common Examples:

TEXT
# View current user's cron
crontab -l

# Edit crontab
crontab -e

# Delete all cron jobs
crontab -r

# Common examples
0 2 * * * /scripts/backup.sh              # Daily at 2:00
*/5 * * * * /scripts/health-check.sh      # Every 5 minutes
0 0 * * 0 /scripts/weekly-report.sh       # Every Sunday at 0:00
0 0 1 * * /scripts/monthly-cleanup.sh     # 1st of every month
0 9-17 * * 1-5 /scripts/business-hours.sh # Weekdays 9-17, every hour
⚠️ Warning: crontab -r deletes all cron jobs for the current user with no confirmation prompt! Accidental deletion can only be recovered from backup. Always use crontab -l to back up before deleting, or develop the habit of editing with crontab -e rather than deleting with crontab -r.

💡 Tip: The cron execution environment differs from an interactive shell — it doesn't load ~/.bashrc, ~/.profile, etc., and PATH typically only includes /usr/bin:/bin. If your script depends on custom environment variables, manually source config files at the top, or use absolute paths for commands in crontab.

(2) systemd service

BASH
# Service unit path
/etc/systemd/system/myapp.service

# Basic structure
[Unit]
Description=My Web Application
After=network.target

[Service]
Type=simple
User=alice
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/node server.js
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

# Usage
sudo systemctl daemon-reload     # Reload configuration
sudo systemctl start myapp       # Start
sudo systemctl enable myapp      # Enable on boot
sudo systemctl status myapp      # View status
sudo journalctl -u myapp -f      # View logs

(3) systemd timer

💡 Tip: Choose systemd timer or cron? If your scheduled task depends on other services, needs logging, or might be missed during system downtime (needs catch-up), choose systemd timer (set Persistent=true). For simple periodic scripts without these needs, cron is more lightweight and intuitive. New projects should try systemd timer first.

BASH
# Timer unit (replaces cron)
/etc/systemd/system/backup.timer

[Unit]
Description=Daily Backup Timer

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target

# Corresponding service
/etc/systemd/system/backup.service

[Unit]
Description=Daily Backup Service

[Service]
Type=oneshot
ExecStart=/opt/scripts/backup.sh

# Usage
sudo systemctl enable --now backup.timer
sudo systemctl list-timers --all

OnCalendar Syntax:

TEXT
OnCalendar=daily                    # Every day at 0:00
OnCalendar=hourly                   # Every hour
OnCalendar=*-*-* 02:00:00           # Every day at 2:00
OnCalendar=Mon *-*-* 03:00:00       # Every Monday at 3:00
OnCalendar=*-*-1 00:00:00           # 1st of every month
OnCalendar=*:0/5                    # Every 5 minutes

(4) journalctl — Log Viewer

BASH
# Basic
journalctl                           # All logs
journalctl -u nginx                  # Specific service
journalctl -u nginx -f               # Follow in real time
journalctl -u nginx --since "1 hour ago"
journalctl -u nginx --until "2026-07-07 10:00"
journalctl -p err                    # Error level and above only
journalctl -p err -u nginx           # nginx error logs

# Management
journalctl --disk-usage              # Check log disk usage
sudo journalctl --vacuum-size=500M   # Limit log size
sudo journalctl --vacuum-time=7d     # Keep 7 days

(5) logrotate — Log Rotation

💡 Tip: logrotate is automatically executed daily by the system's cron (script at /etc/cron.daily/logrotate). You only need to write configuration in /etc/logrotate.d/. After modifying a config, use logrotate -d /etc/logrotate.d/your-config in debug mode to preview, then let it take effect automatically.

BASH
# Configuration locations
/etc/logrotate.conf                  # Global config
/etc/logrotate.d/                    # Per-app configs

# Example: Nginx log rotation
/var/log/nginx/*.log {
    daily
    rotate 7
    compress
    delaycompress
    missingok
    notifempty
    create 640 www-data adm
    postrotate
        [ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid`
    endscript
}

Common Directives:

Directive Meaning
daily/weekly/monthly Rotation frequency
rotate N Keep N archived copies
compress Compress archives (gzip)
delaycompress Delay compression by one cycle
missingok Don't error if file is missing
notifempty Don't rotate empty files
size 100M Only rotate when file reaches specified size
maxage 30 Delete archives older than 30 days

(6) ▶ Example: crontab Configuration

TEXT
# Edit crontab
crontab -e

# Write the following content
# ┌───── minute (0-59)
# │ ┌───── hour (0-23)
# │ │ ┌───── day of month (1-31)
# │ │ │ ┌───── month (1-12)
# │ │ │ │ ┌───── day of week (0-7, 0=Sunday)
# * * * * * command

# Daily 2:00 AM automated backup
0 2 * * * /home/alice/scripts/backup.sh

# Every 5 minutes health check
*/5 * * * * /home/alice/scripts/health-check.sh

# Every Monday 3:00 AM weekly report
0 3 * * 1 /home/alice/scripts/weekly-report.sh

# 1st of every month archive cleanup
0 0 1 * * /home/alice/scripts/cleanup.sh

(7) ▶ Example: Creating a systemd Service

BASH
# Create a systemd service for a Node.js application
sudo tee /etc/systemd/system/webapp.service << 'EOF'
[Unit]
Description=Node.js Web Application
Documentation=https://example.com/docs
After=network.target

[Service]
Type=simple
User=alice
Group=alice
WorkingDirectory=/opt/webapp
Environment=NODE_ENV=production
Environment=PORT=3000
ExecStart=/usr/bin/node /opt/webapp/server.js
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=10

# Security options
ProtectSystem=full
NoNewPrivileges=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now webapp
sudo systemctl status webapp

Output:

TEXT
● webapp.service - Node.js Web Application
     Loaded: loaded (/etc/systemd/system/webapp.service; enabled)
     Active: active (running) since Tue 2026-07-07 10:23:45 UTC
   Main PID: 5678 (node)
      Tasks: 12 (limit: 4915)
     Memory: 45.2M
        CPU: 1.234s

(8) ▶ Example: systemd Timer Replacing cron

BASH
# Backup service
sudo tee /etc/systemd/system/backup.service << 'EOF'
[Unit]
Description=Daily Backup

[Service]
Type=oneshot
ExecStart=/opt/scripts/backup.sh
User=alice
EOF

# Backup timer
sudo tee /etc/systemd/system/backup.timer << 'EOF'
[Unit]
Description=Run backup daily at 2am
Requires=backup.service

[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true

[Install]
WantedBy=timers.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
sudo systemctl list-timers --all

Output:

TEXT
NEXT                        LEFT          LAST                        PASSED
Tue 2026-07-07 02:00:00 UTC 15h left      Mon 2026-07-06 02:00:00 UTC 9h ago
n/a                         n/a           n/a                         n/a

1 timers listed.

(9) ▶ Example: journalctl Log Queries

BASH
# View nginx errors from the last hour
journalctl -u nginx -p err --since "1 hour ago"

# View all logs from yesterday
journalctl --since yesterday --until today

# View logs for a specific PID
journalctl _PID=1234

# Follow multiple services in real time
journalctl -u nginx -u webapp -f

# Export logs to file
journalctl -u webapp --since "2026-07-06" > webapp-logs.txt

# Check log disk usage
journalctl --disk-usage
# Archived and active journals take up 512.0M

# Clean up
sudo journalctl --vacuum-size=200M

Output:

TEXT
Jul 07 10:23:45 server nginx[1234]: GET /api/users 200 1234
Jul 07 10:23:50 server nginx[1234]: POST /api/data 500 0
Jul 07 10:24:01 server webapp[5678]: Error: ECONNREFUSED
-- Logs begin at Mon 2026-07-06 00:00:01 --
Jul 07 09:00:00 server sshd[890]: Accepted publickey for alice
Archived and active journals take up 512.0M
Deleted archived journals: 23
Vacuuming done, freed 312.0M of archived journals

(10) ▶ Example: logrotate Configuration

BASH
# Configure log rotation for a custom application
sudo tee /etc/logrotate.d/custom-app << 'EOF'
/var/log/custom-app/*.log {
    daily
    rotate 14
    compress
    delaycompress
    missingok
    notifempty
    create 640 www-data www-data
    sharedscripts
    postrotate
        systemctl reload custom-app > /dev/null 2>&1 || true
    endscript
}
EOF

# Test the configuration
sudo logrotate -d /etc/logrotate.d/custom-app

# Manually force execution
sudo logrotate -f /etc/logrotate.d/custom-app

Output:

TEXT
reading config file /etc/logrotate.d/custom-app
Handling 1 logs

rotating pattern: /var/log/custom-app/*.log
rotating 14 kept versions
considering log /var/log/custom-app/app.log
  log needs rotating
renaming /var/log/custom-app/app.log to /var/log/custom-app/app.log.1
creating new /var/log/custom-app/app.log mode=0640 user=www-data group=www-data

(11) ▶ Comprehensive Example: Automated Ops System

BASH
#!/bin/bash
# setup-automation.sh - Configure automated operations system

set -euo pipefail

echo "=== Setting Up Automated Ops System ==="

# 1. Create scripts directory
mkdir -p /opt/scripts

# 2. Backup script
cat > /opt/scripts/backup.sh << 'BACKUPEOF'
#!/bin/bash
# Auto backup - triggered daily at 2 AM by cron or systemd timer

SOURCE="/var/www"
DEST="/backup"
RETENTION=30

mkdir -p "$DEST"
BACKUP_FILE="${DEST}/backup_$(date +%Y%m%d).tar.gz"

tar -czf "$BACKUP_FILE" \
    --exclude=node_modules \
    --exclude=.git \
    "$SOURCE" 2>/dev/null

# Clean expired backups
find "$DEST" -name "backup_*.tar.gz" -mtime +$RETENTION -delete

# Log
echo "[$(date)] Backup: $BACKUP_FILE ($(du -h "$BACKUP_FILE" | cut -f1))" >> /var/log/backup.log
BACKUPEOF
chmod +x /opt/scripts/backup.sh

# 3. Health check script
cat > /opt/scripts/health-check.sh << 'HEALTHEOF'
#!/bin/bash
# Health check - runs every 5 minutes

WEBHOOK_URL="https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
SERVICES=("nginx" "postgresql" "webapp")

for svc in "${SERVICES[@]}"; do
    if ! systemctl is-active --quiet "$svc"; then
        MESSAGE="❌ $svc service abnormal $(date)"
        echo "$MESSAGE" | mail -s "Service Alert" admin@example.com
        # curl -X POST -d "text=$MESSAGE" "$WEBHOOK_URL"
    fi
done

# Disk check
DISK_USAGE=$(df -h / | tail -1 | awk '{print $5}' | tr -d '%')
if [ "$DISK_USAGE" -gt 85 ]; then
    echo "⚠️ Disk usage: ${DISK_USAGE}%" >> /var/log/disk-alert.log
fi
HEALTHEOF
chmod +x /opt/scripts/health-check.sh

# 4. Configure cron
(crontab -l 2>/dev/null; echo "") | crontab -
(crontab -l 2>/dev/null; echo "# Auto backup - daily at 2:00") | crontab -
(crontab -l 2>/dev/null; echo "0 2 * * * /opt/scripts/backup.sh > /dev/null 2>&1") | crontab -
(crontab -l 2>/dev/null; echo "# Health check - every 5 minutes") | crontab -
(crontab -l 2>/dev/null; echo "*/5 * * * * /opt/scripts/health-check.sh > /dev/null 2>&1") | crontab -
(crontab -l 2>/dev/null; echo "# Log cleanup - every Sunday") | crontab -
(crontab -l 2>/dev/null; echo "0 0 * * 0 journalctl --vacuum-size=200M > /dev/null 2>&1") | crontab -

echo ""
echo "=== Automated Ops System Setup Complete ==="
echo "Configured tasks:"
crontab -l | grep -v "^#"
echo ""
echo "Manual verification: crontab -l"

❓ FAQ

Q: Are crontab environment variables the same as in the terminal? A: No! The PATH in cron's execution environment is very minimal (usually /usr/bin:/bin). If your script depends on PATH defined in ~/.bashrc, either set PATH at the top of the script or use absolute paths in the cron command.

Q: What advantages does systemd timer have over cron? A: 1) Dependency management: timers can depend on other services. 2) Log integration: automatic journalctl logging. 3) Missed execution: Persistent=true catches up on next boot. 4) More precise: supports millisecond accuracy. 5) Unified management: systemctl list-timers.

Q: Will journalctl logs fill up the hard drive? A: Default maximum is 10% of disk (but no more than 4GB). Use journalctl --vacuum-size=500M or configure SystemMaxUse=500M in /etc/systemd/journald.conf to limit.

Q: Does logrotate run automatically or do I need to configure cron? A: logrotate is executed daily by the system's built-in cron job (at /etc/cron.daily/logrotate). You only need to configure rules in /etc/logrotate.d/ — no additional cron setup needed.

Q: How to debug crontab not executing? A: Three-step troubleshooting: 1) Check if the cron service is running: systemctl status cron. 2) Check cron logs: grep CRON /var/log/syslog. 3) Manually run the script to confirm it works: bash /path/to/script.sh.


📖 Summary


📝 Exercises

  1. Basic (⭐): Configure crontab to run date every hour and append to /tmp/cron-test.log, then use journalctl to view nginx or sshd startup logs
  2. Intermediate (⭐⭐): Create a simple hello.service and start it (Type=simple), then create a logrotate config for /var/log/test-app/*.log with daily rotation keeping 7 days
  3. Challenge (⭐⭐⭐): Write a systemd timer that cleans expired files in /tmp daily at 3:00 AM, with a corresponding service file, and configure journalctl to view its execution logs
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏