Crontab Examples for Self-Hosting
What Is Crontab?
Crontab is the configuration file for cron, Linux’s built-in task scheduler. Every self-hosted server needs automated maintenance — backups, log rotation, certificate renewal, container cleanup. Crontab handles all of it.
If you need a refresher on cron syntax, read Cron Jobs for Maintenance first. This guide focuses on practical, copy-paste-ready examples for self-hosting.
Prerequisites
- A Linux server with SSH access
- Basic Linux command line knowledge
croninstalled (present on virtually all Linux distributions)
Crontab Syntax Refresher
┌───────── minute (0-59)
│ ┌─────── hour (0-23)
│ │ ┌───── day of month (1-31)
│ │ │ ┌─── month (1-12)
│ │ │ │ ┌─ day of week (0-7, 0 and 7 = Sunday)
│ │ │ │ │
* * * * * command
Edit your crontab with:
crontab -e
Backup Examples
Daily Docker Volume Backup
Back up all named Docker volumes to a timestamped tarball every night at 2 AM:
0 2 * * * /usr/bin/docker run --rm -v backup_data:/data -v /backups:/backup alpine tar czf /backup/volumes-$(date +\%Y\%m\%d).tar.gz -C /data . >> /var/log/backup.log 2>&1
Weekly Database Dump
Export a PostgreSQL database from a Docker container every Sunday at 3 AM:
0 3 * * 0 /usr/bin/docker exec postgres-db pg_dump -U postgres myapp > /backups/db/myapp-$(date +\%Y\%m\%d).sql 2>> /var/log/backup.log
For MySQL/MariaDB:
0 3 * * 0 /usr/bin/docker exec mariadb-server mysqldump -u root -p"${MYSQL_ROOT_PASSWORD}" --all-databases > /backups/db/all-$(date +\%Y\%m\%d).sql 2>> /var/log/backup.log
Restic Backup Every 6 Hours
If you use restic for incremental backups:
0 */6 * * * /usr/bin/restic -r /mnt/backup-drive/restic-repo backup /srv/data --exclude-caches --tag automated >> /var/log/restic.log 2>&1
Backup Rotation — Delete Files Older Than 30 Days
30 3 * * * find /backups -name "*.tar.gz" -mtime +30 -delete >> /var/log/backup-cleanup.log 2>&1
Docker Maintenance
Prune Unused Docker Resources Weekly
Dangling images, stopped containers, and unused networks waste disk space:
0 4 * * 0 /usr/bin/docker system prune -af --volumes >> /var/log/docker-prune.log 2>&1
Remove the --volumes flag if you want to keep unused volumes.
Prune Only Dangling Images Daily
Less aggressive — only removes untagged images:
0 4 * * * /usr/bin/docker image prune -f >> /var/log/docker-prune.log 2>&1
Restart a Container Every Night
Some apps develop memory leaks. Restart them during low-traffic hours:
0 5 * * * /usr/bin/docker restart jellyfin >> /var/log/container-restart.log 2>&1
Check for Docker Image Updates
Using Diun or a simple script:
0 8 * * * /usr/bin/docker pull ghcr.io/immich-app/server:v1.99.0 2>&1 | grep -q "Status: Downloaded newer image" && echo "$(date): immich update available" >> /var/log/updates.log
SSL Certificate Renewal
Certbot Auto-Renewal
Certbot installs its own cron/systemd timer, but if you need to set it up manually:
0 0,12 * * * /usr/bin/certbot renew --quiet --deploy-hook "docker restart nginx-proxy" >> /var/log/certbot.log 2>&1
Runs twice daily (Let’s Encrypt recommends this). Only renews certificates within 30 days of expiry.
Monitoring and Health Checks
Check if a Container Is Running Every 5 Minutes
*/5 * * * * /usr/bin/docker ps --filter "name=nextcloud" --filter "status=running" -q | grep -q . || (/usr/bin/docker start nextcloud && echo "$(date): Restarted nextcloud" >> /var/log/container-health.log)
Disk Space Alert
Send a notification when disk usage exceeds 85%:
0 */4 * * * df -h / | awk 'NR==2 {gsub(/%/,"",$5); if($5 > 85) print strftime("%Y-%m-%d %H:%M") " ALERT: Disk usage at " $5 "%"}' >> /var/log/disk-alert.log
Check HTTP Endpoint Health
Verify your services are responding:
*/10 * * * * curl -sf --max-time 10 https://cloud.yourdomain.com/status.php > /dev/null || echo "$(date): Nextcloud DOWN" >> /var/log/health-check.log
Log Management
Compress Logs Older Than 7 Days
0 1 * * * find /var/log -name "*.log" -mtime +7 -not -name "*.gz" -exec gzip {} \; 2>/dev/null
Truncate Large Log Files Monthly
For application logs that grow unbounded:
0 0 1 * * for f in /srv/*/logs/*.log; do [ $(stat -f%z "$f" 2>/dev/null || stat -c%s "$f") -gt 104857600 ] && truncate -s 0 "$f" && echo "$(date): Truncated $f" >> /var/log/log-rotation.log; done
This truncates any log file over 100 MB.
System Maintenance
Update Package Lists Weekly
0 6 * * 1 apt-get update >> /var/log/apt-update.log 2>&1
Don’t auto-upgrade — just update the package index so you know what’s available. Review and apply updates manually or with unattended-upgrades for security patches only.
Security Updates Only — Automatic
On Debian/Ubuntu with unattended-upgrades configured:
0 6 * * * /usr/bin/unattended-upgrade -d >> /var/log/unattended-upgrades/cron.log 2>&1
Sync Time with NTP
Most modern systems use systemd-timesyncd, but if you need manual sync:
0 */6 * * * /usr/sbin/ntpdate pool.ntp.org >> /var/log/ntp-sync.log 2>&1
Dynamic DNS Update
If your home IP changes and you use Dynamic DNS:
*/5 * * * * /usr/local/bin/update-ddns.sh >> /var/log/ddns.log 2>&1
Crontab Best Practices
Always Redirect Output
Without redirection, cron sends output as email (which usually fails on servers without a mail system configured):
# Bad — output goes nowhere useful
0 2 * * * /backup.sh
# Good — output goes to a log file
0 2 * * * /backup.sh >> /var/log/backup.log 2>&1
Use Full Paths
Cron runs with a minimal PATH. Always use absolute paths for commands:
# Bad — docker might not be in cron's PATH
0 2 * * * docker system prune -f
# Good
0 2 * * * /usr/bin/docker system prune -f
Find the full path with which docker.
Escape Percent Signs
In crontab, % is a special character (newline). Escape it with \%:
# Bad — breaks
0 2 * * * echo $(date +%Y%m%d)
# Good
0 2 * * * echo $(date +\%Y\%m\%d)
Stagger Jobs
Don’t schedule everything at the same time:
# Bad — everything at 2 AM
0 2 * * * /backup.sh
0 2 * * * /docker-prune.sh
0 2 * * * /cert-renew.sh
# Good — spread across the night
0 2 * * * /backup.sh
30 3 * * * /docker-prune.sh
0 4 * * * /cert-renew.sh
Test Before Scheduling
Run the command manually first to verify it works:
# Test the command
/usr/bin/docker system prune -af --volumes
# Then add to crontab
crontab -e
Common Mistakes
Job Runs But Nothing Happens
Most common cause: wrong PATH. Cron’s default PATH is minimal. Use full paths to all binaries.
Job Doesn’t Run At All
Check cron is running:
systemctl status cron
Check the cron log:
grep CRON /var/log/syslog
Verify your crontab is saved:
crontab -l
“No MTA installed” Warnings
Cron tries to email command output. Either redirect output to a file (recommended) or install a mail system.
Timezone Issues
Cron uses the system timezone. Check with timedatectl. If your server is in UTC but you want jobs at 2 AM local time, convert the hour.
Next Steps
Crontab covers most scheduling needs for self-hosting. For more complex scheduling (dependencies between jobs, retry logic, conditional execution), consider systemd timers or self-hosted automation tools like n8n.
FAQ
Should I use cron or systemd timers?
Cron for simple, recurring jobs. Systemd timers for jobs that need dependency management, better logging, or resource controls. For most self-hosting maintenance tasks, cron is simpler and sufficient.
How do I see what cron jobs are scheduled?
Run crontab -l for your user’s jobs. Check /etc/crontab and /etc/cron.d/ for system-level jobs. Some packages install their own cron jobs in /etc/cron.daily/, /etc/cron.weekly/, etc.
Can I run a cron job as a specific user?
Yes. Edit that user’s crontab with sudo crontab -u username -e. Or use the system crontab /etc/crontab which has a user field.
How do I debug a failing cron job?
Redirect both stdout and stderr to a log file (>> /var/log/myjob.log 2>&1), then check the log after the scheduled time. Also check /var/log/syslog for cron execution records.
Related
Get self-hosting tips in your inbox
New guides, comparisons, and setup tutorials — delivered weekly. No spam.