OTOthoTools

systemd Unit Builder

Create safer service units
Result.service
[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.