All free tools Generated in your browser

Systemd Timer Generator

Describe a scheduled job and get both files systemd needs: a .timer unit and the Type=oneshot .service it runs.

Generate a timer and its service

.timer + .service
Identity
Letters, digits, and _ . @ - only. Both files share this name, so you get nightly-backup.timer and nightly-backup.service. Do not type a suffix.
One short line. The service uses it as written and the timer uses the same line with the word timer added.
The job
The first token must be an absolute path. Systemd runs the command directly rather than through a shell, so it never searches PATH. Find the full path with command -v.
Absolute path the job starts in. Set it whenever your script reads relative paths such as ./config.json.
An existing unprivileged account. Leave empty to run as root.
Defaults to the user's primary group when left empty.
Absolute path to a file of KEY=VALUE lines. Prefix it with - to let the job run when the file is missing. This is the right place for secrets, with permissions such as 0640.
One KEY=VALUE pair per line. Keys must start with a letter or underscore. Each pair becomes a quoted Environment= line. Do not put secrets here: unit files are world readable.
Schedule
Calendar mode writes OnCalendar=, which is the closest equivalent to a cron entry. Boot mode writes OnBootSec= and OnUnitActiveSec= for jobs that should simply repeat at an interval.
The preset decides which fields below apply. Check the result on the server with systemd-analyze calendar '<expression>'.
24-hour HH:MM, for example 03:30. Seconds are always written as 00. Times follow the server clock and time zone.
Spreads the start over a random window, such as 5min. Useful when several machines would otherwise hit the same backup target at once.
How far systemd may move the start to group wake-ups together. The default is 1min. Use 1s when the job really must run on the minute.
Service options and hardening
Absolute paths, separated by spaces or commas, that stay writable under ProtectSystem=strict. Include every directory the job writes to, such as backup targets, caches, and log directories.

The timer is what you enable, not the service. The generated service has no [Install] section on purpose, so systemctl enable on it will fail. Enable <name>.timer instead. Never overwrite a unit shipped by your distribution; choose a different name or add a drop-in under /etc/systemd/system/<name>.timer.d/.

We do not store your data. Every value is validated and both unit files are assembled entirely in your browser. Nothing you type is sent to VPSLake.

From form to scheduled job

You need the absolute path of the command you want to run on a schedule, the account it should run as, and the time it should run.

  1. 1

    Name the job

    Enter a base name such as nightly-backup and a short description. The name is shared by both files and is the handle you use in every systemctl command.

  2. 2

    Describe the command

    Enter the full ExecStart path, for example /usr/local/bin/backup.sh --daily, then the working directory, user, and group. Add environment values or an EnvironmentFile if the script needs them.

  3. 3

    Choose the schedule

    Pick a calendar preset and set the time, or switch to boot mode and give an interval. Turn Persistent=true on if a run missed while the machine was off should be caught up at the next boot.

  4. 4

    Install both files and test

    Save them as /etc/systemd/system/<name>.timer and /etc/systemd/system/<name>.service, then run sudo systemctl daemon-reload and sudo systemctl enable --now <name>.timer. Check systemctl list-timers <name>* for the next run, force one run with sudo systemctl start <name>.service, and read its output with journalctl -u <name>.service.

Why a scheduled job needs two files

A systemd timer never contains the command. The .timer unit holds only the schedule, and it activates a service of the same base name when that schedule fires. The work lives in the .service unit, which uses Type=oneshot because the command runs to completion instead of staying in the foreground.

That split is why the timer carries [Install] with WantedBy=timers.target and the service carries no [Install] section at all. You enable the timer; the timer starts the service. Running systemctl enable on the service would be wrong, and without an [Install] section it simply fails with a clear message rather than creating a job that runs at every boot.

  • A .timer unit with OnCalendar or OnBootSec and OnUnitActiveSec
  • Persistent, RandomizedDelaySec, and AccuracySec when you set them
  • WantedBy=timers.target so the timer starts at boot
  • A Type=oneshot service with the command, account, and working directory
  • Quoted Environment lines and an optional EnvironmentFile path
  • Sandboxing directives, with no [Install] section on the service

