systemd Service Dependency Ordering: Getting Boot Sequences Right
How to configure proper service dependencies in systemd using Requires, Wants, After, and ordering directives for reliable startup sequences.
Introduction
systemd replaced the traditional SysV init system with a parallelized, dependency-driven service manager. But with great power comes the possibility of great confusion. When services start in the wrong order, you get race conditions, failed connections, and mysterious boot failures. This post explains how systemd dependency ordering works and how to configure it correctly for production services.
Environment
The examples target systemd 252 on Debian 12, but the concepts apply to any modern Linux distribution using systemd. We will configure a hypothetical Node.js application that depends on PostgreSQL and Redis, and should only start after the network is fully configured.
systemctl --version
# systemd 252 (252.19-1~deb12u1)Problem
After adding a custom application service, it fails during boot:
sudo systemctl status myapp.service
# ● myapp.service - My Application
# Loaded: loaded (/etc/systemd/system/myapp.service; enabled; vendor preset: enabled)
# Active: failed (Result: exit-code) since Thu 2025-05-15 10:00:01 UTC; 5s ago
# Process: 1234 ExecStart=/usr/bin/node /opt/myapp/server.js (code=exited, status=1/FAILURE)
# May 15 10:00:01 server systemd[1]: Started My Application.
# May 15 10:00:01 server node[1234]: Error: connect ECONNREFUSED 127.0.0.1:5432The application tried to connect to PostgreSQL before PostgreSQL was ready. Even though the service file specified After=postgresql.service, the database was not accepting connections when the application started.
Analysis
systemd dependencies come in two types:
Relationships (What to start):
Requires=: Strong dependency. If this unit fails, the dependent fails too.Wants=: Weak dependency. Start this unit alongside, but do not fail if it does not start.BindsTo=: Like Requires, but also stops when the bound unit stops.PartOf=: Restart this unit when the specified unit is restarted.
Ordering (When to start):
After=: Start this unit after the specified unit.Before=: Start this unit before the specified unit.
The key insight: ordering and dependencies are orthogonal. You can have After=foo.service without Requires=foo.service, which means your service starts after foo but does not depend on it.
# This is WRONG: ordering without relationship
[Unit]
After=postgresql.service
# postgresql.service may not even be started!
# This is BETTER: explicit relationship
[Unit]
Requires=postgresql.service
After=postgresql.serviceBut even with the correct directives, After= only waits for the service to be started, not for it to be ready. PostgreSQL may start but take several seconds to become ready for connections.
Solution
1. Use systemd notify for readiness signaling
The best solution is having services tell systemd when they are ready:
# /etc/systemd/system/postgresql.service
[Service]
Type=notify
ExecStart=/usr/lib/postgresql/16/bin/postgres -D /var/lib/postgresql/16/main2. Use ExecStartPost for post-start checks
# /etc/systemd/system/myapp.service
[Unit]
Requires=postgresql.service
After=postgresql.service
[Service]
ExecStart=/usr/bin/node /opt/myapp/server.js
ExecStartPost=/bin/bash -c 'until pg_isready -h localhost -p 5432; do sleep 1; done'
Restart=on-failure
RestartSec=53. Use a oneshot helper service for readiness
# /etc/systemd/system/postgres-ready.service
[Unit]
Requires=postgresql.service
After=postgresql.service
[Service]
Type=oneshot
ExecStart=/bin/bash -c 'until pg_isready -h localhost -p 5432; do sleep 1; done'
RemainAfterExit=yes
[Install]
WantedBy=multi-user.targetThen your application depends on postgres-ready.service:
# /etc/systemd/system/myapp.service
[Unit]
Requires=postgres-ready.service
After=postgres-ready.service4. Configure dependency chains correctly
# /etc/systemd/system/myapp.service
[Unit]
Description=My Application
Requires=postgresql.service redis-server.service
After=postgresql.service redis-server.service network-online.target
Wants=network-online.target
[Service]
Type=simple
User=appuser
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/node server.js
Restart=always
RestartSec=3
SuccessExitStatus=143
[Install]
WantedBy=multi-user.target5. Verify dependency trees
systemctl list-dependencies myapp.service
# myapp.service
# ├─postgresql.service
# ├─redis-server.service
# ├─network-online.target
# │ └─networking.service
# └─system.slicesystemd-analyze dot myapp.service | dot -Tsvg > deps.svg6. Debug boot order
systemd-analyze blame | head -10
# Lists services by time taken to start
systemd-analyze critical-chain myapp.service
# Shows the chain of dependencies causing delaysLessons Learned
systemd's dependency system is powerful but requires precision. Always pair After= with Requires= or Wants= to create actual relationships. Do not assume that starting after a service means the service is ready. Use readiness signaling, oneshot helpers, or startup checks to ensure dependencies are truly ready before your application begins. And always test boot sequences with systemd-analyze to understand what is happening.
This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.