How to Create a systemd Timer on Linux: 7 Safe Steps

Cute Linux penguin building an automation robot to illustrate how to create a systemd timer on Linux for scheduled task execution. Linux & Administration
Set up your first systemd timer without touching production data. A tested calendar expression, real service and timer files, and clear verification commands make this a practical Linux scheduling lab.
🐧 Linux & Administration🎓 systemd Timers · Lesson 02/03🛠️ Hands-on lab

Build a systemd Timer You Can Test and Trust

You have a script that works when you run it yourself. Now you want Linux to run it on schedule—and you need a way to prove it actually ran. This tutorial builds a working systemd timer from scratch without touching real backups or production data.

✍️ Servers Academy Editorial Team⏱️ Beginner-friendly practical lesson🧪 Reversible demo
systemd timer activating a Linux service, shown as a luminous clock connected to an automated server task
A timer decides when work starts; a service decides what runs. The illustration shows that handoff, not a literal systemd interface.
🧩 Two real unit filesA .service paired with a .timer
🔎 Verifiable resultsCheck schedules, status and the journal
🧹 Clean removalUndo every configuration file you create
🎓 Mini-course: Linux systemd TimersAll mini-courses ↗
01FoundationsHow systemd timers work
02Practice · You are hereBuild and test a scheduled service
03TroubleshootingComing soon · no link yet

What you will build

The goal is modest: a demo command prints a timestamp. A .service unit runs the command; a .timer unit starts the service every five minutes. This systemd timer is intentionally simple, so each stage is easy to inspect. You will also start the service manually, so you can verify success without waiting for a scheduled run.

Quick answer: To create a systemd timer, write an executable script, create a matching .service file and .timer file under /etc/systemd/system/, validate them, reload systemd, and use systemctl enable --now on the timer—not the service. Inspect the service journal to confirm execution.

This is a hypothetical training exercise, not a benchmark or a backup recipe. It is designed for an Ubuntu, Debian, or similar Linux server using systemd as PID 1. Use a disposable VM or a noncritical test host; you need sudo access. The commands below change local system configuration, so read the cleanup step before starting.

01

Check the system and choose unused names

Confirm that systemd is installed and that the unprivileged nobody account exists. Check that the three example paths are unused before creating anything. A test command is silent when it succeeds; if a path already exists, stop and use a different lab name rather than overwriting someone else’s files.

Terminal · check before creating files
systemctl --version | head -n 1
id nobody
if [ -e /usr/local/bin/sa-timer-demo.sh ] ||
   [ -e /etc/systemd/system/sa-timer-demo.service ] ||
   [ -e /etc/systemd/system/sa-timer-demo.timer ]; then
  printf 'Stop: a demo path already exists.\n'
else
  printf 'All demo paths are available.\n'
fi
⚠️ Stop if a path exists

These files use the sa-timer-demo prefix so they are easy to identify. If any preflight test returns an error, inspect the conflict before proceeding. Do not run the creation commands on an important server without understanding your local unit naming.

02

Create a harmless Bash-compatible script

Rather than modifying logs, databases, or user files, the demo prints one line containing the time. This makes the job visible in the journal without needing a writable directory. A systemd timer can manage a much more complex script later, but the basic setup is the same.

Terminal · create a read-only demonstration script
sudo tee /usr/local/bin/sa-timer-demo.sh >/dev/null <<'EOF'
#!/bin/sh
set -eu
printf 'sa-timer-demo: executed at %s\n' "$(/usr/bin/date --iso-8601=seconds)"
EOF
sudo chmod 0755 /usr/local/bin/sa-timer-demo.sh
/usr/local/bin/sa-timer-demo.sh

The final command runs the script once in your current terminal. If that test fails, fix the script first; a timer cannot repair a broken program. Its output should resemble sa-timer-demo: executed at 2026-10-16T12:15:00+00:00, but your actual time and time zone will differ.

03

Create the service unit

The service defines what systemd will run. For this single action, Type=oneshot is a good fit: systemd waits for the script to finish instead of expecting a long-running daemon.

/etc/systemd/system/sa-timer-demo.service · terminal
sudo tee /etc/systemd/system/sa-timer-demo.service >/dev/null <<'EOF'
[Unit]
Description=Servers Academy demonstration task

[Service]
Type=oneshot
User=nobody
ExecStart=/usr/local/bin/sa-timer-demo.sh
StandardOutput=journal
StandardError=journal
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
EOF
SettingWhy this lab uses it
Type=oneshotThe program runs, exits and is not meant to stay active.
User=nobodyRuns this read-only example without root privileges. Verify the account exists first.
ExecStart=...Gives systemd an absolute executable path.
StandardOutput=journalSends the script’s printed line to the journal.
NoNewPrivileges=, ProtectSystem=, ProtectHome=Constrain this read-only demonstration. A real job that writes files needs deliberately chosen permissions and paths.

A successfully completed oneshot service will normally return to inactive (dead). That is not evidence of failure. Its exit result and journal entries tell the story. For background on unit structure, see how to create a systemd service.

04

Create the timer unit

Now define when the service runs. This is the systemd timer unit, separate from the service it will activate. The expression below means minute 00, 05, 10, 15 and so on, every hour. Five minutes is useful for a short lab; you would probably choose a less frequent schedule for a real maintenance job.

/etc/systemd/system/sa-timer-demo.timer · terminal
sudo tee /etc/systemd/system/sa-timer-demo.timer >/dev/null <<'EOF'
[Unit]
Description=Run the Servers Academy demo every five minutes

