Understanding service units

A systemd service is defined in a plain-text unit file with a .service extension. The file contains sections that describe the unit, the service itself, and when it should be installed. The [Unit] section holds metadata and dependency declarations. The [Service] section specifies the executable and its runtime behavior. The [Install] section tells systemd which target should pull the service in during boot.

User services live in ~/.config/systemd/user while system services belong in /etc/systemd/system. System services run as root by default and survive user logouts; user services run under the logged-in user and stop when that user logs out. Choosing the right location determines whether the service persists across logouts and whether it requires elevated privileges to manage.

Each section uses simple key-value pairs. This structure makes it straightforward to read or edit the file with any text editor. Because the format is declarative, systemd can validate dependencies before attempting to start the service.

Writing a minimal service file

A one-shot service runs a script once and exits. A simple service runs continuously. Both start with a [Unit] Description line. In the [Service] section, ExecStart points to the command or script. For a one-shot job that waits for the network, add After=network-online.target and Type=oneshot. For a long-running program, omit Type or set it to simple and add Restart=on-failure so systemd restarts the process after a crash.

After placing the file, reload the daemon so systemd recognizes the new unit. Then enable the service to create the necessary symlinks for automatic startup. Testing with an immediate start command lets you verify behavior before the next reboot.

  • Place the file in /etc/systemd/system/myproject.service for a system service.
  • Run sudo systemctl enable myproject.service to create the symlink that starts it at boot.
  • Use sudo systemctl start myproject.service to launch it immediately for testing.

Controlling startup order

Systemd reaches targets in sequence during boot. The default.target is the last target reached on a typical desktop or server install. To make your service start near the end of boot, set WantedBy=default.target in the [Install] section. If your program needs the network first, add After=network-online.target in the [Unit] section. The After directive only sets ordering; it does not activate the other unit. Use Wants= or Requires= when you also need the dependency to be started.

Ordering directives prevent race conditions where a project tries to use a resource that has not yet been initialized. Because systemd builds a dependency graph, it can start independent services in parallel while still respecting the declared sequence. This approach replaces the linear execution model of older scripts and reduces boot time variability.

Restart behavior and failure handling

By default systemd leaves a failed service stopped. Adding Restart=on-failure in the [Service] section tells it to restart the process whenever the main process exits with a non-zero code. You can also set RestartSec=5 to wait five seconds between attempts. These options reduce the chance that a transient error leaves your project offline until the next manual reboot. freedesktop.org documents additional Restart settings such as always and on-abnormal for finer control.

Restart policies apply only after the initial start attempt succeeds. If a service fails during its first execution because of a missing dependency or incorrect path, systemd will not automatically retry unless the unit is also configured with appropriate Wants or Requires directives. Combining restart settings with proper ordering therefore improves overall resilience.

Running as a non-root user

Running services as root increases risk if the program is compromised. Add User=pi (or another dedicated account) in the [Service] section to drop privileges. The script or binary must then be readable and executable by that user, and any files it writes must be writable by the same account. When the service runs as a normal user it cannot bind to ports below 1024 or access hardware that requires root without additional configuration such as udev rules.

Privilege separation also limits the scope of any file-system changes the service can make. Before enabling the unit, verify ownership and permissions on all paths referenced by ExecStart and any working directories. This step avoids startup failures that would otherwise appear only after a reboot.

Inspecting logs and service status

Systemd captures stdout and stderr from services and stores them in the journal. Run journalctl --unit=myproject.service to view recent output. Add -f to follow new messages live. systemctl status myproject.service shows whether the unit is active, the main PID, memory usage, and the last few log lines. These commands replace manual log files and work even when the service writes nothing to disk.

The journal stores entries with timestamps and metadata, making it possible to correlate service output with other system events. Filtering by unit name keeps the output focused, while the live-follow option helps during initial debugging. When a service fails repeatedly, the status command quickly reveals the exit code and any recent error messages without needing to search separate log directories.