Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français

systemd: understand the Linux service manager

By updated

Improve this page

systemd is the system and service manager of almost every modern Linux distribution. It is the first program the kernel starts at boot, the one that carries PID 1 (Process IDentifier number 1) and becomes the parent of every other process. Its job fits in four verbs: boot the machine in the right order, start and supervise services (the programs that run in the background without an interface), restart them when they fall, and centralise the logs in a single place. This page sets the mental model: units, targets, dependencies, so that the commands you will type next make sense.

What you will learn

This page is a concept page, not a command tutorial. It helps you understand how systemd reasons, because the most stubborn mistakes in Linux administration come from a wrong mental model, not from a mistyped option.

  • What systemd is, and what sets it apart from systemctl
  • Why it replaced SysV init, and what it is criticised for
  • The concept of unit and the nine types you will meet most
  • The difference between start-up order and dependency
  • Why daemon-reload is not a formality

What exactly is systemd

When you power on a Linux server, the kernel loads into memory, mounts the root filesystem, then hands over to a user-space program. This program receives PID 1. It will never stop as long as the machine runs, and every other process will descend from it. On a modern distribution, this program is called systemd.

Check it on your machine, the command works everywhere:

Terminal window
ps -p 1 -o comm=
# systemd
systemctl --version | head -1
# systemd 257 (257.13-1~deb13u1)

The term daemon, which you will see everywhere in this field, simply means a program that runs in the background, detached from any terminal: a web server, a database, a monitoring agent. The final d of systemd comes from there. The role of systemd is to start these daemons, to know at any moment which ones run, and to react when one of them dies.

Mind a nuance that costs beginners dearly: systemd is not a command. It is a permanent process. What you type is systemctl, the client command that passes your orders to systemd and shows its state. systemd is the server, systemctl is the remote control. The same logic applies to journalctl, which reads the journal systemd collects.

Why systemd replaced SysV init

Before systemd, PID 1 was SysV init. Its operation was simple to describe: at boot, it ran shell scripts stored in /etc/init.d/, called one by one, in the order imposed by numbered symbolic links (S20nginx came before S30postgresql). Each script had to handle by itself the start in the background, writing a PID file, the clean stop and the return codes, often over several hundred lines.

This model has two structural weaknesses. First slowness: a script waiting for the network blocks the whole queue, even services that have nothing to do with it. Then the lack of supervision: once the script is done, nobody watches the process any more. If it dies three hours later, init ignores it.

AspectSysV initsystemd
Start-upSequential, one script at a timeParallel, driven by a dependency graph
DependenciesHand-encoded in the numberingDeclared explicitly in the unit file
SupervisionNone after startPermanent, with a restart policy
Process trackingPID file, often stale or wrongcgroups, every child is tracked
LogsFiles scattered in /var/log/Centralised, queryable journal
Writing a serviceShell script of 100 to 300 linesDeclarative file of 10 to 20 lines

systemd reverses the logic: instead of running scripts in a fixed order, it reads declarative files where each service says what it needs, then it computes the dependency graph and starts in parallel everything that can be. The boot time gain is real, but the real contribution lies elsewhere: systemd keeps control of the processes it started.

What it is criticised for

It would be dishonest to present systemd as a consensus. Its adoption, around 2014-2015, caused one of the most violent controversies in the history of Linux, and the criticism has not gone away.

The central criticism is about scope. systemd did not just replace init: the project absorbed logging, DNS resolution, network configuration, session management, time synchronisation. For its critics, this centralisation contradicts the Unix philosophy of "one tool, one task", creates a single point of failure in PID 1, and makes the system harder to understand end to end. The second criticism is complexity: where an init script could be read with a shell, understanding why systemd took a given decision requires knowing its unit model, its implicit targets and its generators.

The maintainers answer that these components are optional and separable, and that the reliability of supervision justifies the surface. The fact remains: Debian, Ubuntu, RHEL, Fedora, SUSE and Arch adopted it. In a professional environment, you will administer systemd. Alternatives remain nonetheless, and they are not marginal: Alpine Linux uses OpenRC, Void Linux uses runit, and Devuan is a Debian fork created precisely to stay without systemd.

Units, the basic building block

Everything systemd manages is a unit. A unit is an object described by a text file in INI format, split into sections between brackets. A service is a unit, a mount point is a unit, a scheduled task is a unit. It is the central concept: if you remember only one thing from this page, remember that one.

The file extension determines the type of unit and therefore the behaviour of systemd:

ExtensionWhat it managesConcrete example
.serviceA program to start and supervisenginx.service
.timerA scheduled task, the alternative to cronlogrotate.timer
.socketA port or a socket, with on-demand activationssh.socket
.mountA filesystem mount pointboot.mount
.targetA group of units, replaces runlevelsmulti-user.target
.pathWatching a file or a foldercups.path
.deviceA device exposed by the kernelDisk, network card
.sliceA group of CPU and memory limitssystem.slice
.scopeProcesses not started by systemd, but trackedSSH session, container

