Systemd Service Generator

Turn a Python script, Node.js app or any background command into a proper .service unit file that systemd can run — at boot, on demand and with automatic restarts. Pick your runtime, fill in your paths, and the generator builds the unit live: restart policies with start limits, environment variables, tiered security hardening (each directive explained), and a plain-English check of the mistakes that actually break units — relative paths, restart storms, shell syntax systemd will never interpret. When it looks right, copy the one-command install or download the file.

🔒 100% private — nothing is uploaded or stored. The unit file, paths and environment variables are generated entirely in your browser. Your server layout and service configuration are never sent to a server, logged or saved — so you can safely build units for production machines you would not want to paste into a web form.

1 · What are you running?

Pick a runtime to get the right ExecStart shape and its top pitfall. You can change anything afterwards.

2 · Unit basics

Becomes /etc/systemd/system/<name>.service. Lowercase letters, digits, dashes and underscores.
Shown by systemctl status and systemctl list-units.
Leave empty to run as root — the generator will flag that. A dedicated user is safer.
Where the service starts. Relative paths in your code resolve against this.

3 · Startup & restart behaviour

Pause between restarts. 100–200 for ms.
Max starts within the window below, then systemd gives up (bad). Leave empty to allow unlimited.
The window (seconds) the burst is counted over.

4 · Environment

Only lines with a key are written. Values containing spaces or quotes are wrapped automatically.
A file of KEY=value lines — the right place for secrets (chmod 600, owned by the service user). Leave empty if unused.

5 · Security hardening

6 · Your unit file

ready

Install it (one command)

Paste on the target machine. It writes the unit, reloads systemd and enables + starts the service.

Useful day-to-day commands

How a systemd unit file works

A unit file is a plain-text description of one service, split into sections. [Unit] holds metadata and ordering (what the service depends on, start limits). [Service] describes how to run it: the command (ExecStart), who runs it (User), where it starts (WorkingDirectory), its environment, and what to do when it dies (Restart). [Install] says how to enable it — WantedBy=multi-user.target means "start me at boot on a normal multi-user system".

Units live in /etc/systemd/system/ (your own, highest priority) or /usr/lib/systemd/system/ (shipped by packages). After you create or edit one you must run sudo systemctl daemon-reload so systemd re-reads its configuration — this is the step most first-timers forget. systemctl enable creates a symlink that puts the unit in the boot sequence; start runs it now. --now combines both.

Everything your service prints to stdout/stderr goes to the journal (that's what StandardOutput=journal does) — read it with journalctl -u my-service. No log file needed unless you want one.

Restart policies, explained

ValueRestarts when…Use it for
noneverone-shot-ish jobs you start by hand (usually a better fit for a timer unit)
on-failurenon-zero exit, signal crash, timeout or watchdogmost services — the sane default. A clean systemctl stop (exit 0) won't respawn it.
alwaysany exit, including a clean stopprocesses that must never be down; note systemctl stop will still win until the next start
on-abnormalsignal, timeout or watchdog only (not plain non-zero exits)when a non-zero exit is a normal "done" signal

RestartSec is the pause between attempts. On its own, an always-restarting service that crashes on startup will spin forever — start limits stop that: StartLimitBurst=5 + StartLimitIntervalSec=60 means "more than 5 starts in 60 seconds and I give up, marking the unit failed". That's why this generator writes both by default.

The five reasons services fail to start (and the fix)

  1. "status=203/EXEC" — systemd can't execute the command. Almost always a relative path or a missing binary. ExecStart must be an absolute path, and systemd never consults $PATH. This generator checks it for you.
  2. Python: "no such file" for your venv python. systemd doesn't run your shell, so source venv/bin/activate never happens. Point ExecStart straight at the venv interpreter: /home/pi/.venv/bin/python /home/pi/app/server.py.
  3. It starts, then dies — check the journal. journalctl -u my-service -n 50 --no-pager shows the crash. The usual suspects: port already in use, a config file it can't read (wrong user/permissions), or a database it can't reach yet (add network-online.target).
  4. It runs as root and can't write where you expect. Or the opposite: it runs as your user but needs a port below 1024. Pick User= deliberately; for a non-root service that must bind 80/443, put nginx in front instead.
  5. You edited the unit and nothing changed. You forgot sudo systemctl daemon-reload. After editing: reload, then systemctl restart my-service.

Security hardening, in plain English

systemd can sandbox a service so that even if it's compromised, the attacker has far less room to move. The tiers this generator offers:

  • Basic: NoNewPrivileges (no setuid escalation), PrivateTmp (own /tmp), ProtectSystem=full (/usr and /boot read-only), plus kernel-tunable/module/control-group protections. These rarely break a normal app.
  • Strict: everything in Basic, plus no device access (PrivateDevices), /home read-only, no SUID/SGID binaries, personality locked, and MemoryDenyWriteExecute (blocks writable+executable memory — thwarts many code-injection tricks, but can break runtimes that JIT-compile: JVM, some Python/Node extensions). If the service dies on startup under Strict, drop MemoryDenyWriteExecute first.

Hardening is additive — start at Basic, confirm the service works, then try Strict and watch journalctl for what it complains about. That's how you find the minimum sandbox your app actually tolerates.


Luke Keam
Spot a bug or want a custom utility built? I'd love to hear from you. Get in touch
Luke Keam | techgeek.biz