The most surprising thing about systemd services is that they’re not really about "starting" or "stopping" anything in the traditional sense; they’re about describing a desired system state and letting systemd figure out how to achieve it.

Let’s see this in action. Imagine you have a web server, say Nginx, that you want to run. Instead of a simple start command, you have a .service file. Here’s a simplified example:

[Unit]
Description=Nginx Web Server
After=network.target

[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStart=/usr/sbin/nginx
ExecReload=/usr/sbin/nginx -s reload
ExecStop=/bin/kill -s QUIT `cat /run/nginx.pid`
Restart=on-failure
User=nginx
Group=nginx

[Install]
WantedBy=multi-user.target

When you run sudo systemctl start nginx.service, systemd doesn’t just execute /usr/sbin/nginx. It consults this file. It checks the After=network.target directive to ensure networking is ready. It knows that Nginx is a forking process (meaning the ExecStart command will spawn a child process and exit) and that it will create a PIDFile. It has instructions for ExecReload and ExecStop, and crucially, it knows to Restart=on-failure. The WantedBy=multi-user.target means that when the system reaches the standard multi-user runlevel, Nginx should be active.

The core problem systemd services solve is managing the lifecycle and dependencies of processes that need to run reliably on a Linux system. Before systemd, you had init scripts in /etc/init.d/ that were often shell scripts, executed sequentially with arcane numbering schemes. Dependencies were hard to express, and error handling was inconsistent. systemd introduces a declarative, dependency-driven approach.

Internally, systemd uses a sophisticated dependency graph. Each unit (services, sockets, devices, mount points, etc.) is a node in this graph. When you request an action (like start nginx.service), systemd analyzes the dependencies defined in the unit files and determines the optimal order to start or stop other units to satisfy those dependencies. It uses file descriptors for communication and avoids shell scripts for most operations, leading to faster boot times and more robust process management.

The Type directive is critical. Type=forking is for older daemons that fork and exit, leaving the child to run. Type=simple (the default) assumes the ExecStart command is the main process and systemd considers the service started once it’s running. Type=oneshot is for scripts that do a single task and exit. Type=notify is used by daemons that signal systemd when they are ready, allowing systemd to know precisely when a service is truly initialized.

Consider the power of systemd-analyze. Running systemd-analyze blame shows you which services are taking the longest to start during boot. systemd-analyze critical-chain reveals the dependencies that are holding up the boot process. This isn’t just about timing; it’s about understanding the causal relationships between different parts of your system. For instance, if your database service is slow to start, critical-chain might show it’s waiting for a network mount that itself is waiting for a DNS service.

The ExecStop directive is often misunderstood. For Type=forking services that use a PID file, ExecStop=/bin/kill -s QUIT cat /run/nginx.pid`` is common. systemd reads the PID from the specified file and sends the QUIT signal to that process. However, you can also use signals like TERM or HUP. For Type=simple services, ExecStop=/bin/kill $MAINPID is more direct, as systemd tracks the main process ID itself. The key is that systemd uses the specified command to stop the service, not just try to kill its process ID randomly.

A subtle but powerful aspect is how systemd handles resources and isolation. Through directives like ProtectSystem, PrivateTmp, and SandBox, you can configure services to run with significantly reduced privileges and access to the filesystem, enhancing security. For example, ProtectSystem=full prevents a service from writing to /usr, /boot, and /etc, while PrivateTmp=true gives it its own private /tmp and /var/tmp directories.

The next logical step is exploring how to create custom systemd units for your own applications and understand how to manage their dependencies with other services.

Want structured learning?

Take the full Systemd course →