Skip to content
ohsudo, Home

systemd Unit Generator

Service and timer units with restart policies, users and hardening options.

● Runs 100% in your browser — nothing you type leaves this page.

Service

Full path to the program, then its arguments.

Empty = root. A dedicated user is safer.

One KEY=value per line.

Schedule
Running
Limits

Space separated, e.g. postgresql.service redis-server.service

# Generated by OhSudo — https://ohsudo.com/tools/systemd-unit-generator/
[Unit]
Description=My application
# Start after these units (and pull them in); network-online waits for a configured network
Wants=network-online.target
After=network-online.target
[Service]
# How systemd knows the service has started
Type=simple
# Run as this user, not root
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
# The program and its arguments (full path; this is not a shell)
ExecStart=/opt/myapp/bin/server --port 8080
# Environment variables for the program
Environment=NODE_ENV=production
# Restart automatically, after a pause
Restart=on-failure
RestartSec=5
# Basic hardening: no privilege escalation, a private /tmp, /usr /boot /etc read-only
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=full
# No access to /home, /root and /run/user
ProtectHome=yes
# Start at boot (systemctl enable)
[Install]
WantedBy=multi-user.target

Install it

$ sudo useradd --system --no-create-home --shell /usr/sbin/nologin myapp
$ sudo nano /etc/systemd/system/myapp.service
$ sudo systemctl daemon-reload
$ sudo systemctl enable --now myapp.service
$ systemctl status myapp.service
$ journalctl -u myapp.service -f

Paste each file, save, then reload systemd and start it. Logs: journalctl -u <name>.

The address bar updates as you go, so you can bookmark or share the result.

How to use it

  1. Give the service a name and the command to run — with the full path of the program (/usr/bin/node, not node).
  2. Choose the user it runs as. A dedicated user (the install steps create it) is safer than root.
  3. Decide how it runs: all the time, restarted if it crashes — or on a schedule with a .timer, the systemd alternative to cron.
  4. Copy the file(s) to /etc/systemd/system/, then run the commands under the result.

What you get

Common commands

Terminal window
sudo systemctl daemon-reload # after creating or editing a unit file
sudo systemctl enable --now myapp # start now and at boot
systemctl status myapp # state and last log lines
journalctl -u myapp -f # follow the logs
sudo systemctl edit myapp # change settings in a drop-in, without touching the file
systemctl list-timers # when every timer runs next
systemd-analyze calendar "Mon..Fri 08:30" # check an OnCalendar expression
systemctl list-unit-files 'myapp*' # is the unit installed, under which name

Warning

Secrets don’t belong in the unit file. Every user on the machine can read it (systemctl cat, systemctl show). Put passwords and tokens in an EnvironmentFile owned by root with mode 600.

FAQ

Why does my command work in the shell but not in ExecStart?

ExecStart= is not a shell. There is no PATH from your profile, no cd, no &&, | or > redirections, no ~. Use full paths and WorkingDirectory=; if you really need shell syntax, run it through /bin/sh -c '…' — or better, put it in a script. If the service fails to start, see Job for … failed because the control process exited with error code.

simple, exec, notify or oneshot?

simple for most apps that run in the foreground. exec is the same, but systemctl start already fails if the program can’t even be started (wrong path, permissions). notify only if the app tells systemd it’s ready (many daemons can: nginx, PostgreSQL, some app servers). oneshot for scripts that do something and exit — that’s what timers run.

Timer or cron?

Both work. A timer logs every run in the journal, can catch up after the machine was off, doesn’t run twice if the previous run is still going, and uses the same limits and users as services. Cron is simpler for a quick one-liner — see the Cron Builder and Cron job not running.

Which commands do I need to manage the service?

They’re in the systemctl and journalctl Cheat Sheet.

Where can I practice managing services?

In the lesson Services: systemctl and journalctl.

Articles

Learn more

Laravel
Networking

Nginx for Laravel with PHP-FPM: production config, permissions and queues

Deploy Laravel on Nginx and PHP-FPM: a complete HTTPS config pointing at public, permissions for storage, the optimize and queue steps, and the errors you may see.

Intermediate · 3 min read

Gunicorn
Networking

Nginx and Gunicorn for Django: production config, static files and HTTPS

Deploy Django with Gunicorn behind Nginx: a complete HTTPS config, a socket with systemd, static and media files, the settings you need behind a proxy, and common errors.

Intermediate · 3 min read

FastAPI
Networking

Nginx reverse proxy for FastAPI and Uvicorn, with HTTPS and WebSockets

Run FastAPI behind Nginx: a complete HTTPS config, Uvicorn in a systemd service, proxy headers for the real client IP, a subpath, WebSockets, and common errors.

Intermediate · 3 min read

Next.js
Networking

Nginx for Next.js: a production config with HTTPS, caching and standalone builds

Run Next.js behind Nginx: a complete config with HTTPS and long caching for _next/static, the standalone build, a systemd service, and the errors people run into.

Intermediate · 3 min read

reverse proxy
Networking

Nginx reverse proxy for Node.js and Express, with HTTPS and WebSockets

Set up Nginx in front of a Node.js or Express app: a complete config with HTTPS and WebSockets, a systemd service, trust proxy for real client IPs, and common errors.

Beginner · 3 min read

cron job not running
Linux

Cron job not running? Find the reason in the log, PATH and environment

Fix a cron job that does not run or fails silently: check the log, PATH and environment, % signs, /etc/cron.d rules, permissions and lost output.

Beginner · 4 min read

Job for nginx.service failed because the control process exited with error code.
Linux

"Job for nginx.service failed because the control process exited with error code": how to find the cause

Fix "Job for X.service failed because the control process exited with error code": read systemctl status and journalctl, test the config, and fix the usual causes.

Beginner · 5 min read

Killed
Linux

Linux process "Killed" with no error: find the OOM killer and stop it happening again

A process that just prints "Killed" (exit code 137) was usually stopped by the Linux OOM killer. Find it in the kernel log, see what used the memory, and fix it.

Intermediate · 4 min read

Too many open files
Linux

"Too many open files" on Linux: raise the right limit, or find the leak

Fix "Too many open files" (EMFILE): check a process's real limit in /proc, raise LimitNOFILE for systemd services, and tell a low limit from a file descriptor leak.

Intermediate · 3 min read