systemd units, targets, and directives are the fundamental building blocks of the systemd init system, allowing for granular control over system services, hardware, and processes.
Let’s see systemd in action with a simple service. Imagine you want to run a custom Python script named my-app.py that listens on port 8080.
First, you’d create a .service unit file, typically in /etc/systemd/system/my-app.service:
[Unit]
Description=My Custom Application
After=network.target
[Service]
User=myuser
WorkingDirectory=/opt/my-app
ExecStart=/usr/bin/python3 /opt/my-app/my-app.py
Restart=on-failure
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
[Unit]: This section describes the unit itself and its dependencies.Description: A human-readable explanation.After=network.target: This ensures your service starts after the network is up, which is crucial for a network-listening application.network.targetis asystemdtarget unit that signifies network services are ready.
[Service]: This is where you define how the service runs.User=myuser: Runs the service as a non-privileged user for security.WorkingDirectory=/opt/my-app: Sets the current directory for the process.ExecStart=/usr/bin/python3 /opt/my-app/my-app.py: The command to execute to start your service.Restart=on-failure: If the process exits with a non-zero status,systemdwill automatically try to restart it.StandardOutput=journalandStandardError=journal: Redirectsstdoutandstderrto thesystemdjournal, making logs easily accessible viajournalctl.
[Install]: This section defines how the unit should be enabled.WantedBy=multi-user.target: When youenablethis service,systemdcreates a symbolic link so that this service is started when the system reaches themulti-user.targetstate (the typical runlevel for a server without a graphical interface).
After creating this file, you’d tell systemd to reload its configuration:
sudo systemctl daemon-reload
Then, you can start your application:
sudo systemctl start my-app.service
And check its status:
sudo systemctl status my-app.service
You’d see output indicating it’s active and running, along with recent log entries from the journal.
To make it start automatically on boot:
sudo systemctl enable my-app.service
This creates the necessary symbolic link in the .wants directory of multi-user.target.
systemd also uses targets. Targets are essentially unit files that group other units. Think of them like runlevels in older init systems. multi-user.target is the equivalent of runlevel 3, graphical.target is runlevel 5, and reboot.target is for, well, rebooting. You can switch between targets with systemctl isolate <target-name>.
The directives are the key-value pairs within these unit files, like Description, After, ExecStart, Restart, etc. They control every aspect of how a unit behaves. There are directives for dependency management (Requires, Wants, Before, After), process control (ExecStart, ExecStop, User, Group, WorkingDirectory, Environment), logging (StandardOutput, StandardError), and much more.
The truly surprising thing about systemd targets is how they abstract away direct process management for complex boot sequences. Instead of ensuring services start in a specific order by directly managing their PIDs, you declare dependencies (After=, Requires=) between units, and systemd’s dependency solver constructs the execution graph. This means you don’t worry about the order as much as the conditions for starting, and systemd figures out the optimal, safe sequence.
Understanding the dependency resolution mechanism within systemd is key. When you specify After=network.target and Requires=some-database.service, systemd doesn’t just start them in that order. It builds a dependency graph. Requires creates a strong dependency: if some-database.service fails to start, my-app.service won’t even be attempted. After is a ordering constraint; my-app.service will attempt to start after network.target has been reached, but the After directive alone doesn’t guarantee network.target is fully functional, only that its start job has completed. The actual state of the network is often determined by other units that network.target itself depends on.
The next concept you’ll likely grapple with is managing complex inter-service dependencies and ensuring transactional integrity during boot or shutdown.