A timer is a systemd unit with the .timer extension. It runs
nothing itself: at the set time it starts a service, by default the one with
the same name (clean.timer → clean.service). So a job is a pair of
files in /etc/systemd/system: the service says what to do, the timer says
when.
[Unit]
Description=Cleanup old exports
[Service]
Type=oneshot
ExecStart=/usr/local/bin/clean.sh
[Unit]
Description=Run cleanup daily
[Timer]
OnCalendar=*-*-* 03:30
Persistent=true
[Install]
WantedBy=timers.target
Type=oneshot is a service that runs a command and finishes; while the script
runs, the service is activating. You enable the timer: the service has no
[Install] section, and systemctl enable clean.service only prints a long
warning. An enabled timer shows up in the list with its next run time:
student@ubuntu:~$ systemctl list-timers clean.timer --no-pager
NEXT LEFT LAST PASSED UNIT ACTIVATES
Wed 2026-10-21 03:30:00 UTC 17h - - clean.timer clean.service
|
cron |
systemd timer |
| job output |
mail, or lost |
the service journal, journalctl -u |
| the machine was off |
the run is skipped |
Persistent=true runs it after boot |
| the previous run is still going |
a second one starts |
the same unit never starts twice |
| run outside the schedule |
paste the command into a terminal |
systemctl start, same conditions |
The OnCalendar schedule
The full form is DayOfWeek Year-Month-Day Hour:Minute:Second. Any part can be
*, a comma-separated list, a range with .. or a step with /.
| Entry |
When |
hourly |
every hour at :00 |
daily |
every day at 00:00 |
weekly |
Mondays at 00:00 |
*-*-* 03:30 |
every day at 03:30 |
Mon..Fri 03:00 |
weekdays at 03:00 |
Sat,Sun 10:00 |
weekends at 10:00 |
*-*-01 04:00 |
the first day of every month |
*:0/15 |
every 15 minutes |
03:00 Europe/Moscow |
03:00 Moscow time |
Without a zone, time follows the machine's clock (timedatectl).
systemd-analyze calendar checks an entry and shows its next run:
student@ubuntu:~$ systemd-analyze calendar 'Mon..Fri 03:00'
Original form: Mon..Fri 03:00
Normalized form: Mon..Fri *-*-* 03:00:00
Next elapse: Wed 2026-10-21 03:00:00 UTC
From now: 16h left
Besides the calendar, a timer can count from events: OnBootSec=5min is five
minutes after boot, OnUnitActiveSec=1h is an hour after the service last
started.
The service environment
A service starts outside your terminal and gets an environment of its own:
student@ubuntu:~$ sudo systemd-run --pipe --wait --quiet \
sh -c 'echo "PATH=$PATH"; echo "HOME=$HOME"; pwd; id -un'
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
HOME=
/
root
- the working directory is
/, so a relative path in the script starts at the
root; WorkingDirectory= sets another one;
PATH is short: ~/bin and whatever ~/.bashrc adds are missing;
- variables from
~/.bashrc and from crontab lines are missing — set them with
Environment=NAME=value or EnvironmentFile=;
- the user is root with an empty
HOME; with User=student the service runs
as student and gets HOME=/home/student;
~/.profile and ~/.bashrc are not read, and umask is 0022.
The journal
Everything the script prints to stdout and stderr lands in the service
journal. The -o cat option drops the time, host name and
clean.sh[PID] from each line:
student@ubuntu:~$ sudo journalctl -u clean.service -o cat
Starting clean.service - Cleanup old exports...
cleanup started
clean.service: Deactivated successfully.
Finished clean.service - Cleanup old exports.