Skip to content
FreeBSDFix July 11, 2026 5 min read

Fixing Slow Boot Times Caused by Excessive rc.d Service Dependencies

How to find which rc.d script is actually holding up boot, distinguish a genuinely slow service from an unnecessary dependency chain, and fix each case differently.

A FreeBSD system that used to boot in a few seconds and now takes noticeably longer almost always has a specific, identifiable cause hiding in the rc.d sequence — either one script is genuinely slow (waiting on a network resource, a slow disk operation, a service with a long startup routine) or a dependency chain has grown longer than it needs to be, serializing work that could run in parallel. These have different fixes, and guessing which one you’re dealing with wastes time better spent measuring it directly.

Measuring where the time actually goes

rc.d’s own verbose boot logging, combined with timestamps, localizes the problem precisely instead of guessing from perceived boot duration. Boot with verbose logging enabled (either permanently via boot_verbose="YES" in /boot/loader.conf, or one-time from the boot loader prompt with boot -v), then after boot, correlate /var/log/messages timestamps against which rc.d script was running at each point:

grep -E '^\w+ +[0-9]+ [0-9:]+' /var/log/messages | head -100

More directly, rc.d’s own scripts can be timed individually by re-running the sequence non-interactively with timing wrapped around each invocation — but the faster diagnostic in practice is simply watching console output during a normal boot with a stopwatch running, since most rc.d scripts print a startup message (Starting <name>.) as they begin, and a large gap between two consecutive “Starting” lines directly identifies the slow one.

Case 1: one script is genuinely slow

If the delay is concentrated at one specific script, the fix depends entirely on what that script does. Common genuine culprits:

A service waiting on DNS or network resolution that isn’t available yet. Some daemons perform a DNS lookup or attempt an outbound network check during their own startup sequence, and if the network isn’t fully up (or a configured DNS server is unreachable) at the moment they run, they block on a timeout rather than failing fast. Check the specific service’s own configuration for anything resembling a startup health check or remote dependency, and either fix the underlying network reachability or, if the check is unnecessary for local operation, disable it in that service’s own config.

fsck running against a filesystem that wasn’t cleanly unmounted. A dirty UFS filesystem triggers a full fsck during boot, and depending on filesystem size, this can add substantial time. Check /var/log/messages for fsck-related lines around the point where boot slows down; if this is a repeated pattern (not a one-time recovery from an actual unclean shutdown), the underlying cause is the system not shutting down cleanly, not the fsck itself — investigate why shutdowns aren’t clean rather than treating the resulting fsck delay as the primary problem.

A daemon with a genuinely slow initialization routine — building a large cache, performing a database consistency check, or similar. This is the one category where “the script is doing real, necessary work slowly” is the actual answer, and the fix is either service-specific tuning (many daemons have startup-time tradeoffs configurable in their own config files) or accepting the cost.

Case 2: an unnecessarily long or serialized dependency chain

If no single script takes disproportionately long, but the overall sequence has grown, the likely cause is a longer-than-necessary dependency chain — services that don’t actually need to wait for each other but are ordered sequentially because of an overly broad REQUIRE line, most commonly picked up from a third-party port’s rc.d script rather than the base system’s own scripts.

rcorder -s nostart /etc/rc.d/* /usr/local/etc/rc.d/* prints the computed boot order without executing anything, which is the fastest way to see the actual dependency-driven sequence rather than inferring it from log timestamps:

rcorder -s nostart /etc/rc.d/* /usr/local/etc/rc.d/* 2>/dev/null

Scan this output for scripts positioned much later than you’d expect given what they actually do — a script with REQUIRE: NETWORKING DAEMON LOGIN when it genuinely only needs NETWORKING is artificially serialized behind everything LOGIN depends on, for no functional reason. This is common with less carefully written third-party rc.d scripts that copy a broad REQUIRE line from an example rather than declaring only what they actually need.

Fixing an overly broad dependency

If a specific port-installed script’s REQUIRE line is broader than necessary, the direct fix is editing that script’s header (or, more durably, filing the fix upstream with the port maintainer so it doesn’t get overwritten on the next update):

# Before: unnecessarily broad
# REQUIRE: NETWORKING DAEMON LOGIN

# After: only what's actually needed
# REQUIRE: NETWORKING

Because rc.d’s ordering is computed from these declarations rather than a hardcoded sequence, a corrected REQUIRE line takes effect on the very next boot with no other changes needed — rcorder will simply place the script earlier, since its actual dependencies are now satisfied sooner.

When parallelism is the real lever

FreeBSD’s rc.d supports parallel startup of scripts whose dependencies allow it, controlled by rc_conf’s rc_startmsgs/parallel-boot related knobs and enabled via rc_debug/early_late_divider mechanics on modern releases (check rc.conf(5) for the exact current knob names on the release in use, since these have been refined across versions). If the dependency graph genuinely doesn’t force serialization but boot still runs everything one-at-a-time, confirming that parallel boot is actually enabled is worth checking before assuming the dependency graph itself needs restructuring — a correctly parallelizable graph running serially due to a disabled setting is a different, simpler fix than an actually-too-serial dependency chain.

Distinguishing the two categories quickly

The practical test: if rcorder‘s computed sequence looks reasonable and matches what you’d expect given each service’s real requirements, but boot is still slow, the problem is Case 1 — something is actually taking real time to do real work. If the sequence itself looks longer or more serialized than the services’ actual needs justify, it’s Case 2 — a dependency-declaration problem, fixable without touching the services themselves at all. Treating a Case 1 problem as if it were Case 2 (rewriting REQUIRE lines that were already correct) accomplishes nothing; treating a Case 2 problem as Case 1 (tuning a service that was never actually slow) wastes effort on the wrong target. Measuring first, with verbose boot logs and rcorder’s own output, is what tells you which one you actually have.