All free tools Generated in your browser

Systemd Service Generator

Describe your long-running process and get a complete systemd unit file with restart, logging, and hardening directives.

Generate a systemd service unit

.service
Preset and identity
Choosing a preset refills every field below with sensible defaults for that workload. You can edit anything afterwards.
Letters, digits, and _ . @ - only. The tool adds the .service suffix for you.
One short line. It is what systemctl status and systemctl list-units print beside the unit.
An http:// or https:// link to your runbook. Shown by systemctl status when the unit fails.
Ordering and dependencies
Comma separated unit names. Ordering only: it says when to start, not that the other unit must exist.
Soft dependency. Systemd tries to start these units too, but your service still starts if they fail.
Hard dependency. If a required unit fails or stops, your service is stopped as well. Pair it with After= for correct ordering.
Process and commands
Most application servers that stay in the foreground use simple or exec. Use notify only if the program calls sd_notify.
The first token must be an absolute path. Systemd runs the command directly rather than through a shell, so it never searches PATH and node or python on their own will fail. Find the full path with command -v node.
Runs before the main command and must succeed, unless you prefix it with - to ignore failures.
Runs after the main command has been started. Useful for warm-up or notification steps.
What systemctl reload should run. The $MAINPID variable is expanded by systemd.
Leave this empty unless a clean shutdown needs a command. Without it systemd sends SIGTERM to the main process, which is correct for most services.
Absolute path the process starts in. Set it whenever your program 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.
Environment
One KEY=VALUE pair per line. Keys must start with a letter or underscore. Each pair is written as a quoted Environment= line. Do not put secrets here: the unit file is world readable.
Absolute path to a file of KEY=VALUE lines. Prefix it with - to let the service start when the file is missing. This is the right place for secrets, with permissions such as 0640.
Restart behaviour and timeouts
Use on-failure for most services and always for workers that should never stay down.
Delay before a restart. Plain seconds or a time span such as 5s, 2min.
How long startup may take before systemd treats it as failed. Use infinity to disable.
Grace period after SIGTERM before systemd sends SIGKILL.
How many starts are allowed inside the interval before systemd gives up. Written into [Unit].
The window the burst count applies to. Set 0 to switch rate limiting off.
Logging
Keep journal unless you need a plain log file. Read it with journalctl -u.
inherit sends errors to the same place as standard output.
The tag journal entries are labelled with. Handy when several units write to one log file.
Hardening
Absolute paths, separated by spaces or commas, that stay writable under ProtectSystem=strict. Include every directory the service writes to, including log and upload directories.
Resource limits
Maximum open file descriptors. Raise it for busy network services.
A hard cgroup limit such as 512M, 2G, or 40%. Exceeding it triggers the kernel out-of-memory killer.
A percentage. 100% is one full core, so 200% allows two cores.
Install
Use multi-user.target for a system unit in /etc/systemd/system. A user unit lives in ~/.config/systemd/user, is managed with systemctl --user, and should use default.target instead. User units stop at logout unless you run loginctl enable-linger.

Check the unit before you rely on it. A unit that fails to start can leave an application offline after a reboot. Never overwrite a unit shipped by your distribution; create a new name or a drop-in under /etc/systemd/system/<name>.service.d/ instead.

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

From form to running service

You need the absolute path of the command you want to keep running, and the account it should run as.

  1. 1

    Pick a preset and name the unit

    Select the workload closest to yours, then set a unit name such as node-app. The name becomes the filename and the handle you use in every systemctl command.

  2. 2

    Set the command and the account

    Enter the full ExecStart path, for example /usr/bin/node /srv/app/server.js, then the working directory, user, and group. Run command -v node on the server if you are unsure of the path.

  3. 3

    Adjust restart, logging, and hardening

    Choose a restart policy, decide whether output goes to the journal or a file, and keep the hardening options that suit your application. Add every writable directory to ReadWritePaths when ProtectSystem=strict is on.

  4. 4

    Install and test it

    Save the file as /etc/systemd/system/<name>.service, then run sudo systemctl daemon-reload, sudo systemctl enable --now <name>, systemctl status <name>, and follow the log with journalctl -u <name> -f. Reboot once to confirm it comes back.

What the generated unit contains