What the generator cannot know

The tool has no view of your machine. It cannot confirm that the script exists, that it is executable, that the account has been created, or that the account may write to the directories the job needs. Those are the usual reasons a first run fails with status=203/EXEC, status=200/CHDIR, or a permission error in the journal.

Calendar expressions are also worth checking against the machine rather than against intuition. OnCalendar follows the server clock and its time zone, so a job written for 03:30 runs at 03:30 local server time, which may not be your own. Confirm the expression with systemd-analyze calendar '*-*-* 03:30:00', which prints the next elapse, and confirm the zone with timedatectl.

Treat the output as a reviewed starting point rather than an audited configuration. Read both files, adapt them, and test the job on a non-production machine before you rely on it.

Test the pair before you leave it running

Run systemd-analyze verify /etc/systemd/system/<name>.timer to catch syntax problems in both units, then run the job once by hand with sudo systemctl start <name>.service and read journalctl -u <name>.service -n 50. Only enable the timer once that manual run succeeds. To roll back, run sudo systemctl disable --now <name>.timer, delete both files from /etc/systemd/system, then run sudo systemctl daemon-reload and sudo systemctl reset-failed.

Systemd Timer Generator FAQ

The timer. Run sudo systemctl enable --now <name>.timer. The service is activated by the timer and deliberately has no [Install] section, so systemctl enable <name>.service reports that the unit has no installation configuration. Both files still have to exist in /etc/systemd/system, and sudo systemctl daemon-reload has to run after you copy them in.
It stores the last trigger time under /var/lib/systemd/timers. If the machine was off or suspended when a scheduled run was due, the job starts shortly after the next boot instead of waiting for the following scheduled time. It applies to OnCalendar timers only and has no effect on OnBootSec or OnUnitActiveSec, which are already measured from boot and from the previous run.
Run systemctl list-timers <name>* for the next and last elapse of your timer, or systemctl list-timers --all for every timer on the machine. systemctl status <name>.timer shows the trigger line as well. To test an expression without installing anything, run systemd-analyze calendar 'Mon *-*-* 04:00:00', which prints the normalised form and the next elapse.
Both work. A timer gives you journal output per run, dependency ordering, resource limits, the sandboxing directives on this page, catch-up runs through Persistent=true, and start jitter through RandomizedDelaySec. Cron is fewer files and is familiar. If the job already needs an environment file, a dedicated account, or write restrictions, a timer is usually easier to reason about. There is no need to migrate working cron entries for their own sake.
Read journalctl -u <name>.service -n 50, because the output belongs to the service and not to the timer. status=203/EXEC means the path in ExecStart does not exist or is not executable. status=200/CHDIR means the working directory is missing. A read-only filesystem error almost always means ProtectSystem=strict is on and the directory the job writes to is not listed in ReadWritePaths.
Every directive used here exists in systemd 232 and later, which covers Debian 9 and newer, Ubuntu 18.04 and newer, and current RHEL, Rocky, AlmaLinux, and Fedora releases. RandomizedDelaySec arrived in systemd 229 and ProtectSystem=strict in 232, so on anything older leave those two off. Check your version with systemctl --version.
The tool refuses control characters and line breaks in every value, because a stray newline would create a second directive line inside a unit file. It also checks that the base name uses only A-Z a-z 0-9 _ . @ -, that ExecStart begins with an absolute path, that paths are absolute, that environment keys match [A-Za-z_][A-Za-z0-9_]*, that the time is HH:MM in 24-hour form, and that a custom calendar expression uses only the characters systemd allows. The message under the field names the exact problem.
It does not connect to your server, install anything, or check that the paths and accounts you enter exist. It writes one OnCalendar line rather than several, and leaves out OnStartupSec, OnActiveSec, OnClockChange, WakeSystem, and user timers under systemctl --user. It also leaves out the deeper sandboxing directives, such as capability bounding sets and system call filters, which need knowledge of what the job actually does. Add those by hand once the basic pair runs.
No. The page loads one small script that validates the fields and assembles both files locally. There is no upload, no analytics capture of the form, and no storage of what you enter. Closing the tab discards everything.