A systemd service unit file is a declarative configuration that tells systemd how to start, stop, restart, and monitor a background process. It replaces init scripts with a standardized format that systemd parses at boot and on demand.
Understanding the unit file model is essential for reliable production services. You'll write fewer of them than you think, but the ones you write need to be correct.
The Unit File Model
Every service unit file has three sections: [Unit], [Service], and [Install]. Think of them as metadata, runtime behavior, and registration.
[Unit] describes the service to systemd and humans: its name, description, and dependencies. This section answers "what is this thing and what does it need?"
[Service] defines how to run the process: the command, user, restart policy, and resource limits. This section answers "how do I start and keep it running?"
[Install] registers the service in a target, usually multi-user.target for server workloads. This section answers "when should this start?"
Here's a minimal but complete example:
[Unit]
Description=My Application Service
After=network.target
[Service]
Type=simple
User=appuser
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/bin/server
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
This declares a service that starts after the network is up, runs as appuser, restarts on non-zero exit with a 10-second delay, and activates on boot.
The Directive Breakdown
Description is a one-line human label. Use it. Systemctl output shows it; logs reference it.
After and Before define ordering. After=network.target means "don't start me until the network is ready." This is a soft dependency—the service starts even if network.target fails, but it waits for the ordering. Use Requires= or Wants= if you need hard dependencies.
Type controls how systemd knows the service is ready:
simple: systemd assumes the service is ready when ExecStart returns (most common).forking: the process forks into the background; systemd waits for the parent to exit.oneshot: the process runs once and exits; systemd doesn't expect it to stay running.notify: the process sends a readiness signal via sd_notify(); systemd waits for it.
Choose simple unless the application is old or requires background daemonization.
User and Group specify the runtime identity. Always run services as a non-root user. Create a dedicated user for each service if possible.
WorkingDirectory sets the process's working directory. Relative paths in the command are resolved from here.
ExecStart is the command to run. Use absolute paths. Environment variables are expanded. You can use multiple ExecStart directives; systemd runs them in order.
Restart controls restart behavior:
no: don't restart (default).on-failure: restart only on non-zero exit or signal.always: restart unconditionally.on-abnormal: restart on signal or timeout (not clean exit).
RestartSec is the delay (in seconds) before restarting. Default is 100ms. Use 10 or higher in production to avoid restart loops.
StandardOutput and StandardError route logs to systemd journal by default. Set to journal, syslog, kmsg, or a file path. Journal integration is usually best.
Environment sets process environment variables. Use EnvironmentFile= to load from a file.
WantedBy registers the service in a target. multi-user.target is the standard for server services; it activates on boot in non-graphical mode.
Common Patterns
Long-running application:
[Service]
Type=simple
ExecStart=/usr/local/bin/myapp
Restart=on-failure
RestartSec=10
Application with startup dependencies:
[Unit]
After=postgresql.service redis.service
Wants=postgresql.service redis.service
[Service]
Type=simple
ExecStart=/opt/app/bin/server
One-time task (cron replacement):
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup-script
[Install]
WantedBy=multi-user.target
Service with health checks and resource limits:
[Service]
Type=simple
ExecStart=/opt/service/bin/server
Restart=on-failure
RestartSec=5
MemoryLimit=512M
CPUQuota=50%
TimeoutStopSec=30
Gotchas and Trade-offs
Environment variable expansion. Systemd does not invoke a shell by default, so ~ and $HOME are not expanded. Use absolute paths in ExecStart. If you need shell features (pipes, redirects), wrap the command in /bin/sh -c "...", but this adds a subprocess layer.
Restart loops. A service that exits and restarts immediately can consume CPU and fill logs. Always set RestartSec to a sensible value (10 seconds minimum). Consider StartLimitInterval and StartLimitBurst to stop restarting after repeated failures within a window.
Type=forking is fragile. If the application doesn't fork correctly or the parent exits before the child is ready, systemd may lose track of the process. Use Type=simple or Type=notify instead. If you must use forking, test it thoroughly.
Dependency ordering vs. availability. After= only orders startup; it doesn't check if the dependency is healthy. Use Wants= for soft dependencies and Requires= for hard ones. A service can start "after" another but fail if that service isn't actually running.
File descriptor limits. Systemd sets LimitNOFILE (max open files) to 1024 by default on many systems. If your application needs more, set LimitNOFILE=65536 (or higher). Check with systemctl show -p LimitNOFILE servicename.
Killing the service. TimeoutStopSec (default 90 seconds) is how long systemd waits for a graceful shutdown before sending SIGKILL. If your app takes time to drain connections, increase this. If it hangs, decrease it.
Testing and Debugging
After writing a unit file, place it in /etc/systemd/system/ and run:
sudo systemctl daemon-reload
sudo systemctl start myservice
sudo systemctl status myservice
journalctl -u myservice -n 50
Use systemd-analyze verify /etc/systemd/system/myservice.service to check syntax. Use systemctl show myservice to inspect all active settings.
Watch logs in real time with journalctl -u myservice -f.
Takeaway
A well-written systemd service unit file is a runbook encoded as configuration. Start with Type=simple, set Restart=on-failure, always use a non-root user, and test your restart behavior under failure. If your service runs on a Linux VPS, choosing the right host matters too — see the comparison on tinjauhost.biz.id for options worth considering. The three-section structure (Unit, Service, Install) is the mental model; master it and most production workloads become straightforward.