Dependencies and Startup Order

Dependencies and Startup Order

Service dependencies between services. Parallel vs. sequential startup. Detecting and resolving cyclic dependencies.

A web server requires functioning network interfaces. A database requires mounted file systems. Services do not start in an arbitrary order—dependencies between services determine the boot sequence. The init system resolves these dependencies and starts services in the correct order.

Types of Dependencies

Service dependencies describe relationships between services. These relationships define order and necessity:

Startup Ordering (Ordering):

A service must start before or after another service. A web server should start after the network—without networking it cannot accept connections. Ordering defines only the sequence, not the requirement. A service can start after another service even if the first one did not start successfully.

Hard Dependencies (Requirements):

A service absolutely requires another service. A web application that uses PostgreSQL cannot function without a running database. If the database fails to start, the web application must not start either. Hard dependencies enforce success requirements.

Soft Dependencies (Wants):

A service benefits from another service but does not strictly require it. A logging service can optionally use a remote log server. If the remote server is unavailable, local logging still works. Soft dependencies allow fallbacks.

Conflicts (Conflicts):

Two services cannot run simultaneously. A system with multiple DNS servers (unbound, dnsmasq) may enable only one of them—both would attempt to bind to port 53. Conflicts prevent problematic combinations.

Declaring Dependencies

Init systems use different methods to define dependencies:

Declarative Dependencies (systemd):

Services declare their dependencies in configuration files. A web server defines After=network.target and Requires=postgresql.service. The init system reads these declarations and automatically calculates the startup order. Changing dependencies does not require code changes.

Implicit Ordering (SysV Init):

Numbered directories define the startup sequence. /etc/rc3.d/S10network starts before /etc/rc3.d/S20apache2. The numbers (10, 20) determine the order. Lower numbers start earlier. This method is simple but inflexible—changing the startup order requires renaming symbolic links.

Script-Based Checks (BSD Init):

Services check whether dependencies are satisfied in their startup scripts. A web server script tests whether networking is available before starting. This method is transparent but requires dependency-checking logic in every script.

Parallel vs. Sequential Startup

The way an init system processes dependencies has a significant impact on boot time:

Sequential Startup (SysV Init, BSD Init):

Services start one after another in a fixed order. Service A starts completely, then service B starts, then service C. Simple and predictable, but slow. Modern hardware with multiple CPU cores is not used efficiently—only one service starts at a time.

A typical server boot with sequential startup:

  1. Initialize networking (2 seconds)
  2. Start database (3 seconds)
  3. Start web server (1 second)
  4. Start SSH server (0.5 seconds)

Total time: 6.5 seconds, even though SSH and the web server do not require the database.

Parallel Startup (systemd):

Services without mutual dependencies start simultaneously. The SSH server, web server, and logging daemon can start in parallel if they do not depend on each other. This makes optimal use of modern multi-core CPUs. More complex, but significantly faster.

The same server boot with parallel startup:

  1. Initialize networking (2 seconds)
  2. In parallel: Database + SSH + Logging (3 seconds—the database is the slowest service)
  3. Web server starts after the database (1 second)

Total time: 6 seconds. SSH and logging make use of the waiting time while the database starts.

Socket Activation and Parallelization:

Socket activation allows even more aggressive parallelization. The init system starts services in parallel even when dependencies exist. A service that requires another service blocks on the first connection until the dependent service has started. This is the most complex method, but it provides the fastest boot.

Dependency Graphs

The dependency relationships between services form a directed graph. Each service is a node, and each dependency is an edge:

Graph Structure:

A simple web server setup as a graph:

network.target
    ↓
postgresql.service
    ↓
webapp.service
    ↓
nginx.service

nginx requires webapp, webapp requires PostgreSQL, and PostgreSQL requires networking. The graph clearly shows the dependency chain.

Complex Graphs:

Real-world systems have more complex dependency graphs with branches:

        network.target
        ↙     ↓      ↘
    sshd  postgresql  nginx
           ↓
        webapp

Networking is a prerequisite for multiple services. PostgreSQL is required only for webapp. SSH and nginx have no database dependency.

Graph Resolution:

The init system calculates a valid startup order from the dependency graph. Topological sorting finds a sequence that respects all dependencies. Multiple valid startup orders may exist—the init system chooses one of them.

Circular Dependencies

Circular dependencies occur when service A depends on service B and service B depends on service A. Circular dependencies prevent successful startup:

Detecting Cycles:

Service A requires service B. Service B requires service C. Service C requires service A. A cycle is created—no service can start because each one is waiting for another.

A → B → C → A

None of the three services has its prerequisites satisfied. The init system detects such cycles and refuses to start them.

Resolution Strategies:

Weaken Dependencies:

Change a hard dependency (Requires) into a soft dependency (Wants). The cycle remains, but the services can start even if the dependencies are not satisfied.

Ordering Without a Dependency:

Use startup ordering (Before/After) without a success requirement (Requires/Wants). Service A starts after service B but does not strictly require it.

Remove Dependencies:

Identify and remove the unnecessary dependency. Circular dependencies are often configuration errors—two services that require each other should normally be a single service.

Practical Example:

A mail server requires DNS resolution. The DNS server sends status emails through the mail server. A cycle is created. Solution: The DNS server uses an external mail server for status emails or does without email notifications until the mail server is available.

Differences Between Init Systems

The three major init system families handle dependencies differently:

The systemd Approach:

Comprehensive dependency declarations in unit files. Supports After=, Before=, Requires=, Wants=, Conflicts=, BindsTo=, and PartOf=. Automatically calculates the startup order from the dependency graph. Starts all possible services in parallel. Socket activation enables even more aggressive parallelization. A complex system with maximum flexibility and performance.

The SysV Init Approach:

Numbered startup order in runlevel directories. S10network, S20postgresql, S30nginx—the number determines the sequence. No explicit dependency declarations. Strictly sequential startup. Easy to understand but slow during boot. Changing dependencies requires renaming symbolic links.

The BSD Init Approach:

rcorder reads dependency comments from startup scripts:

# PROVIDE: webapp
# REQUIRE: postgresql networking
# BEFORE: nginx

rcorder sorts services based on these comments. It automatically calculates a valid startup order. Startup remains sequential, but the ordering is calculated automatically. A compromise between the complexity of systemd and the simplicity of SysV Init.

Target-Based Synchronization

Targets or milestones group related services together:

Concept:

A target is a virtual service that is considered “reached” once all dependent services are running. network.target is reached when the network interfaces are active. multi-user.target is reached when all multi-user services are running.

Usage:

Services declare dependencies on targets rather than on individual services. A web server requires network.target, not specific networking services. Targets abstract implementation details—which networking service is running is irrelevant, only that networking is functional.

Boot Sequence:

The boot process reaches targets sequentially:

  1. local-fs.target (file systems mounted)
  2. network.target (network available)
  3. multi-user.target (multi-user services running)
  4. graphical.target (graphical environment running)

Services can wait for whichever target is appropriate. A service that requires networking waits for network.target. A graphical service waits for graphical.target.

Dependency Failure Handling

When dependent services fail to start, the init system must react:

Hard Dependencies Fail:

Service A has a hard dependency (Requires) on service B. Service B fails to start. Service A is not started. The dependency chain is interrupted. The logs document the failed dependency.

Soft Dependencies Tolerate Failures:

Service A has a soft dependency (Wants) on service B. Service B fails to start. Service A still starts. Functionality may be limited. The logs warn about the missing optional dependency.

Timeout Handling:

A service does not start within a reasonable amount of time. The init system does not wait indefinitely—typically there is a 90-second timeout. After the timeout, the service is considered failed. Dependent services are not started if a hard dependency exists.

Restart Propagation:

A service with many dependent services is restarted. Should the dependent services also be restarted? BindsTo= forces dependent services to restart. A normal Requires= relationship allows dependent services to continue running.

Practical Scenarios and Debugging

Dependency problems appear in various situations:

Service Starts Too Early:

A web server starts before the network interfaces are available. It binds to 127.0.0.1 instead of the correct IP address. Solution: Add After=network.target to the service configuration.

Service Starts Too Late:

A monitoring service starts after all other services. It misses important boot events. Solution: Set Before=multi-user.target so the service starts earlier in the boot process.

Boot Hangs:

The system waits indefinitely during boot. A service with a long startup time blocks the boot process. The logs show which service is hanging. Increase the timeout or optimize the service’s startup time.

Circular Dependency Prevents Startup:

Several services fail to start. The logs report “Ordering cycle found.” Visualize the dependency graph and remove unnecessary dependencies.

Parallel Startup Causes Race Conditions:

A service sometimes starts successfully and sometimes does not. A race condition occurs during parallel startup. A dependency is missing from the configuration. The service starts before its prerequisite is available. Add the correct ordering dependency.

Summary of Init System Philosophies

The way dependencies are handled highlights the fundamental differences:

systemd: Maximum flexibility and performance through declarative dependencies and parallel startup. A complex system with a steep learning curve. Optimized for fast boot times on modern hardware.

SysV Init: Simplicity through numbered startup order. Predictable and transparent. Slow because of sequential startup. Well suited for systems where boot time is unimportant.

BSD Init: A compromise between the two. Automatic startup ordering based on script comments. Sequential startup with flexible configuration. A balance between simplicity and features.

The choice of init system determines how dependencies are defined, verified, and resolved. All three approaches solve the same problem—starting services in the correct order.


Further Topics

This series explains distribution-independent init concepts. Distribution-specific articles show the concrete implementation:

  • Debian Fundamentals (planned): systemd with Debian-specific characteristics
  • OpenBSD Fundamentals (planned): rcctl and the rc.d system
  • Arch Fundamentals (planned): systemd with Arch-specific units

These articles assume an understanding of init concepts and demonstrate system-specific commands and configuration.


Software and Versions Used

This guide applies to:

  • Unix/Linux systems: General dependency concepts
  • Context: Distribution-independent — systemd, SysV Init, BSD Init
  • Revision: October 2025

Michael of the Dragons

develops books, software, and open frameworks around technical systems, digital independence, and durable software architectures.
More about Michael →
Dependencies and Startup Order
Previous Article → Service Management: Controlling Services