funwithlinux guide

A Technical Overview of Systemd Service Dependencies

In the landscape of modern Linux systems, **systemd** has emerged as the de facto init system, responsible for bootstrapping the user space and managing system processes. At its core, systemd’s power lies in its ability to orchestrate services—daemons, applications, and background processes—efficiently. A critical aspect of this orchestration is **service dependencies**: the relationships that define how services interact, order, and rely on one another. Whether you’re a system administrator ensuring a web server starts only after the network is up, or a developer packaging an application that depends on a database, understanding systemd dependencies is key to building reliable, predictable systems. This blog dives deep into the technicalities of systemd service dependencies, covering types, configuration, tools, troubleshooting, and best practices.

Table of Contents

  1. Understanding Systemd Services: A Primer
  2. What Are Systemd Service Dependencies?
  3. Types of Systemd Service Dependencies
  4. Defining Dependencies in Unit Files
  5. Tools for Inspecting and Managing Dependencies
  6. Troubleshooting Common Dependency Issues
  7. Best Practices for Designing Service Dependencies
  8. Advanced Topics: Targets and Dependency Chains
  9. Conclusion
  10. References

1. Understanding Systemd Services: A Primer

Before diving into dependencies, let’s recap the basics of systemd services. A systemd service is a unit of work managed by systemd, defined by a unit file (typically with a .service extension). Unit files are stored in standard locations:

  • /usr/lib/systemd/system/: Distro-provided services.
  • /etc/systemd/system/: User-defined or overridden services (takes precedence).
  • /run/systemd/system/: Runtime-generated services.

A typical .service file has three main sections:

  • [Unit]: Metadata, descriptions, and dependencies (the focus of this blog).
  • [Service]: Service-specific configuration (executable path, restart policy, user, etc.).
  • [Install]: Configuration for enabling/disabling the service at boot (e.g., WantedBy=multi-user.target).

Services can be in states like active (running), inactive (stopped), failed (crashed), or activating (starting up). Dependencies govern how these states interact between services.

2. What Are Systemd Service Dependencies?

Service dependencies are relationships between two or more systemd units (usually services) that dictate:

  • Whether one service must start before another.
  • Whether one service requires another to function.
  • How services react to each other’s failures or restarts.

For example:

  • A web server (e.g., Nginx) depends on the network being available (network.target).
  • A database-backed application (e.g., a Python app) depends on the database (e.g., MySQL) being running.

Misconfigured dependencies can lead to failures (e.g., the app starts before the database, causing connection errors) or inefficiencies (e.g., unnecessary delays during boot).

3. Types of Systemd Service Dependencies

Systemd defines several dependency directives to model these relationships. Most live in the [Unit] section of a service file. Below are the key types:

Requires: Strict Runtime Dependencies

Directive: Requires=<service>
Behavior: Declares a strict dependency: the current service cannot function without the specified service. If the required service fails to start, the current service is not started. If the required service stops/crashes later, the current service is also stopped.

Example: A service myapp.service that requires mysql.service to run:

[Unit]  
Description=My Database-Backed App  
Requires=mysql.service  

Caveat: Requires does not control order (use After/Before for that). If myapp starts before mysql (due to parallelization), it may fail even with Requires=mysql.service.

Wants: Weak Recommendations

Directive: Wants=<service>
Behavior: A weaker version of Requires. The current service recommends the specified service but can start without it. If the wanted service fails, the current service continues running.

Use Case: Optional dependencies (e.g., a logging service that enhances the app but isn’t critical).

Example:

[Unit]  
Description=My App with Optional Logging  
Wants=logging.service  

Requisite: Pre-Flight Checks

Directive: Requisite=<service>
Behavior: Similar to Requires, but stricter: the specified service must already be active before the current service starts. If the requisite service is not running, the current service fails to start immediately (no attempt is made to start the requisite service).

Use Case: Services that depend on another service being pre-initialized (e.g., a plugin service requiring the main application to already be running).

Example:

[Unit]  
Description=My App Plugin  
Requisite=myapp-main.service  

BindsTo: Tight Coupling

Directive: BindsTo=<service>
Behavior: Stronger than Requires. The current service is tightly bound to the specified service: if the bound service stops (even normally), the current service is stopped. If the bound service is restarted, the current service is also restarted (unless Restart=... is configured otherwise).

Use Case: Services that are part of a single logical unit (e.g., a master-worker process pair).

Example:

[Unit]  
Description=Worker Process  
BindsTo=master.service  

After/Before: Ordering Control

Directive: After=<service> or Before=<service>
Behavior: Controls the start/stop order of services, but does not create a strict dependency. After=X means the current service starts after X starts. Before=Y means the current service starts before Y starts.

Key Point: After/Before only affect order, not whether the other service is running. Use with Requires/Wants to enforce both dependency and order.

Example: Ensure myapp starts after mysql and network.target:

[Unit]  
Description=My App  
Requires=mysql.service network.target  
After=mysql.service network.target  # Ensures order  

Conflicts: Mutual Exclusion

Directive: Conflicts=<service>
Behavior: If the current service starts, the specified service is stopped (and vice versa).

Use Case: Services that cannot run simultaneously (e.g., two web servers on the same port: nginx.service and apache2.service).

Example:

[Unit]  
Description=Nginx Web Server  
Conflicts=apache2.service  

OnFailure: Handling Service Failures

Directive: OnFailure=<service>
Behavior: Specifies a service to start if the current service fails (exits with non-zero status, crashes, etc.).

Use Case: Triggering alerts, fallbacks, or cleanup (e.g., sending an email on failure, or starting a backup service).

Example:

[Unit]  
Description=My Critical App  
OnFailure=send-alert.service  

4. Defining Dependencies in Unit Files

