Guide

Unit Files and the systemd Model

Part 1 of 7By Amith Kumar8 min read
Part 1 of 7systemd Deep Dive: A Hands-On Guide
  1. 1Unit Files and the systemd Model
  2. 2Writing systemd Services Well
  3. 3journald and journalctl in Depth
  4. 4systemd Timers vs cron
  5. 5Sandboxing and Hardening a Unit
  6. 6Resource Control with cgroups v2
  7. 7Socket Activation and User Services
Verified 22 Jul 2026 · Ubuntu 24.04 LTS

What you will have at the end

By the end of this series you can turn anything you run into a real service: one that restarts when it crashes, logs where you can find it, runs on a schedule, and is sandboxed so a bug cannot take the box with it. It all rests on systemd unit files, the small text files that tell systemd what to run and how to treat it.

This first chapter shows you a service surviving a crash on a live Ubuntu 24.04 slice, turns a bare script into that service from nothing, then teaches you to read the unit model so you can write your own.

Key takeaways
  • The series destination: a script you used to babysit in a terminal becomes a managed service that systemd keeps alive, so killing its process just makes it come back with a new PID.
  • There is nothing to install. systemd is already PID 1 on Ubuntu, so the from-zero step is writing your first .service file and running systemctl enable --now.
  • A unit file is plain text in one of a few types: .service, .socket, .timer, .target, .mount. A service groups its keys under [Unit], [Service], and [Install].
  • systemctl cat prints the unit systemd loaded and systemctl show prints every resolved property, so you never have to guess what a service is actually running.
Before you start
  • One Ubuntu 24.04 slice reached over SSH where you hold sudo, with any long-running script of your own to promote into a service. A five-line bash loop is enough to follow along.
  • No package to add. systemd ships as the init system, so systemctl and journalctl are already on the box; you write unit files under /etc/systemd/system and reload.
  • Every demo here is a throwaway named sc-demo-*, kept separate from the units your slice already runs, so nothing you practise on touches nginx, SSH, or the system's own services.
  • Enough shell to write a file with sudo tee or an editor and to read a systemctl status block, which is the one screen the whole series comes back to.

See a service survive a crash first

Here is where this chapter lands, on one screen. This is a small worker running as a systemd service, and it is about to be killed the hard way. Note the Main PID in the status, send that process a SIGKILL, and ask for the status again.

systemctl status sc-demo-web.service
sudo kill -9 876416
systemctl status sc-demo-web.service
The destination shown before any theory: systemctl status showing sc-demo-web.service active on Main PID 876416, a kill -9 on that process, then status again showing the service active on a new Main PID 876485, systemd having restarted it in under a second

The service was active (running) on PID 876416. A kill -9 cannot be caught or ignored, so that process is gone instantly. Yet the second status is active (running) again, this time on PID 876485, started less than a second ago. systemd noticed the process die and started it straight back.

That is the whole point of a service: it stays up whether or not anyone is watching the terminal. Right now you have none of this; your script runs only as long as your shell stays open. The rest of this chapter builds exactly this service from a bare script.

Turn a bare script into your first service

A service starts as something plainer: a script you run by hand and hope stays up. Here is the worker this series uses, a loop that does a unit of work every few seconds. On a fresh slice it lives at /opt/sc-demo/worker.sh and runs only while you sit in the terminal.

#!/bin/bash
# a plain worker you have been running by hand
n=0
while true; do
  n=$((n+1))
  echo "worker: processed job $n"
  sleep 5
done

To hand it to systemd, describe it in a unit file. Write this to /etc/systemd/system/sc-demo-web.service, the directory where units you own live.

[Unit]
Description=ServerCake demo web worker
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/opt/sc-demo/worker.sh
Restart=on-failure
RestartSec=1

[Install]
WantedBy=multi-user.target

Now reload systemd so it reads the new file, then enable and start it in one step. enable wires it to start at boot; --now also starts it immediately.

sudo systemctl daemon-reload
sudo systemctl enable --now sc-demo-web.service
systemctl is-active sc-demo-web.service
The from-zero step: sudo systemctl daemon-reload then enable --now on the newly written sc-demo-web.service, creating the multi-user.target.wants symlink, and systemctl is-active printing active, turning a bare script into a managed service

enable creates a symlink under multi-user.target.wants, which is how the service gets pulled in at every boot, and is-active prints active. Your script is now a managed service. That Restart=on-failure line is what made the crash demo above work: a process killed by a signal counts as a failure, so systemd revives it. Chapter two takes the restart policy apart properly.

Checkpoint

Run systemctl is-active sc-demo-web.service and confirm it prints active, then systemctl is-enabled and confirm enabled. Your bare script is now a service that starts at boot and comes back from a crash, which is the destination you saw at the top of this chapter.

What systemd unit files contain

You just wrote a unit, so now read the model behind it. Everything systemd manages is a unit, and the suffix names its kind. A .service is a process to supervise. A .socket is a listening socket that can start a service on demand. A .timer is a schedule. A .target is a named group of units used to reach a boot state, and a .mount is a filesystem mount. You will spend most of your time on services, and meet the rest later in this series.

A service unit groups its keys into three sections. [Unit] holds the description and the ordering keys such as After and Wants. [Service] describes the process itself: its Type, its ExecStart command, and its restart behaviour. [Install] is read only by systemctl enable, and it records which target should pull the unit in at boot. You never have to guess what systemd actually loaded, because it will print the file back to you.

systemctl cat sc-demo-web.service
systemctl cat printing the sc-demo-web.service unit file with its Unit, Service and Install sections and the path under /etc/systemd/system

The header line shows the file lives under /etc/systemd/system, so it is one you own rather than a vendor copy. When a unit name exists both there and under /lib/systemd/system, the copy under /etc wins, which is why your version takes effect without editing a packaged file. systemctl cat prints the file as written; systemctl show sc-demo-web.service -p Restart goes further and prints the effective value of a single property after systemd applies its defaults.

Overriding a unit with a drop-in

You rarely edit a packaged unit directly, because the next package update would overwrite your change. Instead you write a drop-in: a small file under /etc/systemd/system/name.service.d/ that carries only the keys you want to change, which systemd merges on top of the vendor unit. Running sudo systemctl edit sc-demo-web.service creates one for you and reloads the daemon when you save. This matters because systemctl cat prints the base unit and every drop-in in the order they apply, so when a setting is not what you expect, cat shows you the last file that touched it.

Going further: how systemd resolves and orders units

Boot is not a script that runs top to bottom. systemd reads every unit, builds a dependency graph, and starts units in parallel wherever their ordering allows. Two ideas control this, and they are separate on purpose. Wants and Requires create a dependency, meaning one unit pulls another in. After and Before create ordering, meaning one unit waits for another to start first. You can want a unit without caring when it starts, or order around a unit you did not pull in.

You can measure the result of all that parallelism.

systemd-analyze
systemd-analyze blame
systemd-analyze reporting the boot time split between kernel and userspace, then systemd-analyze blame ranking the units that took longest to start

The first line reports how long the last boot took, split between the kernel and userspace. systemd-analyze blame then ranks units by how long each took to start. On a cloud image the top of the list is usually a first-boot or background maintenance job rather than anything you configured, so read the list to find where boot time actually went before blaming your own service.

Frequently asked questions

Do I need to install systemd first?

No. systemd is the init system on Ubuntu 24.04, running as PID 1 from the moment the machine boots, so systemctl and journalctl are already present. The from-zero step for systemd is not an install; it is writing your first unit file under /etc/systemd/system and running systemctl daemon-reload.

Where do I put a unit file I write myself?

Put it in /etc/systemd/system and run sudo systemctl daemon-reload. Files there override same-named vendor units in /lib/systemd/system, so your version wins without editing a packaged file.

What is the difference between systemctl cat and systemctl show?

cat prints the unit file as written, including any drop-ins. show prints every property after systemd applies its defaults, so it reveals settings the file never lists. Reach for cat to read intent and show to read the effective value.

What does enable do that start does not?

start runs the service now, until the next reboot. enable wires it into a target so it comes back at every boot, by creating a symlink under that target's .wants directory. enable --now does both at once, which is what you almost always want for a service meant to stay running.

What you can do now

You can take a bare script and make it a systemd service: write the unit under /etc/systemd/system, reload, and enable --now it so it starts at boot. You can read the model behind that unit with systemctl cat and systemctl show, override a packaged unit safely with a drop-in, and read systemd-analyze blame to see where boot time went. You have also seen the destination, a service that shrugs off a kill -9 and comes back on a new PID.

That restart was one line, Restart=on-failure, and there is more to getting it right: which Type to pick, how to order a service after its dependencies, and how to stop a crash loop from spinning the CPU. The next chapter writes a service that behaves and breaks it on purpose to watch systemd recover it: writing systemd services well.


Run your services on a slice

Run rock-solid services on a slice

systemd turns your app into a service that restarts, logs and stays sandboxed. Launch a slice, apply this guide, and run anything reliably.

Get early access

This guide is provided for educational purposes and offered as is, without warranty of any kind. The commands change the configuration of a server you control, and some can lock you out if run out of order. Test on a non-production machine, keep a second session open, and take a snapshot or backup before you begin. You are responsible for changes made to your own systems; ServerCake accepts no liability for any loss, downtime, lockout, or damage arising from following this guide or running the accompanying script.

Was this guide helpful?
Share

Spotted something out of date or have a question?

Built by the ServerCake team

Cloud that speaks India.

ServerCake is India's KYC-verified cloud: VMs and managed databases priced in ₹, billed with GST, on India-resident infrastructure. Reserve your spot for early access.

Reserve your spot