The systemd(1) manual counts eleven unit types: the nine of this table plus .automount (on-demand mount) and .swap (swap space). The last three types of the table are rarely written by hand, but you will see them in command outputs, and knowing what they are saves you from feeling lost.

Loaded is not installed

Here is the distinction that unlocks the reading of systemd outputs. Two commands that look alike return very different things:

Terminal window
# What systemd has LOADED in memory, now
systemctl list-units --type=service
# What is INSTALLED on disk
systemctl list-unit-files --type=service

On a minimal Debian 13 system, the first reports 14 loaded services:

14 loaded units listed. Pass --all to see loaded but inactive units, too.
To show all installed unit files use 'systemctl list-unit-files'.

The second counts 119:

119 unit files listed.

The gap is not an anomaly. The 119 files are the units shipped by the packages, present on disk, available. The 14 loaded units are those systemd has actually read and instantiated in memory, because they were started or pulled by a dependency. Everything else sleeps. When list-units does not show your service, it has not disappeared: it is simply not loaded, and list-unit-files will confirm it.

Targets, what replaces runlevels

A target is a unit that does nothing by itself: it serves as a grouping point. It is used as a milestone in the boot, the modern equivalent of the runlevels of SysV init, those numbered levels that defined the state of the machine.

TargetOld runlevelState of the machine
rescue.target1Maintenance, single user, bare minimum
multi-user.target3Operational server, network active, no graphical interface
graphical.target5Same, plus the desktop environment
reboot.target6Reboot

A target groups units: reaching multi-user.target means having started everything attached to it. It is exactly the meaning of the WantedBy=multi-user.target line you will meet in service files: it says "hook me to this milestone". Targets are themselves chained, graphical.target depending on multi-user.target, which depends on basic.target, which depends on sysinit.target.

Terminal window
systemctl list-units --type=target --no-pager
UNIT LOAD ACTIVE SUB DESCRIPTION
basic.target loaded active active Basic System
graphical.target loaded active active Graphical Interface
local-fs.target loaded active active Local File Systems
multi-user.target loaded active active Multi-User System
network-online.target loaded active active Network is Online
sysinit.target loaded active active System Initialization
timers.target loaded active active Timer Units

The systemctl get-default command shows the target reached automatically at boot. On a server, the expected answer is multi-user.target: running a graphical stack on a headless machine wastes memory. The opposite setting reports no error, which makes it hard to spot, and Ubuntu Server ships precisely with graphical.target by default.

The details of the operations (switching on the fly with isolate, setting the target of the next boot with set-default, entering rescue mode without losing SSH access) are covered in Reboot and shut down.

Start-up order is not dependency

It is the most costly confusion of the systemd model, and it produces intermittent failures, the ones that only show up one boot out of five when the machine is loaded. Unit files handle two distinct notions that look alike:

DirectiveWhat it really saysWhat it does not say
After= / Before=Order: if both units start, one goes after the otherIt does not ask for the other unit to start
Requires=Strong dependency: the other is started with me, and if it is stopped explicitly, so am IIt says nothing about order: its failure to start only blocks me if After= is also set
Wants=Weak dependency: try to start the other, go on if it failsIt says nothing about order

The trap is mechanical. A Requires=postgresql.service alone, without After=, does ask for the database to start, but in parallel. Your application then tries to open a connection while PostgreSQL is still initialising its files, and fails. The systemd.unit(5) manual states it precisely: the failure of a required unit only prevents the start if an After= also targets it. Conversely, an After=postgresql.service alone orders correctly, but never asks for the database to start: if nobody else pulls it, it does not start, and your application starts in the void.

The right combination for an application that cannot live without its database declares both:

[Unit]
Description=Billing API
Requires=postgresql.service
After=postgresql.service

For a non-vital dependency, a cache for example, you replace Requires= with Wants=: the service starts even if the cache is unavailable. The systemctl list-dependencies command checks the tree actually built by systemd, rather than the one you think you wrote.

Where unit files live

Unit files are scattered over three directories, and this dispersion is not a defect: it encodes a priority hierarchy that protects your changes.

DirectoryWho writes hereLifetime
/usr/lib/systemd/system/The distribution packagesOverwritten at each update
/run/systemd/system/Temporary units, generated on the flyDisappears at reboot
/etc/systemd/system/You, the administratorPersistent, never overwritten

The priority rule is /etc/ before /run/ before /usr/lib/. A unit placed in /etc/systemd/system/ masks the package one with the same name. This is what makes the following rule non-negotiable: never edit a file in /usr/lib/systemd/system/. Your change will survive there until the next update of the package, which will erase it without warning, and the service will go back to its original behaviour at a moment you did not choose.

