Linux boot simulator: systemd step by step
After the firmware, boot loader and kernel, systemd brings the system up by starting units in parallel wherever their dependencies allow. Describe the units, their start-up times and dependencies, and see the boot timeline and the output of systemd-analyze: what took longest, what was on the critical chain, and what failed.
A line is name.service 1.2s type=notify after=a.target,b.service wants=c.service requires=d.mount. Types: simple (the default), exec, notify, oneshot, forking; targets take no time.
Startup finished in 3.2s (firmware) + 0.9s (loader) + 1.3s (kernel) + 2.1s (initrd) + 6.35s (userspace) = 13.85s multi-user.target reached after 6.35s in userspace.
multi-user.target @6.35s
└─app.service @6.35s
└─docker.service @4.05s +2.3s
└─network-online.target @4.05s
└─systemd-networkd-wait-online.service @550ms +3.5s
└─systemd-networkd.service @300ms +250ms
└─systemd-udevd.service @0ms +300ms 3.5s systemd-networkd-wait-online.service
2.3s docker.service
1.4s [email protected]
1.1s containerd.service
600ms app.service
400ms var-lib.mount
300ms systemd-udevd.service
250ms systemd-networkd.service
180ms systemd-journald.service
150ms boot.mount
120ms nginx.service
80ms sshd.service
… 2 moreHow to use the simulator
Each line describes one unit: its name, how long it takes to start, its type and its dependencies, much as the [Unit] and [Service] sections of a unit file would. The simulator pulls in every unit the boot target needs, starts each as soon as the units it is ordered after are up, and draws the result. Pick a unit to fail to see what else it takes down, and set the firmware, loader, kernel and initrd times to complete the systemd-analyze time line.
From power-on to systemd
When a Linux machine powers on, the firmware (UEFI on current hardware) initialises the hardware and runs a boot loader from disk, such as GRUB or systemd-boot. The boot loader loads the kernel and an initramfs, a small archive with the drivers and tools needed to find and mount the real root file system, which may be encrypted, on LVM or on the network. The kernel unpacks the initramfs and runs its init; once the root file system is mounted, control passes to systemd on the real root as process 1, which starts everything else.
systemd-analyze time reports these phases separately when the firmware and boot loader pass on their timings, as UEFI with systemd-boot does: in the server example, Startup finished in 3.2s (firmware) + 0.9s (loader) + 1.3s (kernel) + 2.1s (initrd) + 6.35s (userspace) = 13.85s. The last part, user space, is what the unit graph decides, and usually the part you can change.
Units and dependencies
systemd manages units: services, mounts, sockets, devices and targets, which are named groups such as multi-user.target that stand for a state of the system. Booting means starting the default target, and systemd builds a transaction of every unit it pulls in. Two kinds of setting shape it, and they are independent:
- Requirement:
Wants=pulls a unit in and does not care if it fails;Requires=pulls it in and makes this unit fail too if it fails and they are ordered.WantedBy=multi-user.targetin an[Install]section adds a Wants= to the target when the unit is enabled. - Ordering:
After=andBefore=only say which starts first, for units that are both in the transaction.
Units with no ordering between them start in parallel, which is where systemd gets its speed. Targets have one extra rule: a target is automatically ordered after the units it wants or requires, unless an ordering between them is already set, so reaching multi-user.target means everything it pulled in has started. The server example pulls in 20 units.
Reading systemd-analyze
systemd-analyze blame lists units by how long they took to start. It is easy to misread: units start in parallel, so the longest one may not have delayed anything. systemd-analyze critical-chain answers the better question, what the target was actually waiting for. It starts at the target and, at each level, follows the dependency that finished last. In the server example:
multi-user.target @6.35s
└─app.service @6.35s
└─docker.service @4.05s +2.3s
└─network-online.target @4.05s
└─systemd-networkd-wait-online.service @550ms +3.5s
└─systemd-networkd.service @300ms +250ms
└─systemd-udevd.service @0ms +300ms Read it from the bottom: udevd, then networkd, then 3.5 s in systemd-networkd-wait-online.service, then Docker, then the app. The 1.4 s file system check near the top of blame ran in parallel and is not on the chain, so making it faster would not shorten the boot.
Waiting for the network
network.target only means the network management stack has been started; network-online.target means a wait-online service has decided the network is configured, which can take seconds and depends on DHCP, link negotiation and the definition of "online". Anything ordered after network-online.target waits for it. In the example, Docker and nginx do; removing those dependencies cuts user space from 6.35s to 5.2s, and the critical chain moves to the file system check and containerd. Services that only need to listen on a socket can usually order after network.target, or bind to any address, and leave network-online.target to things that genuinely need a remote host at start-up.
Service types and readiness
After= waits until a unit has "started", and what that means depends on the service's Type=. For simple, the default, systemd counts the service as started as soon as it forks the process; for exec, once the program has been executed. Neither waits for the program to be ready. oneshot waits for the process to exit, forking for the parent to exit after starting a daemon, and notify for the service to send READY=1 through sd_notify. In the Type=simple trap example the API is ordered after a database that needs 4 s to be ready, but the database is Type=simple, so the API starts at once and the target is reached after 300ms, long before the database can take connections. Making the database Type=notify fixes the order.
Failures and cycles
When a unit fails, units that Requires= it and are ordered after it are not started and get "dependency failed". In the A failing service example, Docker fails and app.service follows; the target is still reached, because it only wants those services. Making containerd.service fail instead stops nothing else, since Docker only Wants= it. Some units react to failure: local-fs.target has OnFailure=emergency.target, which is why a failed mount drops the machine into emergency mode rather than continuing.
An ordering cycle, such as a after b, b after c and c after a, has no valid order. systemd detects it when building the transaction, logs "Found ordering cycle" with the units involved, and deletes one of the jobs to break it, so a unit can silently not start until someone reads the journal.
About the model
Start-up times are fixed numbers you choose, and a unit starts as soon as everything it is ordered after has started; real systems also wait on devices, disks and CPU. Services' default dependencies (on sysinit.target and basic.target) are not added automatically, so the examples write them out. Conflicts=, OnFailure=, socket and path activation and job modes are not modelled. The output follows the format of systemd-analyze, with times measured from the start of user space.
Linux boot and systemd questions and answers
What is the difference between Requires= and After= in systemd?
Requires= (and the weaker Wants=) says which other units must be started along with this one; After= and Before= say in what order. They are independent: two units that require each other but have no ordering start at the same time. A unit that needs another to be running first needs both Requires= (or Wants=) and After=.
How do I read systemd-analyze critical-chain?
It prints the chain of units that held up the target, each line indented under the unit that waited for it. The time after @ is when the unit started, relative to the start of user space, and the time after + is how long it took. In this page's server example the chain runs app.service ← docker.service ← network-online.target: Docker waits for network-online.target, which waits for systemd-networkd-wait-online.service.
Why does network-online.target slow down boot?
Units ordered after it wait until a wait-online service decides the network is fully configured, which can take seconds or time out. In the example, removing every dependency on network-online.target cuts user space from 6.35s to 5.2s. Services that can bind to an address without the network being up should order after network.target instead.
What does Type=simple mean for ordering?
systemd treats a Type=simple service as started as soon as its process is forked, so units ordered after it start straight away, before it is ready. In the Type=simple example the API starts at 0 ms while the database needs 4 s, and the target is reached after 300ms. Type=notify, where the service tells systemd when it is ready, fixes this.
What does "Dependency failed" mean in systemd?
A unit was not started because a unit it Requires= and is ordered After= failed. In the example, when docker.service fails, app.service gets "dependency failed". A Wants= dependency may fail without stopping anything: when containerd.service fails, Docker, which only Wants= it, still starts.
What is an ordering cycle in systemd?
Units ordered after each other in a loop, such as a after b, b after c and c after a. No order satisfies them, so systemd logs "Found ordering cycle" and removes one of the jobs from the transaction to break it, which means some unit silently does not start.
Further reading
- systemd manualbootup(7)The boot process and the chart of standard targets.
- systemd manualsystemd.unit(5)Wants=, Requires=, After=, Before= and how they combine.
- systemd manualsystemd.service(5)Type= and when a service counts as started.
- systemd manualsystemd-analyze(1)time, blame, critical-chain and the other analysis commands.
- systemd.ioRunning Services After the Network Is Upnetwork.target, network-online.target and when to use which.