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-reloadis 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:
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.
| Aspect | SysV init | systemd |
|---|---|---|
| Start-up | Sequential, one script at a time | Parallel, driven by a dependency graph |
| Dependencies | Hand-encoded in the numbering | Declared explicitly in the unit file |
| Supervision | None after start | Permanent, with a restart policy |
| Process tracking | PID file, often stale or wrong | cgroups, every child is tracked |
| Logs | Files scattered in /var/log/ | Centralised, queryable journal |
| Writing a service | Shell script of 100 to 300 lines | Declarative 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:
| Extension | What it manages | Concrete example |
|---|---|---|
.service | A program to start and supervise | nginx.service |
.timer | A scheduled task, the alternative to cron | logrotate.timer |
.socket | A port or a socket, with on-demand activation | ssh.socket |
.mount | A filesystem mount point | boot.mount |
.target | A group of units, replaces runlevels | multi-user.target |
.path | Watching a file or a folder | cups.path |
.device | A device exposed by the kernel | Disk, network card |
.slice | A group of CPU and memory limits | system.slice |
.scope | Processes not started by systemd, but tracked | SSH 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:
# What systemd has LOADED in memory, nowsystemctl list-units --type=service
# What is INSTALLED on disksystemctl list-unit-files --type=serviceOn 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.
| Target | Old runlevel | State of the machine |
|---|---|---|
rescue.target | 1 | Maintenance, single user, bare minimum |
multi-user.target | 3 | Operational server, network active, no graphical interface |
graphical.target | 5 | Same, plus the desktop environment |
reboot.target | 6 | Reboot |
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.
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 UnitsThe 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:
| Directive | What it really says | What it does not say |
|---|---|---|
After= / Before= | Order: if both units start, one goes after the other | It 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 I | It 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 fails | It 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 APIRequires=postgresql.serviceAfter=postgresql.serviceFor 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.
| Directory | Who writes here | Lifetime |
|---|---|---|
/usr/lib/systemd/system/ | The distribution packages | Overwritten at each update |
/run/systemd/system/ | Temporary units, generated on the fly | Disappears at reboot |
/etc/systemd/system/ | You, the administrator | Persistent, 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:
sudo systemctl restart horlogeWarning: The unit file, source configuration file or drop-ins of horloge.servicechanged 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:
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:
sudo systemctl daemon-reloadsudo systemctl restart horlogesystemctl 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.
| Command | What it does | Where to go deeper |
|---|---|---|
systemctl | Drive units: state, start, enabling at boot | Start and diagnose a service |
journalctl | Read the centralised journal collected by systemd | Master the logs with journalctl |
systemd-analyze | Measure the boot, check a unit, audit its security | Harden a systemd service (in French) |
The link with cgroups
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 confuse | Whereas |
|---|---|
| systemd and systemctl | systemd is the PID 1 process; systemctl is the command that talks to it |
| unit and service | A service is one type of unit out of eleven, not a synonym |
| start and enable | start starts now; enable sets up the next boot. Neither does what the other does |
| reload and daemon-reload | reload 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.
- Drive a service day to day, read a
status, understand a failure: Start and diagnose a service. - Guarantee that a service comes back after reboot: Enable a service at boot.
- A service refuses to start and you must find out why: Troubleshoot a service that does not start.
- Schedule a task without going through cron: systemd timers.
- Reduce the attack surface of a service: Harden a systemd service (in French).
- Read and master the logs systemd centralises: journalctl, then log rotation.
- Go one level down, to the kernel mechanism systemd relies on to track and limit each service: cgroups.
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.
Its role
Once loaded, the Linux kernel starts a first user-space program. This program carries PID 1 (Process IDentifier 1) and becomes the parent of every other process. On a modern distribution, this program is systemd.It has four missions:- Initialise the system: mount the filesystems, configure the network, reach a working state.
- Start the services in parallel, respecting their dependencies.
- Supervise: detect that a service has fallen and restart it according to the defined policy.
- Log: collect the messages of every service in the journal, readable with
journalctl.
Check that systemd drives your machine
ps -p 1 -o comm=
# systemd
systemctl --version | head -1
# systemd 257 (257.13-1~deb13u1)
systemd is not a simple program: it is a suite of tools (systemctl, journalctl, systemd-analyze, systemd-run) that share the same unit model./etc/init.d/, called one by one in an order set by symbolic link numbers (S20nginx, S30postgresql).The limits of this model
| Aspect | SysV init | systemd |
|---|---|---|
| Start-up | Sequential, one script at a time | Parallel, driven by a dependency graph |
| Dependencies | Hand-encoded in the order of the numbers | Declared explicitly (After=, Requires=) |
| Supervision | None: if the service falls, nobody knows | Built in, with a restart policy |
| Process tracking | PID file, often wrong | cgroups, every child process tracked |
| Logs | Files scattered in /var/log/ |
Centralised journal (journalctl) |
| Format | Shell script of several hundred lines | Declarative file of 10 to 20 lines |
What it changes in practice
A SysV script had to handle by itself the start in the background, writing the PID file, the clean stop and the return codes. A systemd unit declares what to start, and systemd takes care of the rest.systemd remains criticised for its complexity and for absorbing functions that were historically separate. The major distributions adopted it nonetheless, mainly for the reliability of supervision.| Term | What it is | Where you see it |
|---|---|---|
| systemd | The program running permanently as PID 1 and managing the system | ps -p 1 -o comm= |
| systemctl | The client command you type to give orders to systemd | systemctl status nginx |
| journalctl | The client command to read the journal collected by systemd | journalctl -u nginx |
The main types
| Extension | What it manages |
|---|---|
.service |
A program to start and supervise (the most common) |
.timer |
A scheduled task, the modern alternative to cron |
.socket |
A port or a socket: the service starts at the first connection |
.mount |
A filesystem mount point |
.target |
A group of units, which replaces the old runlevels |
.path |
Watching a file or a folder |
.slice |
A group of CPU and memory resources (cgroup) |
.scope |
A set of processes not started by systemd (SSH session, container) |
Loaded is not installed
Two different commands, two realities:# What systemd has LOADED in memory now
systemctl list-units --type=service
# What is INSTALLED on disk (unit files present)
systemctl list-unit-files --type=service
On a minimal Debian 13 system, the first returns 14 loaded services, the second 119 installed files. The difference is the units present on disk but never activated.The consequence
If you change/etc/systemd/system/my-app.service then run a restart, systemd restarts the old version, the one it has in memory. The demonstration on a service where sleep 30 is replaced with sleep 45 in the file: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.
systemctl show horloge -p ExecStart --value
# ... sleep 30 ... <-- the old value, still applied
After a daemon-reload, systemd rereads the file and the restart finally applies the new command.sudo systemctl daemon-reload
sudo systemctl restart horloge
Not to be confused with reload
| Command | What is reloaded |
|---|---|
systemctl reload nginx |
The configuration of nginx (/etc/nginx/nginx.conf) |
systemctl daemon-reload |
The configuration of systemd (the unit files) |
daemon-reload is mandatory.The distributions under systemd
Debian, Ubuntu, RHEL and its derivatives (Rocky, AlmaLinux), Fedora, SUSE, Arch Linux. It is what you will meet in almost every professional environment.The notable exceptions
| Distribution | Init system | Why this choice |
|---|---|---|
| Alpine Linux | OpenRC | Lightness, widely used as a base image for containers |
| Void Linux | runit | Simplicity, minimalist supervision |
| Devuan | sysvinit | Debian fork created explicitly to stay without systemd |
| Gentoo | OpenRC or systemd | The distribution leaves the choice at installation |
The case of containers
In a classic Docker image, there is no systemd: PID 1 is directly the application process (nginx, python, java). It is deliberate, a container does not need to initialise a machine.systemctl commands therefore do not work there.# In a running standard nginx container
docker exec my-nginx cat /proc/1/comm
# nginx
docker exec my-nginx systemctl status
# sh: 1: systemctl: not found
Two independent notions
| Directive | What it says | What it does not say |
|---|---|---|
After=postgresql.service |
"If PostgreSQL must start, wait for it to be started before starting me" | It does not ask for PostgreSQL to start |
Requires=postgresql.service |
"PostgreSQL must be started with me, and if it is stopped, stop me" | It does not say in which order: its failure to start only blocks me if After= is also set (systemd.unit(5) manual) |
The trap
ARequires= alone starts both units in parallel. Your application can therefore try to open a connection before the database is ready: the failure only shows up one boot out of five, when the machine is slow.The right combination
[Unit]
Description=Billing API
Requires=postgresql.service
After=postgresql.service
Wants= is the weak variant of Requires=: systemd tries to start the other unit, but goes on even if it fails. It is the default choice for a non-vital dependency, such as a cache.Official resources
The reference stays the project documentation: the manpages below are those of the systemd version installed, also readable offline with man.
- systemd.io, the project site
- systemd.unit manpage, the reference of the directives common to all units
- systemd.service manpage, the directives specific to services
- systemctl manpage, the reference of the client command