Where to start First steps track · 15 stages
Linux Labs

systemd-analyze: what slows the boot and what is wrong in a unit

11 min Ubuntu 24.04 · systemd 255

Breaks the boot down by time and by the chain of unit dependencies, and checks a unit file before the service runs.

Cheat sheet

8 commands Printable version
  • systemd-analyze how long the boot took
  • systemd-analyze blame | head units by start-up time
  • systemd-analyze critical-chain chain up to the boot target
  • systemd-analyze critical-chain nginx.service what a unit waited for
  • systemd-analyze verify ./app.service check a unit file
  • systemd-analyze verify app.service check an installed unit
  • systemd-analyze plot > boot.svg boot chart
  • systemd-analyze security app service hardening score

Overview

systemd records when each unit started and finished starting. systemd-analyze reads these times and shows them three ways: the boot total, a list of units and a chain of dependencies. The same command checks a unit file before systemd reads it.

Without a subcommand it prints the boot total of the lab machine:

text
student@ubuntu:~$ systemd-analyze
Startup finished in 2.902s (kernel) + 6.382s (userspace) = 9.285s
multi-user.target reached after 6.356s in userspace.

kernel is the kernel and initramfs before systemd starts, userspace is systemd's work until the boot is done. The systemd-boot loader on UEFI also reports firmware and loader; GRUB does not report these times. The second line names the boot target and when systemd reached it.

How long each unit took — blame

text
student@ubuntu:~$ systemd-analyze blame | head -6
2.368s apparmor.service
2.208s labs-ssh-hostkeys.service
1.151s dev-vda4.device
 892ms containerd.service
 835ms rsyslog.service
 671ms ldconfig.service

The time on a line is how long the unit took to start: from the "start" command to the active state. Units start in parallel, so the times add up to more than the whole boot, and the first line may have delayed nobody. critical-chain shows who actually held the boot.

The time depends on the service type. For Type=oneshot it includes all of the ExecStart= work. A Type=simple service counts as started as soon as systemd creates its process — even if the program then spends a minute getting ready to accept requests.

Services started after the boot are on the list too. A test service with ExecStart=/bin/sleep 4 and Type=oneshot came first:

text
4.007s lab-slow.service

The critical chain

text
student@ubuntu:~$ systemd-analyze critical-chain
multi-user.target @6.356s
└─containerd.service @4.211s +892ms
  └─dbus.service @4.060s +131ms
    └─basic.target @4.045s
      └─sockets.target @4.044s
        └─lxd-installer.socket @4.030s +11ms
          └─sysinit.target @3.989s
            └─apparmor.service @1.618s +2.368s
              └─local-fs.target @1.603s

Two lines before the tree explain the marks: after @ is when the unit became active (seconds from the start of userspace), after + is how long it took to start. Read from the bottom up: each unit waited for the one below it. apparmor.service took 2.368 s, and sysinit.target got it only at 3.989 s — everything above started after that. Units started after the target was reached are not in its chain.

The chain follows After=: of the units this one starts after, it picks the one that finished last. With a unit name it shows what that unit itself waited for; the first line has only +:

text
student@ubuntu:~$ systemd-analyze critical-chain lab-slow.service
lab-slow.service +4.007s
└─nginx.service @4.114s +207ms
  └─basic.target @4.045s
    └─sockets.target @4.044s

lab-slow.service took 4 s, and before that it waited for nginx.service — it has After=nginx.service.

Checking a unit — verify

systemd-analyze verify loads a unit the way systemd does on daemon-reload and prints what it did not like. Give a file by path — you can check it before copying it to /etc/systemd/system; give an installed unit by name. What verify prints for mistakes in a unit:

What is in the unit What verify prints Code
Afer= instead of After= Unknown key name 'Afer' in section 'Unit', ignoring. 0
RestartSec=fast Failed to parse sec value, ignoring: fast 0
Type=simpel Failed to parse service type, ignoring: simpel 0
Wants= on a unit that does not exist nothing 0
WantedBy=multi-user without .target nothing 0
the ExecStart= program is missing Command /usr/local/bin/demo-app is not executable: No such file or directory 1
no ExecStart= Service has no ExecStart=, ExecStop=, or SuccessAction=. Refusing. 1
Requires= on a unit that does not exist Failed to create app.service/start: Unit postgres.service not found. 1
two units with After= on each other Found ordering cycle on …, Transaction order is cyclic 1

Code 0 only means the unit will load. systemd skips a misspelled line and a wrong value, and the service runs with the defaults: without After=, with type simple. A clean unit is empty output.

Options

13

Subcommands and options are in the table below; the full list is in systemd-analyze --help and man systemd-analyze. Boot times and verify work as a regular user, without sudo.