Dependencies are defined in the [Unit] section of a .service file. Let’s combine multiple directives into a realistic example: a Node.js app (nodeapp.service) that depends on:

  • network.target (network connectivity).
  • mysql.service (database).
  • Optional redis.service (caching).
  • Must start after mysql and network.target.
  • Sends an alert if it fails.

Sample Unit File: /etc/systemd/system/nodeapp.service

[Unit]  
Description=Node.js Web Application  
Documentation=https://example.com/docs  
Requires=mysql.service network.target  # Strict dependencies  
Wants=redis.service                    # Optional dependency  
After=mysql.service network.target redis.service  # Order: start after these  
OnFailure=alert-email.service          # Action on failure  

[Service]  
User=appuser  
WorkingDirectory=/opt/nodeapp  
ExecStart=/usr/bin/node server.js  
Restart=on-failure  # Restart on non-zero exit code  

[Install]  
WantedBy=multi-user.target  # Enable at boot under multi-user.target  

Key Notes:

  • [Install] section’s WantedBy=multi-user.target links the service to multi-user.target (a boot target for non-graphical systems). When enabled (systemctl enable nodeapp), a symlink is created in /etc/systemd/system/multi-user.target.wants/.
  • Requires and Wants define runtime dependencies, while WantedBy in [Install] defines boot-time activation (which targets pull in the service).

5. Tools for Inspecting and Managing Dependencies

Systemd provides built-in tools to visualize and debug dependencies. Here are the most useful:

systemctl list-dependencies

Lists all dependencies of a service (both direct and indirect).

Examples:

  • List dependencies of nodeapp.service (what nodeapp depends on):
    systemctl list-dependencies nodeapp.service  
  • List reverse dependencies (what depends on nodeapp):
    systemctl list-dependencies --reverse nodeapp.service  

systemctl show

Shows low-level properties of a service, including dependencies.

Example: View all Requires, Wants, and After directives for nodeapp:

systemctl show nodeapp.service -p Requires -p Wants -p After  

systemd-analyze

Visualizes boot and dependency chains.

  • systemd-analyze plot: Generates an SVG graph of all boot dependencies.
    systemd-analyze plot > dependencies.svg  
  • systemd-analyze critical-chain: Shows the critical path during boot (the longest chain of dependencies that determines boot time).
    systemd-analyze critical-chain nodeapp.service  

systemctl cat

Displays the full unit file (including overrides) to check for typos in dependencies.

Example:

systemctl cat nodeapp.service  

6. Troubleshooting Common Dependency Issues

Even with careful configuration, dependency issues can arise. Here are common problems and fixes:

1. Circular Dependencies

Problem: Service A depends on B, and B depends on A. Systemd detects this and refuses to start either.
Fix: Use systemctl list-dependencies to identify the loop. Remove unnecessary Requires/Wants directives or split services to break the cycle.

2. Service Starts Before Its Dependency (Order Issues)

Problem: Requires=mysql.service is set, but the app starts before MySQL, causing connection errors.
Fix: Add After=mysql.service to enforce order.

3. Missing Dependencies

Problem: systemctl start nodeapp fails with “Dependency failed for Node.js Web Application”.
Debug: Check journalctl -u nodeapp.service for logs. Use systemctl list-dependencies nodeapp to verify required services exist and are enabled.
Fix: Install/enable the missing service (e.g., systemctl enable --now mysql.service).

4. Unintended Service Stops

Problem: nodeapp stops when redis (a Wants dependency) crashes.
Root Cause: Accidentally used Requires=redis.service instead of Wants=redis.service.
Fix: Replace Requires with Wants in the [Unit] section.

7. Best Practices for Designing Service Dependencies

To avoid issues, follow these guidelines:

  1. Minimize Dependencies: Only include critical dependencies. Fewer dependencies reduce complexity and boot time.
  2. Prefer Wants Over Requires: Use Requires only for hard dependencies. Wants makes the system more resilient to optional service failures.
  3. Always Pair Requires/Wants with After/Before: Ensure services start in the correct order (e.g., Requires=mysql.service + After=mysql.service).
  4. Avoid Circular Dependencies: Design services to be modular. If two services depend on each other, merge them or introduce a shared dependency (e.g., a target).
  5. Test with systemd-analyze verify: Validate unit files for syntax/dependency errors:
    systemd-analyze verify nodeapp.service  
  6. Document Dependencies: Add comments in unit files explaining why each dependency is needed (e.g., # Requires MySQL for database connections).

8. Advanced Topics: Targets and Dependency Chains

Systemd uses targets (.target units) to group services and model boot stages (e.g., network.target, multi-user.target). Targets have no [Service] section—they exist solely to order and group services.

Key Targets:

  • network.target: Represents network availability (but not all network interfaces; use network-online.target for full connectivity).
  • multi-user.target: Non-graphical multi-user mode (most servers boot here).
  • graphical.target: Graphical mode (depends on multi-user.target).

Dependency Chains in Boot:

During boot, systemd starts default.target (usually a symlink to multi-user.target or graphical.target). default.target depends on other targets/services, creating a chain:
basic.targetnetwork.targetmulti-user.targetnodeapp.service.

Visualizing with systemd-analyze plot helps identify bottlenecks (e.g., a slow network.target delaying all dependent services).

9. Conclusion

Systemd service dependencies are the backbone of reliable Linux service management. By mastering directives like Requires, Wants, After, and tools like systemctl list-dependencies, you can ensure services start in the right order, fail gracefully, and integrate seamlessly with the system.

Remember: the goal is to design minimal, explicit dependencies that balance robustness and flexibility. With careful planning and testing, you can avoid common pitfalls like circular dependencies and ensure your system boots efficiently and reliably.

10. References