[Timer]
OnCalendar=*-*-* *:00/5:00
AccuracySec=1min
Persistent=false
Unit=sa-timer-demo.service

[Install]
WantedBy=timers.target
EOF
💡 Why we set Persistent=false

We do not need catch-up behavior in a disposable five-minute lab. Persistent=true can trigger a missed calendar occurrence when the timer becomes active again, but it does not queue every missed interval. This is explained in the previous Foundations lesson and the official timer manual.

Setting AccuracySec=1min allows systemd to coalesce timer events within roughly the following minute rather than promise execution on the exact second. The local system clock and time zone affect calendar calculations; review them with timedatectl if your output seems unexpected.

05

Check the calendar, validate the files and test the service

Before enabling the schedule, ask systemd to parse the time expression and show its next occurrences. Then verify the unit definitions, reload the manager and start the service once manually. This isolates script errors from timer errors.

Terminal · validate, reload and run once
systemd-analyze calendar --iterations=3 '*-*-* *:00/5:00'
sudo systemd-analyze verify /etc/systemd/system/sa-timer-demo.service /etc/systemd/system/sa-timer-demo.timer
sudo systemctl daemon-reload
sudo systemctl start sa-timer-demo.service
sudo journalctl -u sa-timer-demo.service -n 12 --no-pager
🔎 What counts as proof?

Look for the sa-timer-demo: executed at ... message in the service journal. Your timestamps will depend on the machine, and journal access may require sudo. The calendar expression was independently syntax-checked on Debian 13 with systemd 257; this does not claim that the tutorial was executed on your server.

06

Enable the timer and inspect the next run

Enable the timer so it starts again after boot, and start it now. The systemd timer will take care of activating its matching service when the configured time arrives.

Terminal · enable and inspect the timer
sudo systemctl enable --now sa-timer-demo.timer
systemctl is-enabled sa-timer-demo.timer
systemctl is-active sa-timer-demo.timer
systemctl list-timers --all sa-timer-demo.timer
systemctl status sa-timer-demo.timer --no-pager

is-enabled answers whether the timer is configured to start automatically; is-active tells you if it is currently running. list-timers shows the next and previous trigger times when available. None of these, by itself, proves the script finished successfully: use the service journal for that.

⚠️ Avoid the common wrong target

systemctl enable --now sa-timer-demo.service is not the correct command for this example: the service has no [Install] section, and you want the .timer to control activation. Also, a timer cannot start a second copy of an already-active target service; a long-running job can prevent a subsequent activation rather than produce overlapping copies.

07

Clean up and decide on a real schedule

When you have seen the scheduled run—or checked a manual run and the next planned trigger—remove the demonstration. These commands operate only on the three paths introduced in this systemd timer tutorial. Read each path before running them.

Terminal · remove only this lesson’s files
sudo systemctl disable --now sa-timer-demo.timer
sudo rm -- /etc/systemd/system/sa-timer-demo.timer
sudo rm -- /etc/systemd/system/sa-timer-demo.service
sudo rm -- /usr/local/bin/sa-timer-demo.sh
sudo systemctl daemon-reload

For a real job, choose the required account and filesystem permissions deliberately, use absolute paths, test manually, and decide whether catch-up execution is safe. Backups, in particular, require verifying actual restore capability; seeing a timer fire is not a backup test.

Which systemd timer settings should you use?

A useful systemd timer starts with the requirements of the job, not a collection of copied directives. Do not combine settings just because they look useful.

NeedTimer optionDecision
Run at a recognizable clock timeOnCalendar=Use a calendar expression; check time zone and clock accuracy.
Run after the machine bootsOnBootSec=Relative to boot time; not a daily wall-clock schedule.
Run again after the unit was activatedOnUnitActiveSec=Relative to the last activation, not an exact clock boundary.
Recover one missed calendar occurrencePersistent=trueUseful only when the job can tolerate catch-up behavior; not a replay queue.
Avoid a synchronized workload spikeRandomizedDelaySec=Spread activations out; not needed in this small lab.

If you already use cron, read Cron vs systemd Timers before migrating. The two tools have different operational tradeoffs; one is not universally better.

When the timer appears to work but nothing happens

What you observeLikely next check
Calendar command rejects the expressionCorrect OnCalendar before editing the unit again.
Timer active, but no execution lineCheck the next scheduled trigger, then the associated service and journal.
Service fails immediatelyCheck script permissions, absolute path, User= and journalctl output.
Oneshot reads inactive (dead)Normal after successful completion; inspect the exit result and journal.
Changes do not take effectRun daemon-reload, then restart the timer if you changed its active schedule.

For detailed log-reading techniques, see journalctl Explained. The next lesson in this mini-course will focus on diagnosing missed runs, failed units and recurring scheduling problems.

Quick knowledge check

Does enabling the .timer also mean the .service ran successfully?

No. Enabling starts the scheduling mechanism. Check the service result and journal output for evidence of execution.

Why do we start the service manually before enabling the timer?

To confirm the script and service configuration work independently of any scheduling rule.

Should a Type=oneshot service remain active after it finishes?

Usually not. Without RemainAfterExit=yes, it normally returns to inactive after completing its work.

Is a five-minute schedule appropriate for production backups?

Not automatically. Choose the interval from recovery requirements, data volume, system impact and restore-test results. This article’s schedule is for a small, disposable lab.

Documentation and further reading

Responsible editorial team: Servers Academy Editorial Team. This is a documented training example, not a report of production deployment or a performance benchmark. WordPress should display the actual publication and substantive update dates outside this Custom HTML block; do not insert a fabricated date here.
Rate article
Servers Academy
Add a comment