systemd Unit Builder
Create safer service units[Unit] Description=Otho worker After=network-online.target Wants=network-online.target [Service] Type=simple User=otho WorkingDirectory=/opt/otho ExecStart=/opt/otho/bin/worker Restart=on-failure RestartSec=5s NoNewPrivileges=true PrivateTmp=true ProtectSystem=strict ProtectHome=true [Install] WantedBy=multi-user.target
Introduction
The systemd Unit Builder generates a service unit (a .service file) with hardening options that are easy to forget: a non-root user, NoNewPrivileges, PrivateTmp, ProtectSystem=strict and ProtectHome.
systemd lets you describe what a service may touch. The generated unit is a defensible starting point for a background worker, an agent or any long-running process.
Objective
- Generate a syntactically correct .service unit from name, command, user and working directory.
- Apply baseline hardening: dedicated user, restart on failure, no new privileges, private /tmp and read-only system paths.
- Keep the unit readable so an administrator can adjust it before deploying.
Inputs
- Description (free text).
- ExecStart — the absolute path to the executable.
- Service user — the unprivileged account the service runs as.
- Working directory (optional).
How it works
The tool assembles three sections. [Unit] declares the description and orders the service after network-online.target. [Service] sets Type=simple, the user and working directory, ExecStart, and Restart=on-failure with a 5-second delay.
Hardening directives follow: NoNewPrivileges=true blocks gaining new privileges through setuid binaries; PrivateTmp=true gives the service its own /tmp; ProtectSystem=strict makes the whole filesystem read-only except explicitly allowed paths; ProtectHome=true hides /home, /root and /run/user.
[Install] declares WantedBy=multi-user.target so the unit can be enabled with systemctl enable.
Testable example
Try it — the analysis runs locally in your browser.
Example
Description: Otho worker ExecStart: /opt/otho/bin/worker User: otho Directory: /opt/otho
Expected output
[Unit] Description=Otho worker After=network-online.target Wants=network-online.target [Service] Type=simple User=otho WorkingDirectory=/opt/otho ExecStart=/opt/otho/bin/worker Restart=on-failure RestartSec=5s NoNewPrivileges=true PrivateTmp=true ProtectSystem=strict ProtectHome=true [Install] WantedBy=multi-user.target
Reading the output
- Save the output as /etc/systemd/system/<name>.service, run sudo systemctl daemon-reload, then sudo systemctl start <name>.
- If the service needs to write to system paths, ProtectSystem=strict will block it — add ReadWritePaths or move the writable data under the working directory.
- The unit is a template: adjust TimeoutStartSec, resource limits (MemoryMax, CPUQuota) and capabilities (CapabilityBoundingSet) to match the workload.
Risks
- Running a service as root with network access is the highest-risk default; the generated unit avoids it, but check your ExecStart binary is owned by root.
- ProtectSystem=strict can break services that write outside their working directory — test before production.
- Restart=on-failure does not protect against crashes that exit 0; consider Restart=always for critical workers.
Limitations
- The tool generates the file; it cannot test that your executable starts or that its permissions are correct.
- It does not generate socket activation, timers or complex dependency graphs.
- Hardening is baseline — a real review should consider capabilities, seccomp and network namespaces.
Official references
FAQ
Why a dedicated user for every service?
A dedicated unprivileged user limits what a compromised service can do: it cannot read other users' files or write outside its own paths. Root should be reserved for the few services that truly need it.
What does NoNewPrivileges=true do?
It prevents the service and its children from gaining privileges through setuid binaries or similar mechanisms, even if such binaries exist on the system.
What is ProtectSystem=strict?
It makes the entire filesystem read-only for the service, except for explicitly allowed paths. It is the strongest ProtectSystem level and pairs well with services that only write to their working directory or /tmp.