systemd: The new and sometimes controversial heartbeat of modern Linux

I originally learned UNIX (setting my stage for Linux) on Solaris 8 as my main workstation and got exposed to a variety of other flavors of both UNIX and Linux in the first few years of my career. One thing that was consistent across them - how services were started and managed. For decades, Linux and UNIX booted the same, using init/system V. Then, in 2010, systemd was introduced into the Fedora project and slowly began being adopted by other distributions, and the way things worked for decades began to change, regardless of whether you loved or hated it. Some folks welcomed the change and increase in scope, while others had a "it's not broke, do not fix it" kind of perspective. 

What is systemd

Today, systemd has changed the way Linux boots, runs, and manages its services and is used by most major distributions of Linux. In this post, we’re going to explore what systemd is, why it was introduced, how it differs from its predecessor init.d,  what service types it supports, and why it matters. 

 

What is systemd? 

At its core, systemd is a system and service manager for Linux. It’s responsible for: 

  • Initializing the system during boot 
  • Managing system services 
  • Handling centralized logging 
  • Maintaining service state across reboots 

 

It’s more than a replacement for the old System V init system (init.d)—it’s a modern and sometimes controversial rethinking of Linux system architecture. 

systemd includes: 

  • systemctl – to manage services (start, stop, enable, disable) 
  • journald – for system logging 
  • Timers – as a cron job alternative 
  • Socket activation – to start services only when needed 
  • Targets – to group services into states like multi-user.target 

 

Some of the ways that systemd differs from init.d: 

systemd vs. init.d 

Feature 

SysV init.d 

systemd 

Startup Method 

Sequential shell scripts 

Parallel service start 

Configuration 

/etc/init.d/ shell scripts 

Declarative unit files 

Dependencies 

Manual (via script naming) 

Built-in with After= / Requires= 

Logging 

External (e.g., syslog) 

Built-in via journald 

Socket Activation 

Not available 

Native support 

Service Monitoring 

Basic (e.g., pidof) 

Uses cgroups for tracking 

 

Systemd launches services in parallel, uses cleaner configuration, and provides tighter integration with system tools. 

 

Service Types in systemd 

In a systemd unit file, the Type= directive defines how the service behaves during startup. 

Common Types: 

  • simple – Default. Runs the service in the foreground. 
  • forking – For traditional daemons that fork into the background. 
  • oneshot – Runs once, then exits. Good for setup tasks. 
  • dbus – Waits for the service to register a D-Bus name. 
  • notify – The service signals systemd when it’s fully ready. 
  • idle – Starts only after all other jobs finish. 

 

Comparing Service Types 

Type  Waits for Completion?  Background Support  Ideal Use Case 
simple  No  No  Most modern daemons 
forking  Yes (after fork)  Yes  Legacy-style daemons 
oneshot  Yes  N/A  Initialization tasks 
dbus  Yes (D-Bus signal)  Yes  Desktop/system daemons 
notify  Yes (custom readiness)  Yes  Complex startup logic 
idle  Delayed  Yes  Background jobs 

 

Example: Basic systemd Unit File 

Here’s a simple example of a custom service file: 

[Unit] 

Description=My Sample Service 

After=network.target 

 

[Service] 

Type=simple 

ExecStart=/usr/bin/myservice --start 

Restart=on-failure 

 

[Install] 

WantedBy=multi-user.target 

To use a forking service instead, change Type=simple to Type=forking, and ensure the program forks as expected. 

  

Why it Matters

I ran into a real-world manifestation of why service types matter the other day. We had a simple wrapper script that was going to start a bunch of Python subprocesses. Unfortunately, the script worked fine when run manually, and then did not seem to work when tried to run automatically within systemd. The behavior we observed was that as soon as we would systemctl start nameOfService, the logs showed that everything started, and then immediately stopped.  

This turned out to be a difference in how systemd handles processes depending on the service type set. A simple process vs a spawning (or forking in systemd unit files) process in this case. 

The wrapper script we had was simple - start 20 Python services and run them in the background (as a service).  

#!/usr/bin/bash 

# 

source /opt/genai/venv/bin/activate 

 

if [ $# -eq 0 ]; then 

  echo "Usage: $0 start|stop" 

  exit 1 

fi 

 

if [ "$1" = "start" ]; then 

  for i in {00..19}; do 

    echo "Starting GenAI Server "$i 

    cd /opt/genai/run/$i 

    streamlit run aiServer.py --server.port 80$i --server.address 0.0.0.0 &> GenAILog.txt & 

  done 

elif [ "$1" = "stop" ]; then 

  killall /opt/genai/venv/bin/python 

  echo "Shutting down GenAI" 

fi 

 

The unit file we used was: 

 

[Unit] 

Description=GenAI Service 

After=network-online.target 

 

[Service] 

ExecStart=/opt/genai/startup.sh start 

ExecStop=/opt/genai/startup.sh stop 

User=genai 

Group=genai 

Type=simple 

Restart=on-failure 

RestartSec=60 

 

[Install] 

WantedBy=default.target 

 

In our case, the attribute "Type" under the [Service] section of our unit file was set to simple. This was causing systemd to terminate all of our Python processes as soon as the wrapper shell script finished its job, despite it being a simple script that was launching (spawning) unrelated processes.  

There are a couple of ways we could've fixed this,  some less elegant than others. If we did not know about the different types and how they worked in systemd, we could've changed our logic and left the last Python process running in the foreground, which would've kept our wrapper script also running, and the child processes would've remained up under the Type=simple definition. 

This isn't what we ideally wanted - we wanted the wrapper to do its job, exit, and leave the Python processes behind. 

This is where the Type = forking comes in. Forking is specifically for this - when a parent process completes and exits cleanly while leaving the child processes it spawned off behind, or if the process is forking and not spawning, it remains behind as the parent of the child processes. 

Technically, forking and spawning are different. When we use a shell script to run a Python script, it's spawning a new process, not forking. 

Some main differences between the two are: 

 

Forking 

  • Creates an exact copy of the existing process 
  • The child starts with an identical copy of the parent's memory 
  • The child then typically uses a function called "exec" to replace itself with a different program 
  • Example: When a web server forks to handle a new connection 

 

Spawning 

  • Creates a new, different process directly 
  • Launches a completely new program with its own initial state 
  • No intermediate copying step 
  • Example: When a shell script runs a Python script 

 

In our case, what happens is: 

  • The shell spawns a new process 
  • This new process runs the Python interpreter 
  • The Python interpreter loads and executes our script 

 

This is spawning, not forking, because the shell doesn't create a copy of itself - it directly creates a new, different process (the Python interpreter). Behind the scenes on Linux systems, spawning might use fork+exec internally, but conceptually it's different from just forking because the result is a completely new program running, not a copy of the original. In our case, the simple service type was not suitable for our spawning needs. 

 

[Unit] 

Description=GenAI Service 

After=network-online.target 

 

[Service] 

ExecStart=/opt/genai/startup.sh start 

ExecStop=/opt/genai/startup.sh stop 

User=genai 

Group=genai 

Type=forking 

Restart=on-failure 

RestartSec=60 

 

[Install] 

WantedBy=default.target 

 

When we updated the systemd unit service type to forking, we were then able to manage it (including start/stop/status) with systemd natively.  

 

Conclusion: The New Normal 

When systemd began replacing the older init. d-based systems, many people felt like it went against the UNIX philosophy of "do one thing and do it well". Instead of systemd remaining an initialization system, it grew to include logging, job scheduling, network config, and more. Having said that, systemd was a much-needed update to Linux and the way that service management was done. 

systemd is now a foundational part of most Linux distributions. It offers: 

  • Better boot performance 
  • Cleaner configuration 
  • Modern features like socket activation and service dependency management 

It took a while for it to mature into a proper replacement, and now in 2025 systemd is here to stay. If you're unfamiliar with how systemd works, I'd recommend starting with basic services, playing with unit files, and getting comfortable using systemctl and journalctl. Once you see what it can do, you might just become a fan. 

At iuvo, our consultants, on average, have over 24 years of experience in the IT industry. If you need help with any technology needs our team at iuvo is ready to help.

 

Subscribe Here For Our Blogs:

Recent Posts

Categories

see all