How to Write a systemd Service Unit File
A systemd unit file has three sections and about eight directives that matter for production. This guide walks through each one - what it does, why it is there, and how to validate the file without touching a running system.
The three sections every unit needs
A service unit lives in a .service file. It always has three sections:
[Unit] for metadata and ordering, [Service] for how to run the process,
and [Install] for when to start it on boot.
Here is a complete, production-ready example for a Python Flask API:
[Unit]
Description=Production Flask API
After=network.target
[Service]
Type=simple
User=appuser
ExecStart=/opt/venv/bin/python /opt/api/app.py
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
[Unit] - metadata and ordering
Description- a human-readable label shown insystemctl statusoutput.After=network.target- tells systemd to start this unit only after the network stack is up. It is an ordering hint, not a hard dependency; your service starts last in the normal boot sequence without a tight dep chain.
[Service] - the process spec
Type=simple- the process systemd starts is the service. Do not useType=forkingunless the binary double-forks into the background on its own (old-school daemons do this; gunicorn/uvicorn/python do not).User=appuser- run as a non-root account. If the user does not exist, systemd will fail to start the unit. Create it withuseradd --system appuser.ExecStart- the full path to the binary and its arguments. Always use absolute paths; relative paths break when the working directory changes. For virtualenv Python, point at the venv's interpreter:/opt/venv/bin/python.Restart=always- restart the process on any exit (crash, OOM kill, non-zero exit code). Combine withRestartSec=5to add a 5-second cooling-off before the next attempt so a tight crash loop does not peg CPU.
[Install] - boot integration
WantedBy=multi-user.target is the correct target for almost every server
service. It means "enable this unit when the system boots into the normal
multi-user runlevel." After writing the file, enable and start it:
systemctl enable --now api.service
--now enables (creates the symlink) and starts the service in one shot.
Validate before you deploy
systemd-analyze verify checks syntax and required fields without starting
the service or needing PID 1 running:
systemd-analyze verify api.service
A clean file exits with no output. Any warning or error is printed to stderr.
Fix them before copying the file to /etc/systemd/system/.
Common production additions
Once the basics pass, harden the unit with resource and security directives:
[Service]
MemoryMax=512M
CPUQuota=50%
NoNewPrivileges=true
EnvironmentFile=/etc/api.env
EnvironmentFile keeps secrets out of the unit file itself - write
KEY=VALUE pairs to /etc/api.env (mode 0600, owned by root) and
systemd injects them as environment variables at start time.
Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.
Try it in a workspace →What you'll practice
- Writing the three required systemd unit sections with correct directives
- Choosing the right Type= and wiring ExecStart to a virtualenv Python binary
- Validating a unit file with systemd-analyze verify before deploying
FAQ
What are the three sections of a systemd service unit file?
[Unit] holds the description and ordering (e.g. After=network.target). [Service] defines how to run the process - ExecStart, Type, User, Restart. [Install] sets when it starts on boot (WantedBy=multi-user.target).
How do I validate a systemd unit file without restarting the system?
Run systemd-analyze verify <unit-file>. It checks syntax and required fields locally - no running systemd PID 1 needed. A clean file produces no output; errors and warnings are printed to stderr.
What is the difference between Type=simple and Type=forking?
Use Type=simple when the process systemd launches stays in the foreground - Python, Node.js, gunicorn, uvicorn all do this. Use Type=forking only for old-style daemons that double-fork into the background on startup.
Keep learning
Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.
Open the workspace →