Linux systemd Services: A Practical Guide
Write, enable, and debug systemd services on Linux. Learn unit files, dependencies, restart policies, and journalctl logs through a small hands-on example.
What you'll learn
- ✓What a systemd unit file looks like and where to put it
- ✓How to start, stop, enable, and disable services
- ✓How restart policies keep services alive
- ✓How to read service logs with journalctl
- ✓How to set environment variables and resource limits
Prerequisites
- •Comfortable with the Linux command line
Almost every modern Linux distribution boots with systemd as PID 1. If you run anything long-lived on a Linux server — a web app, a worker, a background daemon — turning it into a systemd service is how you make it boot on startup, restart on crash, log cleanly, and behave like every other piece of the system. The good news is the basics fit in one short unit file.
Where unit files live
Two directories matter. Distribution packages drop their unit files into /lib/systemd/system/. Your own services belong in /etc/systemd/system/. If a unit with the same name exists in both, the one under /etc wins, which is exactly the override behavior you want.
A unit file is plain INI. The minimum useful service unit has three sections: [Unit], [Service], [Install].
A first service
Suppose you have a Node app at /opt/myapp/server.js that you want running as user app on boot. Create /etc/systemd/system/myapp.service:
[Unit]
Description=My Node API
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=app
Group=app
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/node server.js
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production
Environment=PORT=3000
[Install]
WantedBy=multi-user.target
A few lines deserve commentary. After=network-online.target tells systemd to wait for the network. Type=simple means the process started by ExecStart is the service — no forking, no PID files. Restart=on-failure makes systemd relaunch the process if it exits non-zero. WantedBy=multi-user.target is what systemctl enable hooks into so the service starts at boot.
Loading and starting
Whenever you add or edit a unit, tell systemd to reread its files.
sudo systemctl daemon-reload
sudo systemctl start myapp
sudo systemctl enable myapp
sudo systemctl status myapp
enable creates the symlink that makes the service start on boot. status prints the current state, recent log lines, and the cgroup tree of child processes.
To stop or disable:
sudo systemctl stop myapp
sudo systemctl disable myapp
sudo systemctl restart myapp
Reading logs
systemd captures stdout and stderr of every service and feeds them into the journal, queryable with journalctl.
journalctl -u myapp # all logs for the unit
journalctl -u myapp -f # follow live
journalctl -u myapp --since "1 hour ago"
journalctl -u myapp -p err # only errors and worse
Because the journal is structured and indexed, you do not need a separate log file. If you also want plain files, your app can log to its own destination, but the journal alone is usually enough.
Environment variables
Two patterns work well. Inline with Environment= as shown above, or from a file with EnvironmentFile=.
[Service]
EnvironmentFile=/etc/myapp/env
ExecStart=/usr/bin/node server.js
Then /etc/myapp/env contains shell-style assignments:
NODE_ENV=production
PORT=3000
DATABASE_URL=postgres://user:pass@db/app
Keep that file mode 0640 and owned by the service user. It is the natural place for secrets that are not big enough to justify a vault.
Restart policies
The Restart= directive has several useful values:
no— never restart.on-failure— restart only if the process exits non-zero or crashes.on-abnormal— restart on signal, watchdog, or core dump.always— restart no matter how it exited.
Pair it with RestartSec=5 to avoid hammering when something is genuinely broken, and with StartLimitBurst= and StartLimitIntervalSec= to give up after too many failures. Without limits, a tight crash loop will burn CPU forever.
Resource limits and sandboxing
systemd has a generous toolbox for keeping a service in its lane.
[Service]
MemoryMax=512M
CPUQuota=50%
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
MemoryMax and CPUQuota push the cgroup limits. NoNewPrivileges prevents the process from gaining capabilities it did not start with. ProtectSystem=strict mounts /usr and friends read-only for this service. PrivateTmp gives the service its own /tmp. None of this requires changing your application — it is enforced from the outside.
Dependencies between services
If your app needs PostgreSQL up first and should fail to start when it cannot reach the database, use Requires= and After=:
[Unit]
Requires=postgresql.service
After=postgresql.service
Requires= is a hard dependency — if Postgres fails, your unit also stops. Wants= is a soft dependency — try to start it, but do not give up if it fails. Pick the weaker one when you can; tight coupling makes restarts brittle.
Debugging checklist
When a service refuses to start, work through it in order: systemctl status myapp for the headline, journalctl -u myapp -n 50 for the recent log, then systemd-analyze verify /etc/systemd/system/myapp.service to catch syntax errors. Nine times out of ten the answer is a wrong path in ExecStart or a permission problem on WorkingDirectory.
Once you have written one unit file, every future service follows the same shape. That consistency is the whole point of systemd.
Related articles
- Linux Linux Cron and systemd Timers: A Practical Comparison
Run scheduled jobs on Linux with cron or systemd timers. How they differ, when to choose each, and recipes that survive reboots and log rotations.
- Linux Linux Logging with journalctl: Filters, Fields, and Forwarding
Master systemd's journal: query logs with structured filters, tail in real time, persist across reboots, and forward to a central collector.
- Linux systemd Service Units: A Practical Tutorial
Write, install, and operate systemd service units the right way. Learn unit syntax, restart policies, logging with journalctl, and common gotchas.
- Linux systemd Basics: Services, Units, and journalctl
Learn systemd from the ground up: units and services, systemctl commands, restart policies, logs with journalctl, and writing your first .service file.