A reboot that takes 90 seconds is 90 seconds of downtime and 90 seconds of a customer watching a spinner. On a typical 2 vCPU cloud instance, a stock Debian or Ubuntu image frequently needs 45-70 seconds to reach a usable login prompt, and most of that time is spent in services nobody asked for. This walkthrough uses systemd-analyze to find the slow units, then removes or reorders them for measurable gains.
Establish a Baseline Before Touching Anything
Never optimize blind. Capture three reboots so you can discount network-dependent variance, then record the numbers.
for i in 1 2 3; do
reboot
done
# after each boot:
systemd-analyze
systemd-analyze blame | head -25
systemd-analyze critical-chain
On a representative test box (2 vCPU, 2 GB RAM, NVMe backing store, Ubuntu 22.04) the baseline looked like this: firmware 0.9s, loader 0.3s, kernel 2.1s, userspace 41.2s, for a total of 44.5s. Userspace is almost always where the win lives.
| Unit | Baseline time | After tuning |
|---|---|---|
| cloud-init.service | 11.8s | 2.0s (network-wait) |
| systemd-networkd-wait-online | 9.4s | 0s (masked) |
| apt-daily.service | 4.6s | 0s (masked) |
| snapd.seeded.service | 3.5s | 3.4s (kept) |
| [email protected] | 2.9s | 1.2s |
| Total userspace | 41.2s | 7.9s |
The Usual Suspects on Cloud Images
systemd-networkd-wait-online blocks the boot path until every interface is configured. It returns success after 120s by default even if a NIC never comes up, which silently eats two minutes on misconfigured hosts. Mask it unless something genuinely depends on being online during boot.
systemctl mask systemd-networkd-wait-online.service
cloud-init is the other big cost. If you bake your image or use provisioning scripts you trust, disable the network stage and keep only the local datasource. Edit /etc/cloud/cloud.cfg.d/99-fastboot.cfg:
datasource_list: [ NoCloud ]
network: {config: disabled}
apt-daily and apt-daily-upgrade already run on timers, but their .service units still land in the boot transaction. Mask them so updates happen from the timer, not the boot path. If you manage patching through a scheduled maintenance window (a common pattern on a managed versus unmanaged server), this is safe.
Pruning vs. Masking: Know the Difference
systemctl disable removes the symlink but leaves the unit available. systemctl mask points the unit at /dev/null, so nothing can start it, including another unit’s dependency graph. Use mask for things you are certain you never want, and disable when a socket or timer may still activate it later. Always check what depends on a unit first:
systemctl list-dependencies --reverse apt-daily.service
Shave the Kernel Phase Too
Kernel time rarely drops below ~2s, but you can trim it. Removing unused modules and firmware via initramfs-tools blacklists, and switching the console to quiet, saves a few hundred milliseconds. On bare-metal or KVM with many NICs, the network driver probe is often the largest kernel-phase cost; narrowing /etc/modules helps.
Verifying the Result
Reboot three more times and compare systemd-analyze output. A realistic target for a small web or API instance is sub-10s userspace and sub-13s total. If your boot is dominated by a single vendor agent you cannot remove, check whether it supports an async or socket-activated mode before accepting the cost. Boards that stay high due to disk contention may instead need I/O tuning, which pairs naturally with the choices covered in the cloud VPS benefits overview.
# final check: produce a per-phase SVG and diff two boots
systemd-analyze plot > /tmp/boot.svg
systemd-analyze --no-pager time
Treat boot time as a tracked metric, not a one-time cleanup. Re-run blame after every major package upgrade, because a new agent dropped in by a provider is the most common cause of a boot regression.



Leave a Reply
You must be logged in to post a comment.