systemd unit

On this page 8

A systemd unit is a declarative configuration object that describes one resource managed by systemd, such as a service, a mount point, a socket, a device or a timer. In Linux system initialization, units replace the shell scripts of SysV init: instead of writing the steps to start a daemon, an administrator declares what the resource is, what it depends on and how it should be supervised, and systemd computes the startup order itself.

Every unit lives in a plain-text unit file whose suffix names its type, for example nginx.service or home.mount. The file uses an INI-like syntax with sections such as [Unit], [Service] and [Install], and systemd loads these files into memory as a dependency graph that drives boot, shutdown and runtime changes.

How a systemd unit is structured

A unit file is divided into sections, and each section holds key-value directives. The [Unit] section is common to all unit types and carries the description, documentation hints and dependency and ordering directives. A type-specific section follows, such as [Service], [Socket], [Mount] or [Timer], and an optional [Install] section defines how the unit is enabled for automatic start.

The table below shows the three sections that appear in a typical service unit and what each one controls.

SectionTypical directivesWhat it controls
[Unit]Description, After, Requires, WantsMetadata, dependencies and ordering
[Service]ExecStart, Type, Restart, UserHow the process is started and supervised
[Install]WantedBy, RequiredBy, AliasWhich target pulls the unit in when enabled
Main sections of a service unit file

Unit files are read from several directories with a fixed precedence order. Vendor files shipped by packages live in /usr/lib/systemd/system, runtime units in /run/systemd/system, and administrator files in /etc/systemd/system, which overrides both. A drop-in directory such as /etc/systemd/system/nginx.service.d/ holds .conf fragments that change single directives without copying the whole vendor file, which keeps local changes intact across package upgrades.

Unit types and what they manage

systemd defines eleven unit types, and the suffix of the file name selects the type. Most administrators work daily with services, sockets, timers, mounts and targets, while slices and scopes matter for resource control through cgroups.

Unit typeSuffixManagesTypical use
Service.serviceA process or daemonWeb servers, databases, agents
Socket.socketA listening socket or FIFOSocket activation on first connection
Target.targetA group of unitsBoot milestones such as multi-user.target
Timer.timerA time-based triggerScheduled jobs replacing cron entries
Mount.mountA file system mount pointGenerated from /etc/fstab or written by hand
Path.pathA watched file or directoryStart a unit when a file appears
Slice.sliceA cgroup subtreeCPU and memory limits for groups of units
Common systemd unit types

A target unit has no process of its own; it is a synchronization point that groups other units. Targets replace SysV runlevels, so multi-user.target corresponds roughly to runlevel 3 and graphical.target to runlevel 5. A template unit such as getty@.service is instantiated with a parameter, for example getty@tty2.service, which lets one file manage many similar instances.

Dependencies and ordering between units

systemd separates requirement dependencies from ordering dependencies, and confusing the two is the most frequent mistake in unit files. Requires= and Wants= decide which units are started together, while After= and Before= decide only the sequence. Without an ordering directive, related units start in parallel, which is the main reason systemd boots faster than sequential init scripts.

DirectiveEffectFailure behavior
Wants=Starts the listed unit as wellStartup continues if it fails
Requires=Starts the listed unit as wellThis unit is stopped if it fails or stops
BindsTo=Stronger form of RequiresStops when the other unit disappears
After=Starts this unit after the otherNo effect on whether it starts
Conflicts=Stops the other unit when this startsPrevents both running at once
Dependency and ordering directives

The usual pattern for a daemon that needs the network is Wants=network-online.target combined with After=network-online.target. The Wants line pulls the target in, and the After line makes the daemon wait until the network is configured. Using After alone has no effect if nothing else activates the target.

Service types and supervision settings

The Type= directive tells systemd when a service counts as started, which matters for every unit ordered after it. A wrong type leads to dependent units starting too early or to systemd killing a daemon it believes has failed.

TypeConsidered started whenSuitable for
simpleThe main process has been forkedPrograms that stay in the foreground
execThe binary has been executed successfullyForeground programs needing exec errors reported
forkingThe parent process exitsClassic daemons that fork into the background
oneshotThe process has finishedSetup scripts and one-time tasks
notifyThe service sends READY=1 via sd_notifyDaemons with an explicit readiness protocol
Service types and their readiness signal

Supervision is configured with restart policies. Restart=on-failure restarts a service after a non-zero exit or a crash, and RestartSec= sets the delay, commonly between 1 and 10 seconds. The settings StartLimitIntervalSec= and StartLimitBurst= cap restarts, by default 5 starts within 10 seconds, after which the unit enters a failed state instead of looping indefinitely.

Units can also carry sandboxing and resource directives. ProtectSystem=strict, PrivateTmp=yes and NoNewPrivileges=yes restrict what a service can touch, and MemoryMax= or CPUQuota= set cgroup limits. The command systemd-analyze security scores a service by how many of these protections are active.

Managing units in daily administration

Administrators control units with systemctl. Starting a unit acts immediately, while enabling it creates symbolic links in the .wants directory of the target named in WantedBy=, so the unit starts at the next boot. The two operations are independent, and systemctl enable --now combines them.

  • systemctl daemon-reload makes systemd reread changed unit files; without it, edits are ignored.
  • systemctl edit nginx.service creates a drop-in override safely instead of editing the vendor file.
  • systemctl mask links a unit to /dev/null so that it cannot be started at all, even as a dependency.
  • systemctl list-dependencies and systemd-analyze critical-chain show the dependency tree and boot delays.
  • Logs of any unit are read with Journalctl, for example journalctl -u nginx.service.

Common mistakes with unit files

Most unit problems come from a handful of recurring errors. The table pairs each symptom with its usual cause and fix.

SymptomLikely causeFix
Changes have no effectMissing daemon-reloadRun systemctl daemon-reload
Service starts before the networkAfter= without Wants=Add both directives for network-online.target
Daemon reported as failed after startType=simple on a forking daemonUse Type=forking with PIDFile= or run in foreground
Edits lost after a package upgradeVendor file edited directlyMove changes to a drop-in file
Unit does not start at bootStarted but never enabledRun systemctl enable
Symptoms, causes and fixes

A further pitfall is relying on shell syntax in ExecStart=. systemd does not run commands through a shell, so pipes, redirections and variable expansion such as $HOME behave differently; a command that needs them must be wrapped explicitly in /bin/sh -c.

In plain words

A systemd unit is like a labeled instruction card for one part of the computer. Each card says what the part is, which other parts must be ready first and what to do if it stops working.

When the computer starts, systemd reads all the cards, works out the right order and starts many parts at the same time when they do not depend on each other. The key idea is that the administrator describes the goal rather than writing the steps, and systemd handles the rest.

A real-world example

A small company runs an internal Python web application on a Linux virtual machine. It used to be started by hand in a terminal session and stopped whenever that session closed. The administrator writes a dedicated service unit, /etc/systemd/system/inventory.service, with Type=simple, User=inventory, ExecStart=/opt/inventory/venv/bin/gunicorn app:app, Restart=on-failure and RestartSec=5.

The [Unit] section declares Wants= and After= on both network-online.target and postgresql.service, and [Install] sets WantedBy=multi-user.target. After systemctl daemon-reload and systemctl enable --now inventory.service, the application starts automatically at boot, waits for its database and recovers from crashes within five seconds. A drop-in file later adds MemoryMax=1G, so a memory leak no longer threatens the whole server.