Option What it does Example
time The boot total — kernel, userspace and when the target was reached; same as no subcommand. systemd-analyze
blame Units by start-up time, longest first; does not show who waited for whom. systemd-analyze blame | head
critical-chain [UNIT] The chain of units by After= that held the boot target or the given unit. systemd-analyze critical-chain nginx.service
verify FILE|UNIT Load a unit the way systemd does and print errors; empty output means a clean unit. systemd-analyze verify /etc/systemd/system/app.service
plot An SVG chart of the boot — each unit as a bar on a timeline. systemd-analyze plot > boot.svg
security [UNIT] A 0–10 score of a service's restrictions — see the systemd-hardening article. systemd-analyze security nginx
calendar EXPR Check a timer's OnCalendar= expression and show the next run. systemd-analyze calendar 'Mon..Fri 03:00'
--no-pager Print straight to the terminal, without less. systemd-analyze blame --no-pager
cat-config PATH Show a config together with all its .d files in the order they apply. systemd-analyze cat-config sysctl.d
timespan SPAN Parse a time span (90s, 1h 30min) the way systemd will read it. systemd-analyze timespan 90s
unit-paths The directories systemd searches for units, in order. systemd-analyze unit-paths
--fuzz SPAN In critical-chain, also show units that finished within this time of the chosen one. systemd-analyze critical-chain --fuzz 1s
--user Look at the user's service manager instead of the system one. systemd-analyze --user blame

Nothing found. Try a shorter query.

Recipes

3

Find what delays the boot

  1. systemd-analyze — how long the boot took and to which target.
  2. systemd-analyze critical-chain — the unit with the largest + in the chain.
  3. systemd-analyze critical-chain <unit> — what it waited for.
  4. systemctl cat <unit> — its After=, Wants= and Requires=. A common case is a service waiting for the network, see boot-targets.
  5. After the fix — verify, sudo systemctl daemon-reload and a reboot: the times of this boot are already recorded and will not change.

Check a unit before starting it

bash
systemd-analyze verify ./app.service      # the file before installing
sudo cp app.service /etc/systemd/system/
sudo systemctl daemon-reload
systemd-analyze verify app.service        # installed, by name
systemd-analyze verify app.service 2>&1 | grep . || echo clean

The last line suits a script: verify prints its messages to stderr, and empty output is the sign of a clean unit. The exit code alone cannot tell you that.

Did the service come up at boot by itself

text
student@ubuntu:~$ systemctl show nginx \
> -p ActiveEnterTimestampMonotonic,NRestarts
NRestarts=0
ActiveEnterTimestampMonotonic=7224455

ActiveEnterTimestampMonotonic is the microseconds from the kernel start to the moment the service became active: 7.2 s, inside the boot (9.285 s by systemd-analyze). A service started by hand after login shows a large number. NRestarts is how many times systemd restarted it after a crash.

Troubleshooting

5

Bootup is not yet finished

text
Bootup is not yet finished
  (org.freedesktop.systemd1.Manager.FinishTimestampMonotonic=0).
Please try again later.
Hint: Use 'systemctl list-jobs' to see active jobs

The boot is still going: some job has not finished — a service with a long start or one waiting for the network. systemctl list-jobs shows which; systemctl is-system-running prints starting meanwhile. The job ends by itself or by TimeoutStartSec= — 90 s by default; a Type=oneshot service has no limit by default and holds the boot until it exits.

Removed the first line of blame, and the boot is just as slow

blame shows how long each unit took to start, not who waited for whom. A unit that started in parallel with the rest did not hold the boot. Look at critical-chain: only what stands in the chain delays the boot.

verify is silent, and the service does not start

A misspelled key or a wrong value gives code 0 — read the output. If there is no output, the problem is not in the file: systemctl status <unit> shows the exit code, for example status=203/EXEC — the ExecStart= program is missing or not executable.

Unit … not found

Requires= or Wants= names a unit that does not exist: a typo, or the package is not installed. With Requires= the service does not start; with Wants= systemd silently skips the dependency. systemctl list-unit-files 'postgres*' shows the real name — here postgresql.service.

Transaction order is cyclic

text
Failed to start cyc-a.service: Transaction order is cyclic.
  See system logs for details.

The units wait for each other through After=. The journal names both: Found ordering cycle on cyc-b.service/start. At boot systemd breaks the cycle itself and drops one of the jobs — then one service silently does not start. Remove the extra After=.

If the units are linked by Requires=, systemctl start fails right away, as above. With a Wants= link systemd tries to break the cycle, and sudo systemctl restart may not return the prompt: both jobs hang in systemctl list-jobs as waiting. Ctrl+C only stops the command from waiting, the jobs stay; after fixing the unit and sudo systemctl daemon-reload they run by themselves. verify on such a cycle prints Found ordering cycle on …/start and Job …/start deleted to break ordering cycle, code 1.

See also

Loading…