Coldwast
systemd service file generator
Fill in the command that starts your app and get a .service unit you can drop into /etc/systemd/system/, with the install commands to match. The generator also reads what you typed and flags the mistakes that make a unit fail on the first start. Everything runs in your browser.

Your unit file
What the generator noticed
Install it
The five lines that decide whether it works
ExecStart is not a shell. systemd splits the line into arguments itself and runs the program directly. A pipe, a > redirection or && is handed to your program as a literal argument. systemd also expands $VAR on its own, so a literal dollar sign has to be written $$. When the generator sees shell syntax it wraps the command in /bin/sh -c and does that escaping for you.
Use an absolute path. A path such as ./server is rejected outright. A bare name such as node is accepted, but systemd searches only its own fixed directories, not the PATH of your login shell, so a Node installed with nvm or a tool in ~/.local/bin is not found.
Type=simple or exec for almost everything. A web server, a worker or a bot that stays in the foreground is simple. exec differs in one useful way: the start is only reported as successful once the binary has actually been executed, so a wrong path fails systemctl start instead of failing silently a moment later. forking is for old daemons that put themselves in the background, and notify only works if the program calls sd_notify.
Restart=on-failure with a limit. on-failure restarts after a crash or a non-zero exit, but not after a clean stop; always restarts even then. By default systemd allows 5 starts in 10 seconds, and with a 5-second delay a crashing app never reaches that limit, so it loops forever. StartLimitIntervalSec=300 with StartLimitBurst=5 makes it give up and leave a clear failed state.
Wants= and After= together. After=network-online.target on its own only sets an order; it does not pull the target in. Without Wants=network-online.target your service can still start before the network is up, which is the classic "works after a manual restart, fails at boot" bug.
The sandbox, and what was left out on purpose
With ProtectSystem=strict the whole filesystem is read-only for the service, except the paths you list in ReadWritePaths=. PrivateTmp gives it its own /tmp, NoNewPrivileges stops it from gaining rights through setuid binaries, and the kernel and cgroup settings stop it from changing system-wide state. Run systemd-analyze security yourapp.service before and after: it prints an exposure score and lists each setting still open.
Two common recommendations are deliberately absent. MemoryDenyWriteExecute=true breaks just-in-time compilers, which means Node.js, the JVM and PyPy. PrivateDevices=true is safe for a web app but breaks anything that talks to a GPU or a serial port. Add them yourself if you know your program does not need them.

When it will not start: the exit codes
systemctl status yourapp shows a status like status=203/EXEC. The number tells you which step failed before your program even ran:
| Status | Meaning | Usual fix |
|---|---|---|
203/EXEC | The program could not be executed | Wrong path, missing execute bit, or a script without a shebang line |
200/CHDIR | WorkingDirectory is missing or not accessible | Create it, or check that ProtectHome is not hiding it |
217/USER | The User= account does not exist | Run the useradd line above |
226/NAMESPACE | The sandbox could not be set up | A path in ReadWritePaths does not exist; create it or prefix it with - |
Any other failure is your own program exiting, and its output is in journalctl -u yourapp -b.
Changing it later
After editing the file in /etc/systemd/system/, run sudo systemctl daemon-reload before restarting, or systemd keeps using the old version. For a unit installed by a package, do not edit it in place: sudo systemctl edit yourapp creates an override file that survives upgrades and reloads systemd for you.
Related: what a VPS is, putting a reverse proxy in front of the app, SSH basics, and for scheduled jobs the cron expression explainer.