The right method is called a drop-in: a configuration fragment placed in a <unit>.service.d/ subdirectory, which contains only what you change. systemd stacks the package file and your fragments. The systemctl edit command creates this fragment for you, and systemctl cat shows the final result, original file and drop-ins merged. To know which file is actually used, systemctl show <unit> -p FragmentPath gives the exact path.

daemon-reload, and why it is mandatory

systemd does not reread unit files at each command. At boot, it builds in memory a compiled graph of every unit, and it is this in-memory version it applies. Changing a file on disk changes nothing until you tell it.

The demonstration is beyond appeal. Take a service whose file contains sleep 30, and replace this value with sleep 45 on disk. With nothing else, a restart:

Terminal window
sudo systemctl restart horloge
Warning: The unit file, source configuration file or drop-ins of horloge.service
changed on disk. Run 'systemctl daemon-reload' to reload units.

systemd warns you, but it restarts the old version anyway. The proof, by querying the command it has just really run:

Terminal window
systemctl show horloge -p ExecStart --value
{ path=/bin/sh ; argv[]=/bin/sh -c while true; do date; sleep 30; done ; ... }

The disk says 45, systemd runs 30. After a daemon-reload, the same query finally returns the right value:

Terminal window
sudo systemctl daemon-reload
sudo systemctl restart horloge
systemctl show horloge -p ExecStart --value
{ path=/bin/sh ; argv[]=/bin/sh -c while true; do date; sleep 45; done ; ... }

Do not confuse the two "reloads". systemctl reload nginx asks nginx to reread its configuration (/etc/nginx/nginx.conf). systemctl daemon-reload asks systemd to reread its unit files. The two words look alike, the two commands have nothing in common. The rule fits in one line: after any creation or change of a unit file, daemon-reload is mandatory. The only exception, systemctl edit does it for you when the editor closes (systemctl manual, systemd 257).

The ecosystem around systemd

systemd is not an isolated binary but a suite of tools that share the same unit model. Three commands cover the essentials of daily work, and knowing which does what saves looking in the wrong place.

CommandWhat it doesWhere to go deeper
systemctlDrive units: state, start, enabling at bootStart and diagnose a service
journalctlRead the centralised journal collected by systemdMaster the logs with journalctl
systemd-analyzeMeasure the boot, check a unit, audit its securityHarden a systemd service (in French)

Each service started by systemd is placed in its own cgroup (control group), a kernel mechanism of Linux that groups processes to count and limit them together. This explains the CGroup field you see in a systemctl status:

CGroup: /system.slice/nginx.service
├─81 "nginx: master process /usr/sbin/nginx"
├─82 "nginx: worker process"
└─83 "nginx: worker process"

This detail has three very concrete consequences. systemd tracks every child process, including those that detached themselves, where SysV init lost their track as soon as the PID file became wrong. A systemctl stop really stops the whole group, without an orphan process that keeps holding a port. Finally, resource limits (maximum memory, CPU quota) apply to the whole group. The mechanism itself is detailed in the page Understand Linux cgroups.

Vocabulary traps

Four pairs of terms look alike and cause most misunderstandings. Fixing them now will save you time on the practical pages.

People confuseWhereas
systemd and systemctlsystemd is the PID 1 process; systemctl is the command that talks to it
unit and serviceA service is one type of unit out of eleven, not a synonym
start and enablestart starts now; enable sets up the next boot. Neither does what the other does
reload and daemon-reloadreload reloads the config of the service; daemon-reload reloads the unit files

The start / enable pair deserves special emphasis because it explains the server that works perfectly until the first reboot, then does not come back: the service had been started, never enabled. The full mechanism, with the symbolic links created by enable and the role of presets, is covered in Enable a service at boot.

Where to go next

The mental model is set. The actions, for their part, are spread over dedicated pages, each matching a precise need.

Put it into practice

systemd is understood by reading, but mastered by repairing. The drill sets units to create, enable and diagnose under exam conditions: no hint, and the validation is about the state after reboot, never about the command typed.

Key points

systemd is PID 1: the first process started by the kernel, parent of all others, never stopped as long as the machine runs.

systemd is not systemctl: the first is the process that manages the system, the second the client command that passes your orders to it.

Everything is a unit: service, timer, socket, mount, target, slice. The file extension determines the type and the behaviour.

Loaded is not installed: list-units shows what is in memory, list-unit-files what exists on disk. The gap is normal.

Order is not dependency: After= orders, Requires= requires. You often need to declare both, or face intermittent failures.

/etc/ always wins: you never edit /usr/lib/systemd/system/, you go through a drop-in.

daemon-reload is mandatory: without it, systemd keeps applying the old version of the file, even after a restart.

Frequently asked questions

The questions below are those that come up most often among administrators discovering systemd: its definition, its relation to SysV init, the distinction with systemctl, and the exact role of daemon-reload.

Official resources

The reference stays the project documentation: the manpages below are those of the systemd version installed, also readable offline with man.

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn