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.
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.