[{"data":1,"prerenderedAt":218},["ShallowReactive",2],{"seo-verification":3,"blog-cron-vs-systemd-timers-automate-your-linux-vps-ma-en":6,"promo-bar-likely":217},{"google":4,"bing":5},"EycwPY2XMyTkVzas3n1ygeNJFGAH513qrMjfDljzsMQ","",{"key":7,"data":8},"blog-cron-vs-systemd-timers-automate-your-linux-vps-ma-en",{"id":9,"slug":10,"slugs":11,"title":15,"excerpt":16,"readTime":17,"views":18,"isPinned":19,"noindex":19,"publishedAt":20,"updatedAt":21,"category":22,"categories":27,"featuredImage":29,"bgImage":30,"posterImage":31,"relatedSolution":29,"intro":32,"sections":33,"ctaTitle":161,"ctaBody":162,"ctaButton":163,"ctaUrl":164,"relatedPosts":165},230,"cron-vs-systemd-timers-automate-your-linux-vps",{"fr":12,"en":10,"ar":13,"es":14},"cron-systemd-timers-automatisation-vps","cron-مقابل-systemd-timers-أتمتة-vps-linux","cron-vs-systemd-timers-automatizar-vps-linux","cron vs systemd timers: Automate Your Linux VPS","Practical comparison of cron and systemd timers: syntax, logs, migration and troubleshooting for recurring tasks on a Linux VPS.",12,1,false,"2026-08-06T00:00:00+00:00","2026-09-20T21:13:53+00:00",{"id":23,"name":24,"slug":25,"color":26,"icon":25},2,"Automation","automatisation","bg-brand-action\u002F10 text-brand-action",[28],{"id":23,"name":24,"slug":25,"color":26,"icon":25},null,"\u002Fblog\u002Fcovers\u002Fbg.svg","\u002Fblog\u002Fcovers\u002Fcron-systemd-timers-automatisation-vps-poster.svg","On a Linux VPS, automation is not optional: nightly backups, TLS certificate renewal, log cleanup, health checks — these tasks run themselves or they don't run at all. Cron has ruled for decades, but systemd timers offer deeper integration with the service manager. This guide compares both tools, explains when to choose one over the other, and walks you through creating or migrating a systemd timer step by step.",[34,38,49,52,55,99,102,105,133,151,155,158],{"type":35,"title":36,"body":37},"h2","Why automate tasks on a VPS?","A VPS is a server running continuously, often without direct human supervision. That is precisely why automating recurring tasks is essential: nobody will be around at 3 AM to trigger a backup or renew a Let's Encrypt certificate. A forgotten task can mean data loss, an expired certificate taking the site offline, or a disk full because log rotation never ran.\n\nIn practice, five categories of tasks come up on every serious Linux VPS: **Nginx log rotation** (`logrotate` or a dedicated script) to prevent `\u002Fvar\u002Flog\u002Fnginx\u002F` from growing into gigabytes over a few weeks; **orphaned Docker image cleanup** (`docker image prune -af`) to reclaim disk space without manual intervention; **PostgreSQL backups** (`pg_dump` to remote storage) to guarantee an acceptable RPO in case of failure; **SSL certificate checking** before expiry, to trigger a certbot renewal rather than discover a broken HTTPS site; and scheduled **lightweight scraping or indexing** scripts that need to run at a fixed time. The two main tools available on Linux — cron and systemd timers — allow you to schedule these operations reliably.",{"type":39,"title":40,"items":41},"ul","Typical use cases on a VPS",[42,43,44,45,46,47,48],"**Backups** — MySQL or PostgreSQL dumps to remote storage (`pg_dump -Fc mydb | gzip > \u002Fbackup\u002Fmydb_$(date +%F).sql.gz`), file archiving with `rsync` or `rclone` to an S3 bucket","**TLS certificate renewal** — `certbot renew --quiet` or `acme.sh --cron` scheduled twice a day, with automatic Nginx reload on success","**Log rotation and cleanup** — deleting files older than 30 days (`find \u002Fvar\u002Flog -mtime +30 -delete`), weekly compression, purging application logs in production","**Docker cleanup** — `docker image prune -af`, removing orphaned volumes, rotating container logs to free up `\u002Fvar\u002Flib\u002Fdocker`","**Health checks** — verifying a service is listening (`curl -sf http:\u002F\u002Flocalhost:3000\u002Fhealth`), auto-restarting if needed, alerting via webhook","**Data synchronization** — `rsync` to a second node, cache refresh, syncing static files to a CDN","**Scheduled application tasks** — sending reminder emails, computing periodic reports, content indexing, lightweight scraping scripts",{"type":35,"title":50,"body":51},"cron: a quick refresher","Cron is present on virtually every Linux system. To schedule a task, edit the current user's crontab with `crontab -e`. The syntax uses **five fields** separated by spaces, followed by the command:\n\n```\n# ┌── minute (0-59)\n# │  ┌── hour (0-23)\n# │  │  ┌── day of month (1-31)\n# │  │  │  ┌── month (1-12)\n# │  │  │  │  ┌── day of week (0-7, 0=Sunday)\n# │  │  │  │  │\n  0  3  *  *  *  \u002Fusr\u002Flocal\u002Fbin\u002Fbackup.sh\n```\n\nShortcuts `@daily`, `@weekly`, `@monthly` and `@reboot` simplify common cases. Files placed in `\u002Fetc\u002Fcron.d\u002F` belong to the system and can specify the execution user directly on the line. To review past executions, two paths: `\u002Fvar\u002Flog\u002Fsyslog` (Debian\u002FUbuntu) filtered with `grep CRON \u002Fvar\u002Flog\u002Fsyslog`, or `journalctl -t CRON` on systems with journald. One often-underestimated detail: cron's default `PATH` is **minimal** — `\u002Fusr\u002Fbin:\u002Fbin` — which forces the use of absolute paths in scripts, or redefining `PATH=` at the top of the crontab. The `MAILTO` variable controls output mailing; setting it to empty (`MAILTO=\"\"`) silently disables it. Cron keeps no native history of past runs and does not handle missed jobs if the machine was off.",{"type":35,"title":53,"body":54},"systemd timers: an introduction","A systemd timer is a pair of two unit files: a `.service` file describing what to run, and a `.timer` file describing when to trigger it. The timer is activated with `systemctl enable --now myservice.timer` and managed by the service manager, exactly like any other systemd service.\n\nThe `OnCalendar=` directive accepts rich, readable syntax:\n- `daily` — every day at midnight\n- `Mon *-*-* 03:00:00` — every Monday at 3 AM\n- `*-*-* 02:30:00` — every day at 2:30 AM\n- `*:0\u002F15` — every 15 minutes\n\nThe `AccuracySec=` directive (default: 1 minute) lets systemd group nearby timers to save CPU wakeups — on a shared or low-power server, reducing this to `1s` ensures precise trigger times. The `Persistent=true` directive in the `[Timer]` block is the feature that sets systemd timers apart from cron: if the machine was off at the scheduled time, the timer **catches up the missed execution** at next boot. All output goes through journald and is accessible via `journalctl -u myservice.service`. The command `systemctl list-timers` shows all active timers, their next deadline and last activation date.",{"type":56,"title":57,"headers":58,"rows":62},"comparison","cron vs systemd timers: comparison table",[59,60,61],"Criterion","cron","systemd timer",[63,67,71,75,79,83,87,91,95],[64,65,66],"Schedule syntax","5-field `* * * * *`","`OnCalendar=` readable (`daily`, `Mon 03:00`)",[68,69,70],"Logs","Mail output or manual redirect, `\u002Fvar\u002Flog\u002Fsyslog`","Native journald, `journalctl -u`",[72,73,74],"Missed jobs (machine off)","Lost (unless `anacron`)","`Persistent=true` replays after reboot",[76,77,78],"Inter-service dependencies","None","`After=`, `Requires=`, `Wants=`",[80,81,82],"Isolation and resource limits","Inherits shell env, minimal PATH","Cgroups, `MemoryMax=`, `CPUQuota=`",[84,85,86],"Failure notification","None natively","`OnFailure=notify.service` triggers an alert",[88,89,90],"Run as specific user","User field in `\u002Fetc\u002Fcron.d\u002F`","`User=` and `Group=` in `.service`",[92,93,94],"Debugging","Hard without logs","`systemctl status`, `journalctl -xe`",[96,97,98],"Availability","All Unix systems (containers, BSD, Alpine)","Systems with systemd (Debian, Ubuntu, RHEL…)",{"type":35,"title":100,"body":101},"When to keep cron?","Cron remains the right choice in several concrete situations. On **older or minimal systems without systemd** — some lightweight Docker containers, Alpine images, BSD systems — cron is often the only tool available and there is no reason to move away from it. In a **multi-user environment** where each developer manages their own tasks via `crontab -e` without root rights, cron is simpler to delegate: creating a systemd `.service` requires access to `\u002Fetc\u002Fsystemd\u002Fsystem\u002F`.\n\nFor **simple one-liner scripts** — a weekly `certbot renew`, a nightly `rsync` with no dependencies — the crontab stays readable and maintainable without multiplying files. Cron is also the right choice for **onboarding a non-Linux team**: the five-field syntax, documented everywhere, is more accessible than a systemd unit for someone new to system administration. Finally, if your cron tasks **have been working for years without issues**, migration brings no immediate value. The deciding criterion is the need for structured logs, inter-service dependencies, or resource constraints — if you don't need them, cron does the job.",{"type":35,"title":103,"body":104},"When to switch to systemd timers?","Systemd timers become the clear choice as soon as the task goes beyond a simple isolated command. You need **journald** to trace every execution, its output and return code without manual plumbing? Systemd timers. Your backup script must only run after PostgreSQL is ready, declaring `After=postgresql.service` in the `[Unit]` block? Systemd timers. You want to cap the memory of a sync job so it does not starve other processes (`MemoryMax=512M`)? Systemd timers.\n\n**Inter-service dependencies** are the strongest argument: a backup launched while the database is not yet available after a reboot produces an empty file with no visible error under cron. Systemd guarantees the order. **Integrated logging** via journald removes all the noise from manual redirections — `journalctl -u backup.service --since yesterday` gives the full history with return codes. **Error handling** with `OnFailure=` lets you trigger a notification (email, webhook) without polluting the script itself. And if the server restarts at 2:58 AM when the 3 AM backup was due, `Persistent=true` guarantees it will run at next boot — something cron cannot do without `anacron`.",{"type":106,"title":107,"steps":108},"steps","Create a systemd timer from scratch (example: daily backup)",[109,112,115,118,121,124,127,130],{"title":110,"body":111},"Create the service file","Open `\u002Fetc\u002Fsystemd\u002Fsystem\u002Fbackup.service` and enter:\n\n```ini\n[Unit]\nDescription=Daily PostgreSQL backup\nAfter=postgresql.service\n\n[Service]\nType=oneshot\nUser=postgres\nExecStart=\u002Fusr\u002Flocal\u002Fbin\u002Fbackup.sh\n```\n\nThe `oneshot` type indicates the service exits after the script completes — the standard type for scheduled tasks.",{"title":113,"body":114},"Create the timer file","Create `\u002Fetc\u002Fsystemd\u002Fsystem\u002Fbackup.timer` with:\n\n```ini\n[Unit]\nDescription=Daily trigger for backup\n\n[Timer]\nOnCalendar=*-*-* 03:00:00\nPersistent=true\n\n[Install]\nWantedBy=timers.target\n```\n\n`Persistent=true` ensures that if the machine was off at 3 AM, the backup runs at next boot.",{"title":116,"body":117},"Reload systemd and enable the timer","Run `systemctl daemon-reload` so systemd picks up the new unit files, then activate and immediately start the timer:\n\n```bash\nsystemctl enable --now backup.timer\n```",{"title":119,"body":120},"Check with systemctl status","Check the timer state with `systemctl status backup.timer` — the output must show `active (waiting)` and display the next trigger date. Also check the service state after the first run with `systemctl status backup.service`.",{"title":122,"body":123},"List all active timers","The command `systemctl list-timers --all` displays a table of all system timers with three key columns: `NEXT` (next execution), `LEFT` (time remaining) and `LAST` (last execution). This is your dashboard for verifying that your timers fire as expected.",{"title":125,"body":126},"Read the logs","Access all service output with `journalctl -u backup.service` for the full history, or `journalctl -u backup.service -n 50 --since today` for the last 50 lines today. The script's return code is visible in the journald output.",{"title":128,"body":129},"Add a failure notification (optional)","To be alerted on failure, add in the `[Unit]` block of the `.service` file:\n\n```ini\nOnFailure=notify-failure@%n.service\n```\n\nThis service can send an email, a Slack webhook, or an ntfy ping depending on your infrastructure. This is what cron cannot do natively without plumbing inside the script itself.",{"title":131,"body":132},"Test the service manually","Before waiting for the scheduled trigger, test immediate execution with `systemctl start backup.service` (without `.timer`). Check the result immediately with `journalctl -u backup.service -n 20`. If the script succeeds, the setup is validated.",{"type":106,"title":134,"steps":135},"Migrate an existing cron job to systemd",[136,139,142,145,148],{"title":137,"body":138},"Identify the cron job to migrate","List your crontabs with `crontab -l` (current user) and `cat \u002Fetc\u002Fcron.d\u002F*` (system). Note the exact command, the execution user, and the five-field schedule. For example: `30 2 * * 1 root \u002Fusr\u002Fbin\u002Fcertbot renew --quiet` means every Monday at 2:30 AM as root.",{"title":140,"body":141},"Translate the schedule to OnCalendar","The `OnCalendar=` format reads `DayOfWeek Year-Month-Day Hour:Minute:Second`. `30 2 * * 1` becomes `Mon *-*-* 02:30:00`.\n\nTo validate the translation before applying it, use `systemd-analyze calendar`:\n\n```bash\nsystemd-analyze calendar 'Mon *-*-* 02:30:00'\n```\n\nThe command displays the next 10 calculated occurrences, eliminating any ambiguity about the actual behavior.",{"title":143,"body":144},"Create the .service and .timer files","Create `\u002Fetc\u002Fsystemd\u002Fsystem\u002Fcertbot-renew.service` with `Type=oneshot`, `ExecStart=\u002Fusr\u002Fbin\u002Fcertbot renew --quiet`, and `User=root`. Then create `\u002Fetc\u002Fsystemd\u002Fsystem\u002Fcertbot-renew.timer` with `OnCalendar=Mon *-*-* 02:30:00` and `Persistent=true`. Run `systemctl daemon-reload && systemctl enable --now certbot-renew.timer`.",{"title":146,"body":147},"Test the service before disabling cron","Test execution immediately with `systemctl start certbot-renew.service` and check the result via `journalctl -u certbot-renew.service -n 20`. Verify the timer appears in `systemctl list-timers` with a coherent next date. **Only disable the cron line after validation.**",{"title":149,"body":150},"Disable the cron line","Once the timer is validated, comment out or delete the line in the original crontab. For a file in `\u002Fetc\u002Fcron.d\u002F`, rename it with a `.disabled` extension or delete it. Leave both running in parallel for a few days if the task is critical — cron and systemd timers do not interfere.",{"type":152,"title":153,"body":154},"tip","Validate and debug your OnCalendar expressions","Before activating a timer in production, always validate your `OnCalendar=` expression with `systemd-analyze calendar`:\n\n```bash\nsystemd-analyze calendar \"daily\"\nsystemd-analyze calendar \"Mon *-*-* 02:30:00\"\nsystemd-analyze calendar \"*:0\u002F15\"\n```\n\nEach command displays the next occurrence and the following ten — making a malformed expression or unexpected time offset immediately visible. For an overview of all your active timers with their next trigger date and time remaining, `systemctl list-timers --all` is your daily operational dashboard.",{"type":35,"title":156,"body":157},"Common systemd timer troubleshooting","Five problems come up regularly when setting up timers.\n\n**1. \"Unit not found\" on `systemctl enable`** — the `.timer` or `.service` file is not in `\u002Fetc\u002Fsystemd\u002Fsystem\u002F` or has an incorrect name. Check the exact spelling with `ls \u002Fetc\u002Fsystemd\u002Fsystem\u002F*.timer`. A `daemon-reload` is mandatory before any `enable` or `start` on a new file.\n\n**2. Timer active but never triggers** — does `systemctl list-timers` show a coherent date in the `NEXT` column? If `NEXT` is empty or in the past, a missing `Persistent=true` combined with a machine that hasn't rebooted since the timer was created is a common cause. Check `systemctl status backup.timer`: `active (waiting)` is the normal state.\n\n**3. ExecStart not found or permission denied** — the `PATH` in a `.service` is minimal like cron. Always use the **absolute path** in `ExecStart=` (`\u002Fusr\u002Fbin\u002Fcertbot` not `certbot`). Verify the script is executable (`chmod +x \u002Fusr\u002Flocal\u002Fbin\u002Fbackup.sh`) and that the user declared in `User=` has access to it.\n\n**4. Permissions on the script** — if `User=postgres` but the script is owned by root without execute permission for postgres, the service will fail with code 203\u002FEXEC. Check `journalctl -u backup.service -n 20` to see the exact message.\n\n**5. Conflict between cron and systemd on the same task** — if you are migrating progressively, make sure both are not active simultaneously for a destructive task (log purge, file rotation). Both will execute independently and double the operations. The check is simple: `crontab -l | grep backup` and `systemctl is-active backup.timer`.",{"type":35,"title":159,"body":160},"Conclusion","Cron and systemd timers coexist without issue on the same server — you do not have to migrate everything at once. Keep cron for your simple tasks and existing user scripts, and adopt systemd timers for new automations that benefit from journald logs, inter-service dependencies or cgroup isolation. The `systemd-analyze calendar` command and `systemctl list-timers` are your best allies for validating and monitoring your schedules.","A Linux VPS ready for your automations","Our cloud VPS running Ubuntu and Debian include systemd, journald and the full modern stack. Deploy your timers, scripts and backups on reliable infrastructure, with full root access and responsive support.","Launch a Linux VPS","\u002Fvps-cloud",[166,184,199],{"id":167,"slug":168,"slugs":169,"title":173,"excerpt":174,"readTime":17,"views":18,"isPinned":19,"noindex":19,"publishedAt":175,"updatedAt":176,"category":177,"categories":178,"featuredImage":29,"bgImage":30,"posterImage":180,"relatedSolution":181},31,"installing-apache-airflow-on-a-vps-self-hosted-automation",{"fr":170,"en":168,"ar":171,"es":172},"installer-apache-airflow-vps","تثبيت-apache-airflow-على-خادم-vps-أتمتة-ذاتية-الاستضافة","instalar-apache-airflow-en-un-vps","Install Apache Airflow on VPS: Docker Compose and PostgreSQL","Install Apache Airflow on VPS with Docker Compose: PostgreSQL as metadata backend, multi-worker CeleryExecutor, encrypted connections and debugging — complete 2026 guide.","2026-05-20T00:00:00+00:00","2026-11-04T16:30:25+00:00",{"id":23,"name":24,"slug":25,"color":26,"icon":25},[179],{"id":23,"name":24,"slug":25,"color":26,"icon":25},"\u002Fblog\u002Fcovers\u002Finstaller-apache-airflow-vps-poster.svg",{"categorySlug":182,"appSlug":183},"automation-workflows","airflow",{"id":185,"slug":186,"slugs":187,"title":191,"excerpt":192,"readTime":17,"views":23,"isPinned":19,"noindex":19,"publishedAt":193,"updatedAt":194,"category":195,"categories":196,"featuredImage":29,"bgImage":30,"posterImage":198,"relatedSolution":29},212,"woodpecker-ci-and-forgejo-cicd-pipeline-on-a-vps",{"fr":188,"en":186,"ar":189,"es":190},"woodpecker-ci-pipeline-vps-forgejo","woodpecker-ci-وforgejo-خط-أنابيب-cicd-على-خادم-vps","woodpecker-ci-forgejo-pipeline-cicd-vps","Woodpecker CI and Forgejo: CI\u002FCD pipeline on a VPS","Deploy Woodpecker CI v3 with Forgejo on your VPS: Docker Compose setup, YAML pipelines, secrets, multi-arch runners and OAuth troubleshooting.","2026-08-02T00:00:00+00:00","2026-11-08T01:13:13+00:00",{"id":23,"name":24,"slug":25,"color":26,"icon":25},[197],{"id":23,"name":24,"slug":25,"color":26,"icon":25},"\u002Fblog\u002Fcovers\u002Fwoodpecker-ci-pipeline-vps-forgejo-poster.svg",{"id":200,"slug":201,"slugs":202,"title":206,"excerpt":207,"readTime":208,"views":209,"isPinned":19,"noindex":19,"publishedAt":210,"updatedAt":176,"category":211,"categories":212,"featuredImage":29,"bgImage":30,"posterImage":214,"relatedSolution":215},29,"installing-kestra-on-a-vps-self-hosted-automation",{"fr":203,"en":201,"ar":204,"es":205},"installer-kestra-vps","تثبيت-kestra-على-خادم-vps-أتمتة-ذاتية-الاستضافة","instalar-kestra-en-un-vps","Installing Kestra on a VPS: self-hosted automation","Install Kestra on your VPS: orchestration of declarative workflows in YAML for your data and ETL pipelines, self-hosted with Docker.",3,8,"2026-05-22T00:00:00+00:00",{"id":23,"name":24,"slug":25,"color":26,"icon":25},[213],{"id":23,"name":24,"slug":25,"color":26,"icon":25},"\u002Fblog\u002Fcovers\u002Finstaller-kestra-vps-poster.svg",{"categorySlug":182,"appSlug":216},"kestra",true,1794270384075]