A systemd service file has three sections. [Unit] holds the description, documentation link, ordering, and the start rate limit. [Service] holds the type, the commands, the account, the environment, restart and timeout settings, logging, hardening, and resource limits. [Install] holds the target that pulls the unit in at boot when you run systemctl enable.

Only the directives you fill in are written. Empty fields and unchecked boxes are left out completely, so the file stays short enough to read and review by hand.

  • Ordering with After, Wants, and Requires
  • Service type, commands, account, and working directory
  • Quoted Environment lines and an EnvironmentFile path
  • Restart policy, timeouts, and start rate limiting
  • Journal or file logging with an optional syslog tag
  • Sandboxing directives and cgroup resource limits

What it cannot know about your server

The generator has no view of your machine. It cannot confirm that the binary exists, that the user account has been created, that the working directory is present, or that the service user can read the files it needs. Those are the three most common reasons a new unit fails with status=203/EXEC or status=200/CHDIR.

Hardening is the other area that needs testing. ProtectSystem=strict makes the entire filesystem read-only apart from the paths you list, so an application that writes a PID file, a cache, a socket, or an upload directory will fail until that directory appears in ReadWritePaths. Turn the options on one at a time if a service stops working after you add them.

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

Test a new unit before you depend on it

Run systemd-analyze verify /etc/systemd/system/<name>.service to catch syntax problems, start the unit manually, and watch journalctl -u <name> -f before you enable it at boot. Do not replace a unit that your distribution ships; use a different name or a drop-in file so package updates do not overwrite your work. To roll back, run sudo systemctl disable --now <name>, delete the file, and run sudo systemctl daemon-reload.

Systemd Service Generator FAQ

Systemd executes the command itself instead of handing it to a shell, so there is no PATH lookup and no shell expansion. A bare node server.js fails with status=203/EXEC. Run command -v node or which python3 on the server and paste the full path the shell reports. If you genuinely need shell features such as pipes or globbing, set ExecStart=/bin/sh -c '...', though a small wrapper script is usually easier to read.
Use simple or exec when the program stays in the foreground, which covers most Node.js, Python, Go, and Java servers. Use forking only when the program deliberately daemonises and the parent exits. Use oneshot for a script that runs to completion. Use notify only when the program calls sd_notify to announce readiness; if it does not, systemd waits for the start timeout and then marks the unit failed.
Almost always the cause is ProtectSystem=strict blocking a write. Add every directory the application writes to, including logs, caches, PID files, sockets, and uploads, to ReadWritePaths. If the process is a Node.js, Java, or .NET application, also make sure MemoryDenyWriteExecute is off, because those runtimes need memory that is both writable and executable. Switch options off one at a time and check the journal after each change.
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. StartLimitBurst and StartLimitIntervalSec are written into [Unit], which is correct from systemd 229 onwards; on much older systems those two keys lived in [Service] under different names. Check your version with systemctl --version.
No. Unit files in /etc/systemd/system are world readable, and systemctl show prints the environment to any user. Put secrets in a separate file such as /etc/myapp/app.env, set it to chown root:appuser and chmod 0640, and reference it with EnvironmentFile. Keep the Environment field for non-sensitive values such as NODE_ENV or PORT.
Run sudo systemctl disable --now <name> to stop it and remove the boot symlink, delete /etc/systemd/system/<name>.service, then run sudo systemctl daemon-reload and sudo systemctl reset-failed <name>. Because the generator only writes a new file, nothing else on the system is modified and nothing else needs undoing.
The tool refuses control characters and line breaks in any value, because a stray newline would create a second directive line inside the generated file. It also checks that the unit name uses only A-Z a-z 0-9 _ . @ -, that dependency entries end in a real unit suffix such as .service or .target, that paths are absolute, that environment keys match [A-Za-z_][A-Za-z0-9_]*, and that CPUQuota is a percentage. The field message names the exact problem.
It does not connect to your server, install anything, or check that the paths and accounts you enter exist. It also leaves out directives it cannot set safely without knowing your system, such as socket activation, capability bounding sets, system call filters, and cgroup device rules. Add those by hand once the basic unit runs, and validate the file with systemd-analyze verify and systemd-analyze security <name>.
No. The page loads one small script that validates the fields and assembles the text locally. There is no upload, no analytics capture of the form, and no storage of what you enter. Closing the tab discards everything.