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.
| Section | Typical directives | What it controls |
|---|---|---|
| [Unit] | Description, After, Requires, Wants | Metadata, dependencies and ordering |
| [Service] | ExecStart, Type, Restart, User | How the process is started and supervised |
| [Install] | WantedBy, RequiredBy, Alias | Which target pulls the unit in when enabled |
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 type | Suffix | Manages | Typical use |
|---|---|---|---|
| Service | .service | A process or daemon | Web servers, databases, agents |
| Socket | .socket | A listening socket or FIFO | Socket activation on first connection |
| Target | .target | A group of units | Boot milestones such as multi-user.target |
| Timer | .timer | A time-based trigger | Scheduled jobs replacing cron entries |
| Mount | .mount | A file system mount point | Generated from /etc/fstab or written by hand |
| Path | .path | A watched file or directory | Start a unit when a file appears |
| Slice | .slice | A cgroup subtree | CPU and memory limits for groups of units |
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.
| Directive | Effect | Failure behavior |
|---|---|---|
| Wants= | Starts the listed unit as well | Startup continues if it fails |
| Requires= | Starts the listed unit as well | This unit is stopped if it fails or stops |
| BindsTo= | Stronger form of Requires | Stops when the other unit disappears |
| After= | Starts this unit after the other | No effect on whether it starts |
| Conflicts= | Stops the other unit when this starts | Prevents both running at once |
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.
| Type | Considered started when | Suitable for |
|---|---|---|
| simple | The main process has been forked | Programs that stay in the foreground |
| exec | The binary has been executed successfully | Foreground programs needing exec errors reported |
| forking | The parent process exits | Classic daemons that fork into the background |
| oneshot | The process has finished | Setup scripts and one-time tasks |
| notify | The service sends READY=1 via sd_notify | Daemons with an explicit readiness protocol |
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-reloadmakes systemd reread changed unit files; without it, edits are ignored.systemctl edit nginx.servicecreates a drop-in override safely instead of editing the vendor file.systemctl masklinks a unit to/dev/nullso that it cannot be started at all, even as a dependency.systemctl list-dependenciesandsystemd-analyze critical-chainshow 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.
| Symptom | Likely cause | Fix |
|---|---|---|
| Changes have no effect | Missing daemon-reload | Run systemctl daemon-reload |
| Service starts before the network | After= without Wants= | Add both directives for network-online.target |
| Daemon reported as failed after start | Type=simple on a forking daemon | Use Type=forking with PIDFile= or run in foreground |
| Edits lost after a package upgrade | Vendor file edited directly | Move changes to a drop-in file |
| Unit does not start at boot | Started but never enabled | Run systemctl enable |
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.