Table of Contents
-
- 1.1 What is Systemd?
- 1.2 Units: The Building Blocks
- 1.3 Targets: Grouping Units
-
Core Concepts of Dependency Management
- 2.1 Why Dependencies Matter
- 2.2 Dependency Types: Hard vs. Soft
-
Essential Dependency Directives
- 3.1
Requires=andRequiresOverridable= - 3.2
Wants=andWantedBy= - 3.3
After=andBefore= - 3.4
BindsTo=andPartOf= - 3.5
Conflicts=andOnFailure= - 3.6
Documentation=(Honorable Mention)
- 3.1
-
- 4.1 Socket Activation: Lazy Dependency Resolution
- 4.2 Target-Based Dependencies (e.g.,
multi-user.target) - 4.3 Ordering with Multiple Dependencies
-
Debugging and Validation Tools
- 5.1
systemctl list-dependencies: Visualize Dependencies - 5.2
systemd-analyze: Analyze Boot and Dependency Chains - 5.3
systemctl show: Inspect Unit Properties - 5.4
journalctl: Troubleshoot Failures
- 5.1
1. Understanding Systemd Basics
Before diving into dependencies, let’s establish foundational knowledge about systemd and its components.
1.1 What is Systemd?
Systemd is a system and service manager for Linux operating systems. It initializes the system during boot, manages running processes, and handles service lifecycle (start, stop, restart, reload). Unlike traditional init systems (e.g., SysVinit), systemd is designed for parallelization, on-demand service activation, and fine-grained dependency control—making it faster and more flexible.
1.2 Units: The Building Blocks
Systemd organizes system resources into units, which are configuration files describing services, sockets, devices, mounts, targets, and more. Units are stored in /usr/lib/systemd/system/ (system-provided) or /etc/systemd/system/ (user-customized).
Common unit types include:
- .service: A background service (e.g.,
nginx.service,postgresql.service). - .target: A logical grouping of units (e.g.,
multi-user.targetfor a text-mode login). - .socket: A network or IPC socket (e.g.,
sshd.socketfor SSH). - .mount: A filesystem mount point (e.g.,
/home.mount). - .device: A hardware device (e.g.,
sda1.device).
For dependency management, .service and .target units are the most relevant.
1.3 Targets: Grouping Units
Targets act as “meta-units” that bundle other units to define system states. For example:
poweroff.target: Shuts down the system.multi-user.target: Boots into a multi-user text-mode environment (no GUI).graphical.target: Depends onmulti-user.targetand starts the GUI.
Targets are used to control the boot process and group services. Dependencies often reference targets to ensure services start in the correct system state.
2. Core Concepts of Dependency Management
At its core, systemd’s dependency management ensures units start (or stop) in a specific order and only when their prerequisites are met.
2.1 Why Dependencies Matter
Consider a web application that relies on a PostgreSQL database and a network connection. If the web app starts before PostgreSQL, it will fail to connect. If PostgreSQL starts before the network, it may not bind to the correct interface. Dependencies solve this by:
- Ordering: Ensuring units start/stop in the right sequence.
- Prerequisites: Ensuring dependencies are active before a unit starts.
- Cleanup: Stopping dependent units when a critical service fails.
2.2 Dependency Types: Hard vs. Soft
Systemd distinguishes between hard dependencies (required for operation) and soft dependencies (optional, enhances functionality):
- Hard: If the dependency fails, the dependent unit fails (e.g.,
Requires=). - Soft: If the dependency fails, the dependent unit continues (e.g.,
Wants=).
3. Essential Dependency Directives
Dependency behavior is defined by directives in unit files (e.g., .service files). Below are the most critical directives, with use cases and examples.
3.1 Requires= and RequiresOverridable=
-
Requires=: Declares a hard dependency. The dependent unit will start only if all listed units start successfully. If any required unit fails, the dependent unit is not started (or is stopped if already running).- Syntax:
Requires=unit1.service unit2.target - Example: A Node.js app requiring PostgreSQL:
[Unit] Requires=postgresql.service - Caveat:
Requires=does not enforce ordering. UseAfter=to ensure the dependency starts first (see §3.3).
- Syntax:
-
RequiresOverridable=: Similar toRequires=, but allows overriding viasystemctl editor drop-in files. Use when you want to let users modify dependencies without editing the original unit file.
3.2 Wants= and WantedBy=
-
Wants=: Declares a soft dependency. The dependent unit attempts to start the listed units, but if they fail, the dependent unit still starts.- Use Case: Optional features (e.g., a logging service that enhances but isn’t required for the app).
- Example:
[Unit] Wants=logging.service # Logging is optional
-
WantedBy=: Defines reverse dependencies in the[Install]section. It adds the current unit to theWants=list of the target unit.- Example: Making a service start on boot by linking it to
multi-user.target:[Install] WantedBy=multi-user.target # Equivalent to: multi-user.target Wants=myapp.service
- Example: Making a service start on boot by linking it to
3.3 After= and Before=
These directives control ordering (not dependency existence). They ensure units start/stop in a specific sequence but do not enforce that the dependency exists.
After=: The current unit starts after the listed units.Before=: The current unit starts before the listed units.- Example: Ensuring PostgreSQL starts before the Node.js app:
[Unit] Requires=postgresql.service After=postgresql.service # Critical: Ensures PostgreSQL starts first - Key Note:
After=/Before=alone does not create a dependency. Always pair withRequires=/Wants=to ensure the dependency is actually started.
3.4 BindsTo= and PartOf=
These enforce tight coupling between units, often used for cleanup.
-
BindsTo=: A stronger version ofRequires=. If the bound unit stops (for any reason), the dependent unit is stopped immediately.- Use Case: A service tightly coupled to a device (e.g., a USB modem service bound to
ttyUSB0.device). - Example:
[Unit] BindsTo=ttyUSB0.device
- Use Case: A service tightly coupled to a device (e.g., a USB modem service bound to
-
PartOf=: A weaker form ofBindsTo=. If the listed unit stops, the dependent unit is stopped, but the reverse is not true (stopping the dependent unit does not affect the listed unit).- Use Case: Auxiliary services (e.g., a cache service that should stop when the main app stops).
3.5 Conflicts= and OnFailure=
-
Conflicts=: Declares units that cannot run simultaneously with the current unit. If a conflicting unit starts, the current unit is stopped, and vice versa.- Example: A legacy service conflicting with a modern replacement:
[Unit] Conflicts=legacy-app.service
- Example: A legacy service conflicting with a modern replacement:
-
OnFailure=: Specifies units to start if the current unit fails.- Use Case: Alerting or recovery (e.g., restarting the service or sending an email).
- Example:
[Unit] OnFailure=restart-app.service # Custom recovery unit
3.6 Documentation= (Honorable Mention)
While not a dependency directive, Documentation= is critical for maintainability. It links to docs explaining the unit’s dependencies (e.g., “Requires PostgreSQL 14+”).
- Example:
[Unit] Documentation=https://myapp.com/docs/dependencies.md
4. Advanced Dependency Scenarios
Systemd’s flexibility shines in complex setups. Below are advanced patterns.
4.1 Socket Activation: Lazy Dependency Resolution
Socket activation defers service startup until a client connects to a socket. The socket unit starts at boot, and the service starts only when needed.
-
How it works:
- A
.socketunit defines the socket (e.g.,myapp.socket). - The
.serviceunit declaresRequires=myapp.socketandAfter=myapp.socket. - When a client connects to the socket, systemd starts the service, which inherits the socket.
- A
-
Example:
myapp.socket:[Unit] Description=My App Socket [Socket] ListenStream=/run/myapp.sockmyapp.service:[Unit] Requires=myapp.socket After=myapp.socket [Service] ExecStart=/usr/bin/myapp --socket /run/myapp.sock -
Benefit: Reduces boot time by avoiding unnecessary service startups.
4.2 Target-Based Dependencies
Targets group units to define system states. For example, multi-user.target is a common target for server environments.
-
Example: A service that should start in
multi-user.targetbut notgraphical.target:[Install] WantedBy=multi-user.target Conflicts=graphical.target -
Common Targets:
basic.target: Early boot services (e.g., mounts, udev).network.target: Network is available.multi-user.target: Multi-user text mode (default for servers).
4.3 Ordering with Multiple Dependencies
For units with multiple dependencies, use After= to enforce a startup sequence.
-
Example: A web app requiring PostgreSQL, Redis, and the network:
[Unit] Description=My Web App Requires=postgresql.service redis.service network.target After=postgresql.service redis.service network.target # All dependencies start firstHere, systemd starts
network.target, thenpostgresql.serviceandredis.service(in parallel, unless ordered), then the web app.
5. Debugging and Validation Tools
Systemd provides tools to visualize and troubleshoot dependencies.
5.1 systemctl list-dependencies
Lists all dependencies of a unit (direct and indirect).
-
Command:
systemctl list-dependencies myapp.service -
Options:
--reverse: Show units that depend onmyapp.service.--all: Include indirect dependencies.
-
Example Output:
myapp.service ├─postgresql.service │ └─network.target └─redis.service
5.2 systemd-analyze
Analyze boot time and dependency chains.
systemd-analyze plot > boot.svg: Generates a SVG graph of the boot process, highlighting dependencies and bottlenecks.systemd-analyze critical-chain: Shows the longest dependency chain (critical path) during boot.
5.3 systemctl show
Inspect a unit’s properties, including dependencies.
- Command:
systemctl show myapp.service --property=Requires,Wants,After - Output:
Requires=postgresql.service Wants=logging.service After=postgresql.service network.target
5.4 journalctl
Check logs to debug dependency failures:
- Command:
journalctl -u myapp.service -u postgresql.service - Use Case: Identify why
myapp.servicefailed (e.g., “PostgreSQL not listening on port 5432”).
6. Best Practices for Effective Dependency Management
To avoid pitfalls, follow these guidelines:
- Prefer
Wants=OverRequires=: Soft dependencies make systems more resilient to optional service failures. - Always Pair
Requires=/Wants=withAfter=/Before=: Ordering is not implied by dependencies! - Avoid Circular Dependencies: Systemd detects cycles but may start units unpredictably. Use
systemd-analyze verifyto check. - Leverage Targets: Use standard targets (e.g.,
network.target) instead of hardcoding service names. - Test with
systemd-analyze: Validate boot time and dependency chains. - Document Dependencies: Use
Documentation=and comments in unit files.
7. Conclusion
Systemd’s dependency management is a cornerstone of modern Linux system reliability. By mastering directives like Requires=, Wants=, and After=, leveraging targets, and using debugging tools, you can ensure services start in the right order, recover gracefully from failures, and optimize boot time. Whether you’re managing a simple service or a complex distributed system, proper dependency management reduces downtime and simplifies maintenance.