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.
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
/etc/systemd/system/<name>.service. Lowercase letters, digits, dashes and underscores.systemctl status and systemctl list-units.3 · Startup & restart behaviour
4 · Environment
5 · Security hardening
6 · Your unit file
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
| Value | Restarts when… | Use it for |
|---|---|---|
no | never | one-shot-ish jobs you start by hand (usually a better fit for a timer unit) |
on-failure | non-zero exit, signal crash, timeout or watchdog | most services — the sane default. A clean systemctl stop (exit 0) won't respawn it. |
always | any exit, including a clean stop | processes that must never be down; note systemctl stop will still win until the next start |
on-abnormal | signal, 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)
- "status=203/EXEC" — systemd can't execute the command. Almost always a relative path or a missing binary.
ExecStartmust be an absolute path, and systemd never consults$PATH. This generator checks it for you. - Python: "no such file" for your venv python. systemd doesn't run your shell, so
source venv/bin/activatenever happens. PointExecStartstraight at the venv interpreter:/home/pi/.venv/bin/python /home/pi/app/server.py. - It starts, then dies — check the journal.
journalctl -u my-service -n 50 --no-pagershows 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 (addnetwork-online.target). - 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. - You edited the unit and nothing changed. You forgot
sudo systemctl daemon-reload. After editing: reload, thensystemctl 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, andMemoryDenyWriteExecute(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, dropMemoryDenyWriteExecutefirst.
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.
Suggested
Luke Keam | techgeek.biz