Module 11 — systemd and Services

Updated 2 September 2026

Module 11 — systemd and Services

Module 8 ended on a cliffhanger: nohup keeps a program alive, but nothing keeps it managed — restarted when it crashes, started at boot, logged, stopped cleanly. That job belongs to systemd, PID 1 on essentially every modern Linux server. This module teaches you to run, inspect, and write services — the single most common day-to-day task in operations, and a guaranteed interview topic.

Legend used throughout: 🧠 concept → 🧩 analogy → 🧪 exercise → ✅ expected result (hidden) → 🎯 interview questions (hidden)

Before you start

You need Modules Module 1 — What Linux Is–Module 10 — Bash Scripting — especially processes/daemons/signals (Module 8: systemd supervises processes and stops them with the exact TERM→KILL ladder you learned), the filesystem (unit files live in specific places), and scripting (a service usually runs a script). Tools: a systemd-based Ubuntu 24.04 machine — a normal cloud VM or WSL2. Two caveats that matter for the exercises: (1) some minimal containers don't run systemd as PID 1 — inside one, systemctl status prints System has not been booted with systemd as init system and the live commands won't work; use a real VM. (2) Where an exercise's output can't come from an un-booted machine, it's marked — but systemd-analyze verify and systemd-analyze calendar work anywhere systemd is installed, so those exercises run even in a container.

Goes deeper: the boot sequence that ends in systemd is the companion track's Module 05 — Boot & Init: Firmware to systemd — read it alongside this module.

This page uses Mermaid diagram blocks. Notion shows them as code by default — click the block and set it to Preview to see the diagram. This reminder appears once per page.

Part A — What systemd is

A1. The service manager, and PID 1

Official docs: systemd(1) · systemctl(1)

🧠 Module 8 introduced PID 1 as the ancestor of all processes; now meet what it actually does. systemd is the first program the kernel starts at boot (PID 1) and the init system — the thing that brings the machine up and manages everything running on it. Its core job is running services: long-lived programs (Module 8's daemons) that it starts in the right order, restarts when they die, stops cleanly on shutdown, and captures the logs of. It replaced the older init/SysV scripts because doing all this reliably — with dependencies, parallelism, and supervision — needs a real manager, not a pile of shell scripts.

The command you drive it with is systemctl. Everything systemd manages is a unit (A2); a service is one kind of unit.

Real-world analogy — the building operations manager

nohup was hiring a contractor and telling them "keep working after I leave" (Module 8) — nobody supervises them. systemd is the building's operations manager: it hires every service, starts them in the right order (heating before the offices open), notices when one collapses and restarts it, shuts them down cleanly at night, and keeps a logbook of everything. The building runs because one manager owns the whole lifecycle.

Where the analogy stops working. A building manager delegates and sleeps. systemd is PID 1 — it personally is the parent (or ancestor) of every managed process, never off-duty, and if it exits the whole machine panics (Module 8's "PID 1 cannot die"). It's less a manager who oversees staff than the building's own nervous system: remove it and there is no building, only hardware.

🧪 Exercise A1.1 — Meet the manager (live commands need a booted systemd machine)
bash
systemctl --version | head -1        # which systemd? (works even in a container)
ps -p 1 -o pid,comm                  # what IS pid 1 on this machine?
systemctl is-system-running          # a one-word health summary (booted machines only)
✅ Expected result — click to reveal

On a booted Ubuntu 24.04 VM:

javascript
systemd 255 (255.4-1ubuntu8.15)
    PID COMMAND
      1 systemd
running

What to read out of it:

  • systemd 255 — the version; Ubuntu 24.04 ships systemd 255. Yours may differ by a point release.
  • PID 1 is systemd — Module 8's abstract "PID 1" now has a name and a job. Every service you'll manage is its descendant.
  • is-system-running returns one word: running (healthy), degraded (something failed — investigate with systemctl --failed), starting, etc. It's the fastest "is this box OK?" check there is.
  • In a non-systemd container you'll instead see ps -p 1 show something else (a container init) and is-system-running fail with Failed to connect to bus — that's the caveat from "Before you start"; switch to a VM for the live parts, or read on for the systemd-analyze exercises that work anywhere.

A2. Units — the things systemd manages

Official docs: systemd.unit(5)

🧠 systemd manages everything as units, each a plain-text INI-style file describing one manageable thing. The types you'll meet: .service (a program to run — the overwhelmingly common one), .timer (a schedule that triggers a service — the cron replacement, Part D), .socket (start a service on demand when its port is connected), .mount (a filesystem mount — Module 12), .target (a named group of units for reaching a system state, e.g. multi-user.target = "fully booted, multi-user, networked"). Unit files live in three places, in priority order: /etc/systemd/system/ (admin-created — yours), /run/systemd/system/ (runtime), and /usr/lib/systemd/system/ (package-provided — don't edit these directly). A file in /etc overrides one of the same name in /usr/lib — the mechanism for customizing a packaged service without touching its shipped file.

Real-world analogy — index cards in a recipe box

Each unit is an index card in the ops manager's recipe box: one card per thing that can be run, each stating what it is, what it needs first, and how to start and stop it. .service cards are dishes; .target cards are whole menus ("dinner service" = these ten dishes together); .timer cards say "start this dish at this time".

Where the analogy stops working. Recipe cards are inert until a cook reads them. Unit files are live wiring: systemd holds them all in memory as a dependency graph, and changing a card means telling systemd to re-read the box (systemctl daemon-reload) — edit a unit file and forget the reload, and systemd keeps cooking from the old card, which is the single most common "why didn't my change take effect?" surprise in this whole module.

🧪 Exercise A2.1 — Look at real units (list commands need a booted machine; the file inspection works anywhere)
bash
ls /usr/lib/systemd/system/*.target | head -5     # some packaged targets (works anywhere)
cat /usr/lib/systemd/system/multi-user.target      # what a target file looks like
systemctl list-units --type=service | head -8      # running services (booted machines only)
✅ Expected result — click to reveal
javascript
/usr/lib/systemd/system/basic.target
/usr/lib/systemd/system/graphical.target
/usr/lib/systemd/system/multi-user.target
...

then the target file:

javascript
[Unit]
Description=Multi-User System
Documentation=man:systemd.special(7)
Requires=basic.target
Conflicts=rescue.service rescue.target
After=basic.target rescue.service rescue.target
AllowIsolate=yes

then, on a booted machine, a table of .service units with their load/active/sub state and description.

What to read out of it:

  • Target files are tiny — a target is pure dependency wiring, no program of its own. Requires=basic.target and After=basic.target say "I need the basic system up first, and I come after it" — the ordering-and-dependency vocabulary that Part C teaches you to write.
  • The list-units table is the live view: each service's name, whether it's loaded, active, and its fine-grained sub state (running/exited/dead). This is the fleet operator's at-a-glance status.
  • Notice you read a packaged unit from /usr/lib — the read-only shelf. Your own units go in /etc/systemd/system, next section onward.

Part A — Interview questions

🎯 "What is systemd?" — asked verbatim in GeeksforGeeks' Linux Interview Questions (70+ questions, updated July 2026)

systemd is the init system and service manager on most modern Linux distributions — PID 1, started by the kernel at boot. It brings the system up (resolving dependencies and starting units in parallel where possible), supervises services (restarting crashed ones, stopping them cleanly), manages logging (the journal), timers, mounts, sockets, and more — everything as units. It replaced SysV init and Upstart, whose sequential shell-script approach couldn't do dependency-aware, parallel, supervised startup.

The details that separate candidates: "PID 1 and service manager" as the two-part definition; naming what it replaced and why (parallelism, dependency ordering, supervision, unified logging — not just "it's newer"); and knowing the everyday interface is systemctl for control and journalctl for logs. The mature nuance: systemd is controversial for its scope ("does too much"), which you can acknowledge while noting it's the de-facto standard you must know.

🎯 "Explain the Linux boot process." — asked verbatim in GeeksforGeeks' Linux Interview Questions (70+ questions, updated July 2026); InterviewBit (2025) asks it as "Guide me through the Linux boot process, from when you press the power button to the login prompt."

Firmware (BIOS/UEFI) powers on and runs its POST, then hands off to the bootloader (GRUB on most Linux), which loads the kernel and an initial ramdisk (initramfs) into memory. The kernel initializes hardware, mounts the real root filesystem, and starts PID 1 — systemd. systemd then brings the system to its default target (usually multi-user.target for servers, graphical.target for desktops), starting all the units that target requires — networking, logging, sshd, the application services — in dependency order, ending at a login prompt.

The details that separate candidates: naming the handoffs (firmware→bootloader→kernel→systemd→target) as a chain of increasing abstraction; that systemd reaching the default target is "boot finished"; the modern correction to older answers — there is no /etc/inittab or numeric runlevels on systemd systems, those are SysV concepts mapped onto targets (the runlevel question below); and systemd-analyze blame / critical-chain to debug slow boots, which shows real operational experience.

Part B — Managing services with systemctl

B1. start, stop, restart, status

Official docs: systemctl(1)

🧠 The four verbs you'll type thousands of times, all needing sudo (they change system state — Module 4): systemctl start name launches a service now; stop stops it now (using Module 8's TERM→wait→KILL ladder, configurable per service); restart stops then starts (the reflexive "have you tried turning it off and on again"); and — the most-used, no sudo needed — systemctl status name shows whether it's running, its PID, memory, and the last few log lines. reload (where supported) tells a service to re-read its config without restarting — no dropped connections, the graceful choice when a service supports it.

Counter-intuitive: restart and reload are not interchangeable, and the difference is an outage. restart stops the process — every in-flight request is dropped, every connection severed, and there's a gap (however brief) where the service is down. reload keeps the process running and just re-reads config — zero downtime. For a config change on a busy production service, reload is correct and restart is a small self-inflicted outage. But reload only works if the service implements it (many do; some don't), and it only applies to changes the service can absorb live — so knowing which your change needs, and which your service supports, is real operational judgment, not a reflex.
Real-world analogy — the shop's opening and closing

start unlocks and opens the shop; stop serves the last customer and locks up; restart closes and immediately reopens (customers mid-purchase are turned out); reload is handing the staff updated price lists mid-day without closing the doors. status is glancing through the window: lights on? staff present? busy?

Where the analogy stops working. A shopkeeper knows instantly whether the shop is open. A service can be in stranger states — active (running) but wedged, activating and stuck, failed and not coming back — and status's single top line (active/inactive/failed) can hide a service that's technically "up" but not actually working. That's why status's log tail matters as much as its state word, and why Part D's journalctl exists: the window-glance isn't always the truth.

🧪 Exercise B1.1 — Drive a real service (needs a booted machine; uses ssh, present on most servers)
bash
systemctl status ssh                 # is the SSH server running? (no sudo needed to look)
sudo systemctl restart ssh           # restart it (safe — won't drop YOUR existing session)
systemctl status ssh | head -12      # confirm it came back
✅ Expected result — click to reveal
javascript
● ssh.service - OpenBSD Secure Shell server
     Loaded: loaded (/usr/lib/systemd/system/ssh.service; enabled; preset: enabled)
     Active: active (running) since Wed 2026-09-02 14:02:11 UTC; 3h 40min ago
   Main PID: 812 (sshd)
      Tasks: 1 (limit: 4633)
     Memory: 5.8M
        CPU: 142ms
     CGroup: /system.slice/ssh.service
             └─812 "sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups"

Sep 02 14:02:11 myhost systemd[1]: Starting ssh.service...
Sep 02 14:02:11 myhost systemd[1]: Started ssh.service.

What to read out of it, line by line (this output is a literacy test interviewers use):

  • The coloured dot and Active: active (running) — the service is up. active (running) = a process is running; active (exited) = a one-shot that finished successfully; inactive (dead) = stopped; failed = crashed.
  • Loaded: … enabled — the second word is the boot setting (B2): enabled means it starts at boot, separate from whether it's running now.
  • Main PID: 812 (sshd) — Module 8's process, owned by this service. CGroup: shows systemd tracks all the service's processes in a control group (Module 16's cgroups, previewed) — so it can stop the whole tree cleanly, not just the main PID.
  • The bottom lines are the last few journal entries — status tails the logs automatically, which is why status is usually your first debugging command. The restart didn't drop your SSH session because your session is a separate process from the sshd listener being restarted — a genuinely reassuring fact worth understanding before you ever restart ssh on a box you're logged into.

B2. enable, disable, and the enabled-vs-active distinction

Official docs: systemctl(1)

🧠 The distinction interviewers love, because it separates people who've run servers from people who haven't. start/stop control whether a service is running right now. enable/disable control whether it starts automatically at boot — a completely independent setting. systemctl enable name creates a symlink (Module 3!) from the boot target's .wants/ directory to the unit, so systemd starts it when reaching that target. The four combinations all exist and all mean something:

active (running now)inactive (stopped now)
enabled (starts at boot)Normal running serviceWill start on next reboot — but is down now (did it crash?)
disabled (won't start at boot)Running now, but won't survive a reboot — the classic trapFully off

enable --now does both at once (enable and start); disable --now disables and stops.

Trap — "I started it, why is it gone after reboot?" systemctl start does not make a service start at boot. Start it, get it working, walk away — and the next reboot brings the machine up without it, because you never enabled it. This is one of the most common junior mistakes and a favourite interview question. The habit: for anything that should persist, sudo systemctl enable --now name — enable and start in one command — so "works now" and "works after reboot" are never accidentally different. Conversely, disabling without stopping (disable alone) leaves a service running that won't come back after reboot — a time bomb for whoever reboots next.
🧪 Exercise B2.1 — See the two settings independently (needs a booted machine)
bash
systemctl is-active ssh              # is it running NOW? → active
systemctl is-enabled ssh             # will it start at BOOT? → enabled
# The two are independent — check the boot setting of another service:
systemctl is-enabled cron            # → enabled (cron is enabled by default on Ubuntu)
systemctl status ssh | grep Loaded   # the Loaded line shows the enabled state too
✅ Expected result — click to reveal
javascript
active
enabled
enabled
     Loaded: loaded (/usr/lib/systemd/system/ssh.service; enabled; preset: enabled)

What to read out of it:

  • is-active and is-enabled are different questions with different answers — the whole point. Scripts and monitoring use these one-word commands (they set exit codes too, Module 5) rather than parsing status.
  • On a real machine, run systemctl is-enabled on a few services and you'll find some active but disabled (started manually, won't survive reboot) — go find out why, because that's exactly the state that causes "it worked until we rebooted" incidents.
  • The Loaded: line embeds the enabled state (; enabled;) so status answers both questions at once — but is-active/is-enabled are the scriptable forms.

Part B — Interview questions

🎯 "How do you manage services using systemctl?" — asked verbatim in GeeksforGeeks' Linux Interview Questions (70+ questions, updated July 2026)

sudo systemctl start/stop/restart name to control it now; reload to re-read config without downtime (if supported); systemctl status name to inspect state, PID, and recent logs; sudo systemctl enable/disable name to control auto-start at boot; enable --now / disable --now to combine boot-setting and immediate action; systemctl is-active / is-enabled for scriptable one-word answers. Logs via journalctl -u name.

The details that separate candidates: the enable-vs-start distinction stated unprompted (running-now vs starts-at-boot are independent — the classic trap); reload-vs-restart (graceful config re-read vs an outage); and reaching for enable --now as the safe default so "works now" and "works after reboot" can't drift apart. Reciting the five verbs is recall; explaining the two orthogonal axes (now/boot) is experience.

🎯 "How to change the default run level in Linux?" — asked verbatim in Turing's 100+ Linux interview questions (2025)

On systemd, "runlevels" are targets. Set the default with sudo systemctl set-default multi-user.target (the server default, ≈ old runlevel 3) or graphical.target (≈ runlevel 5). Check it with systemctl get-default; switch the running system to a target without rebooting with sudo systemctl isolate multi-user.target. The old SysV runlevels map: 0→poweroff.target, 1→rescue.target, 3→multi-user.target, 5→graphical.target, 6→reboot.target.

The details that separate candidates: knowing there is no /etc/inittab on modern systems and that numeric runlevels are a compatibility mapping onto targets, not the real mechanism; get-default/set-default/isolate as the actual commands; and that a "target" is a more flexible concept than a runlevel (arbitrary named groupings, not a fixed 0–6 ladder) — which reframes an old-Unix question as current knowledge.

Part C — Writing your own service

C1. A minimal service unit

🧠 This is where Module 8's cliffhanger closes for good. To make your program a managed service — restarted on crash, started at boot, logged — you write a .service unit. Its three sections:

  • [Unit] — metadata and ordering: Description=, and After=network.target ("start me after the network is up").
  • [Service] — how to run it: ExecStart= (the absolute-path command — Module 5's PATH lesson: no bare names here), Type= (simple for a normal foreground program, oneshot for a run-once task), Restart=on-failure (the supervision Module 8's nohup lacked!), RestartSec=5 (wait 5s between restarts), and User= (run as an unprivileged account, not root — Module 4's least-privilege).
  • [Install] — when to auto-start: WantedBy=multi-user.target (start me when the system reaches multi-user, i.e. at boot, once enabled).

Put the file in /etc/systemd/system/name.service, run sudo systemctl daemon-reload (so systemd re-reads its recipe box — A2's trap), then enable --now.

Real-world analogy — writing the job description

A unit file is a job description you hand HR (systemd): the title (Description), when the role starts relative to others (After), the actual duties (ExecStart), what to do if the employee quits unexpectedly (Restart=on-failure — re-hire), and which department roster they belong to (WantedBy). Write it once, and the manager handles hiring, supervising, and re-hiring forever.

Where the analogy stops working. HR reads a job description once, at hiring. systemd holds the whole org chart in memory and must be told when a description changes (daemon-reload) — edit the file, skip the reload, and it keeps running the old description while the file on disk says something new. The description isn't the source of truth once loaded; systemd's in-memory copy is. Forgetting daemon-reload after editing a unit is the module's signature mistake, and it fails silently — the service runs the stale config with no error.

🧪 Exercise C1.1 — Write a unit and validate it (the writing and systemd-analyze verify work anywhere; enable/start need a booted machine)
bash
# The program our service will run (a trivial forever-loop daemon):
sudo tee /usr/local/bin/hello-daemon.sh >/dev/null <<'EOF'
#!/usr/bin/env bash
while true; do echo "hello at $(date)"; sleep 60; done
EOF
sudo chmod +x /usr/local/bin/hello-daemon.sh
# The unit file:
sudo tee /etc/systemd/system/hello.service >/dev/null <<'EOF'
[Unit]
Description=Hello demo service
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/hello-daemon.sh
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF
systemd-analyze verify /etc/systemd/system/hello.service   # syntax/sanity check — WORKS ANYWHERE
echo "verify exit: $?"
# On a booted machine, now bring it to life:
sudo systemctl daemon-reload
sudo systemctl enable --now hello.service
systemctl status hello.service | head -6
✅ Expected result — click to reveal

systemd-analyze verify prints nothing and exits 0 when the unit is valid (verified on a systemd-255 machine) — silence is success (Module 1, still true). Then on a booted machine:

javascript
verify exit: 0
Created symlink /etc/systemd/system/multi-user.target.wants/hello.service → /etc/systemd/system/hello.service.
● hello.service - Hello demo service
     Loaded: loaded (/etc/systemd/system/hello.service; enabled; preset: enabled)
     Active: active (running) since Wed 2026-09-02 18:15:03 UTC; 1s ago
   Main PID: 20194 (hello-daemon.sh)

What to read out of it:

  • systemd-analyze verify is your pre-flight check — it catches typos, missing ExecStart, non-executable commands, and bad directives before you try to run the service. Run it on every unit you write; it works even in a container with no booted systemd.
  • enable --now printed the symlink it created — literally Module 3's symlink, from multi-user.target.wants/ to your unit. That symlink is what "enabled" means — its presence is why systemd starts your service at boot. You can see the whole mechanism in one line of output.
  • Loaded: … /etc/systemd/system/hello.service; enabled — your file, from the admin directory, enabled. Active: active (running), Main PID — it's alive and supervised. Module 8's nohup could never say enabled or restart this on crash; now systemd will.
  • Test the supervision: sudo kill <Main PID>, wait 5 seconds (RestartSec), systemctl status hello again — a new PID, because Restart=on-failure brought it back. That auto-restart is the entire reason services beat nohup.
🧪 Exercise C1.2 — A broken unit, caught before it runs (fails on purpose; works anywhere)
bash
sudo tee /etc/systemd/system/bad.service >/dev/null <<'EOF'
[Unit]
Description=Broken example

[Service]
Type=simple
ExecStart=
EOF
systemd-analyze verify /etc/systemd/system/bad.service
echo "verify exit: $?"
sudo rm /etc/systemd/system/bad.service      # clean up the example
✅ Expected result — an error, on purpose — click to reveal
javascript
bad.service: Service has no ExecStart=, ExecStop=, or SuccessAction=. Refusing.
Unit bad.service has a bad unit file setting.
verify exit: 1

What to read out of it:

  • An empty ExecStart= means "no command to run" — systemd refuses the unit outright, and verify caught it with a precise message and a non-zero exit (scriptable — you can lint units in CI, Module 10's discipline applied to infrastructure).
  • The lesson is the habit: systemd-analyze verify before daemon-reload, every time. A unit that fails verify would fail to start on a real machine too, but catching it with verify means you find out in one second instead of after a daemon-reload, an enable, and a confusing status reading failed.

C2. Dependencies, ordering, and targets

🧠 Services rarely stand alone — a web app needs the network up, maybe a database running first. systemd expresses this in [Unit] with two independent kinds of directive, and confusing them is a classic bug:

  • Ordering: After= / Before= — the sequence ("start me after the network"). Ordering alone does not pull anything in.
  • Requirement: Wants= / Requires= — the dependency ("I need the database service"). Wants= is a soft dependency (start it too, but I'll survive if it fails); Requires= is hard (if it fails, I fail).

You almost always want both together — Requires=db.service and After=db.service — because requiring a service without ordering means systemd may start them simultaneously, and your app races the database it "requires". Targets (A2) are the top of this graph: multi-user.target Wants= all the services enabled into it, giving you the fully-booted state.

Diagram source
flowchart TD
    T["multi-user.target<br>(fully booted)"] --> A["your-app.service"]
    A -->|"Requires= + After="| D["postgresql.service"]
    A -->|"After="| N["network-online.target"]
    D --> B["basic.target"]
    N --> B
    B --> S["sysinit.target<br>(early boot)"]
Counter-intuitive: Requires= without After= starts them at the same time. Requiring a dependency and ordering after it are separate switches — Requires=db.service pulls db in, but says nothing about when, so systemd, which starts everything in parallel by default, may launch your app and the database simultaneously. Your app then tries to connect to a database that isn't listening yet and crashes on startup — an intermittent, maddening bug that looks like a code problem and is actually a missing After=. The rule: a real dependency is almost always both Requires= (or Wants=) and After=. Parallel-by-default is systemd's great speed feature and this exact footgun at once.
Real-world analogy — the wedding schedule versus the guest list

Two separate documents run an event. The guest list (Requires/Wants) says who must be there; the schedule (After/Before) says in what order things happen. Inviting the caterer (guest list) doesn't tell them when to serve — that's the schedule's job. Confuse the two and the caterer arrives but serves dinner during the speeches.

Where the analogy stops working. A human caterer uses common sense about timing even if the schedule is vague. systemd has none — if you list a requirement but no ordering, it does the maximally-parallel thing and starts everything at once, cheerfully racing. The two documents aren't just good practice to keep separate; systemd literally treats them as unrelated, and supplies zero implicit timing. You must write both.

🧪 Exercise C2.1 — Inspect a real dependency graph (needs a booted machine)
bash
systemctl list-dependencies multi-user.target | head -15   # what does "fully booted" pull in?
systemctl cat ssh                                          # ssh's actual unit file, with its After=/Wants=
✅ Expected result — click to reveal
javascript
multi-user.target
● ├─ssh.service
● ├─cron.service
● ├─rsyslog.service
● ├─systemd-logind.service
● ├─basic.target
● │ ├─...
...

then ssh's unit showing its [Unit] After=network.target auditd.service and [Install] WantedBy=multi-user.target.

What to read out of it:

  • list-dependencies draws the tree systemd walks at boot: multi-user.target wants sshd, cron, logging… each of which pulls in its dependencies, down to basic.target and the early-boot units. This is the boot process (Part A's interview question) as a live graph.
  • systemctl cat prints a unit's real, merged configuration — the fast way to read what a service actually does, including any admin overrides. Reading After=/Wants= in real units trains the vocabulary you'll write in your own.
  • The green dots are live state — this isn't a static file, it's the running dependency graph with each unit's status attached. Boot order and current health, in one view.

Part C — Interview questions

🎯 "What are Daemons?" — asked verbatim in Turing's 100+ Linux interview questions (2025); GeeksforGeeks (2026) has the companion "What is a daemon in Linux?" (covered in Module 8)

Background processes with no controlling terminal that provide services continuously — sshd, cron, databases, web servers (Module 8's TTY-? definition). On modern Linux, daemons are managed as systemd services: rather than the program daemonizing itself (the old double-fork ritual), you write a .service unit with Type=simple and let systemd own the lifecycle — starting, supervising, restarting, and logging it. The unit file is how you make a program a daemon today.

The details that separate candidates: connecting the Module 8 concept (a daemon is a detached background process) to the Module 11 practice (you don't hand-daemonize any more — systemd does it via a unit); Type=simple vs Type=forking (the latter for legacy programs that still daemonize themselves, where systemd needs to know to expect the fork); and that Restart=on-failure gives you supervision the old self-daemonizing model never had.

🎯 A synthesis the banks imply — "how do you turn a script into a service that survives crashes and reboots?"

Write a .service unit in /etc/systemd/system/: [Service] with the absolute-path ExecStart=, Restart=on-failure and RestartSec= for crash supervision, User= for least privilege; [Install] with WantedBy=multi-user.target for boot; [Unit] with After=/Requires= for dependencies. Then systemd-analyze verify it, sudo systemctl daemon-reload, and sudo systemctl enable --now name. Logs go to the journal automatically (journalctl -u name).

The details that separate candidates: the full lifecycle in order (write → verify → daemon-reload → enable --now); why each directive earns its place (Restart for supervision, User for security, After+Requires for dependencies); the daemon-reload-after-edit habit; and framing it against the alternatives — this is strictly better than nohup (Module 8, no supervision) and cron (no long-running supervision), which is the reason services exist.

Part D — Logs and timers

D1. journalctl — the systemd log

Official docs: journalctl(1)

🧠 systemd captures the stdout/stderr of every service into a central, structured, queryable log — the journal — read with journalctl. This is a genuine upgrade over scattered text files in /var/log (Module 14 covers both): the journal knows which service each line came from, when, at what priority, and lets you filter on all of it. The queries you'll use constantly:

  • journalctl -u name — all logs for one service (the everyday one).
  • journalctl -u name -f — follow live (Module 3's tail -f, now service-aware).
  • journalctl -u name --since "10 min ago" — time-windowed.
  • journalctl -u name -p err — only error-priority and worse.
  • journalctl -u name -e — jump to the newest entries.
  • journalctl -b — only this boot (since the last reboot); -b -1 the previous boot.

Because a service's logs are captured automatically, you no longer configure log files for your own services — write to stdout/stderr (Module 5) and systemd journals it. That's why Module 8's nohup wrote to nohup.out and a real service doesn't: the manager owns the logs.

Counter-intuitive: systemctl status shows only the last ~10 log lines — the real story is in journalctl. status's log tail is a teaser; a service that's been crash-looping for an hour shows only its most recent gasp in status. The actual diagnosis — when it started failing, what the first error was, how often it's restarting — lives in journalctl -u name, scrolled back to where the trouble began. Junior engineers read status and stop; the pattern that solves incidents is status for the now, then journalctl -u name --since … for the story. Reading only the teaser is why the same incident gets "fixed" three times.
Real-world analogy — the central switchboard log versus sticky notes

Old-style logging was every service scribbling on its own sticky note in its own drawer (/var/log/thisservice.log) — you had to know which drawer. The journal is a central switchboard that timestamps every message, tags it with who called and how urgent, and lets you ask "show me everything from the database service, errors only, since 2pm" in one query. status's tail is glancing at the last message on the board; journalctl is searching the whole board.

Where the analogy stops working. A switchboard log is permanent unless someone shreds it. The journal is size-capped and rotates by default — old entries are discarded to bound disk use, and on many systems it's not even persistent across reboots unless configured (Storage=persistent). So "it's in the journal" has a time horizon; for long-term retention or fleet-wide search you ship logs to a central system (Module 14). The board is excellent for now and recent; it is not your archive.

🧪 Exercise D1.1 — Read a service's story (needs a booted machine)
bash
journalctl -u ssh --since "today" | head -15    # ssh's log for today
journalctl -u ssh -p warning                    # only warnings and worse
journalctl -b | tail -5                          # tail of this boot's whole journal
# (to follow live in another terminal: journalctl -u ssh -f )
✅ Expected result — click to reveal
javascript
Sep 02 14:02:11 myhost systemd[1]: Starting ssh.service...
Sep 02 14:02:11 myhost sshd[812]: Server listening on 0.0.0.0 port 22.
Sep 02 14:02:11 myhost sshd[812]: Server listening on :: port 22.
Sep 02 14:02:11 myhost systemd[1]: Started ssh.service.
Sep 02 15:47:03 myhost sshd[1423]: Accepted publickey for zaeem from 10.0.0.5 port 51234 ssh2
...

What to read out of it:

  • Every line is tagged: timestamp, hostname, the process (sshd[812] — name and PID), then the message. The journal added that structure automatically — the service just wrote plain text to stderr.
  • The Accepted publickey for zaeem from 10.0.0.5 line is a real security-relevant event (a successful SSH login — Module 15) surfaced by a simple -u ssh query. Filtering one service's logs out of the whole system's noise is what makes the journal powerful.
  • -p warning filtered by priority — the journal knows each line's severity (the err/warning/info levels), so "show me only problems" is one flag, not a grep guess. This is the structured-logging advantage over plain text files.

D2. Timers — the modern cron

🧠 Module 10's cron has a modern successor in systemd: a timer unit (.timer) that triggers a service unit (.service) of the same name on a schedule. Why prefer it over cron: the job's output goes to the journal (queryable, not silently mailed nowhere — Module 10's cron trap, solved); Persistent=true runs a missed job after boot (cron just skips it — the anacron feature, built in); dependencies and resource limits apply like any service; and systemctl list-timers shows every schedule and its next run in one view. The schedule uses OnCalendar= with systemd's calendar syntax — and systemd-analyze calendar 'expression' tells you exactly when it'll fire, a dry-run cron never offered.

Real-world analogy — the scheduler that keeps its own logbook

cron was the bare timer switch — fires and forgets, no record (Module 10). A systemd timer is a scheduler integrated with the whole operations system: it fires the job, the job's output lands in the central logbook (journal), missed firings are caught up after an outage, and one board (list-timers) shows every scheduled task and when each runs next. Same "do this on a schedule", wrapped in the manager's supervision and record-keeping.

Where the analogy stops working. cron is one line in one file — trivially simple to write and read. A timer is two files (.timer + .service) plus daemon-reload and enable — more ceremony for a one-off. The trade is real: for a quick personal job, cron's one line still wins; for anything production — that must log, survive missed runs, and be visible fleet-wide — the timer's extra structure pays for itself. Knowing which to reach for is the judgment.

🧪 Exercise D2.1 — Design a schedule and verify it (the calendar checks work anywhere; enable needs a booted machine)
bash
# systemd-analyze calendar tells you EXACTLY when an expression fires — the dry run cron never had:
systemd-analyze calendar '*-*-* 02:30:00'      # daily at 02:30
systemd-analyze calendar 'Mon..Fri 09:00'      # weekdays at 09:00
systemd-analyze calendar 'weekly'              # a named shortcut
# On a booted machine, list every active timer and its next run:
systemctl list-timers --all | head -8
✅ Expected result — click to reveal
javascript
Normalized form: *-*-* 02:30:00
    Next elapse: Thu 2026-09-03 02:30:00 UTC
       From now: 3h 41min left
  Original form: Mon..Fri 09:00
Normalized form: Mon..Fri *-*-* 09:00:00
    Next elapse: Thu 2026-09-03 09:00:00 UTC
  Original form: weekly
Normalized form: Mon *-*-* 00:00:00
    Next elapse: Mon 2026-09-07 00:00:00 UTC

then, on a booted machine, list-timers showing each timer, its NEXT run, LAST run, and the unit it activates.

What to read out of it (the Next elapse dates are relative to when you run it):

  • systemd-analyze calendar proved what each expression means — Mon..Fri 09:00 normalized to "weekdays at 09:00:00", next firing this Thursday. Compare cron, where you decode 30 2 * * * in your head and hope. Verifying the schedule before deploying it is the whole point — no more "the backup ran at the wrong time" surprises.
  • weekly is a named shortcut (systemd has daily, hourly, monthly too) — readable schedules without cron's five-field puzzle.
  • list-timers is the fleet-friendly view cron never had: all scheduled work and its next-run time in one place. On a real server it's the first command for "what automated jobs run on this box, and when?"

Part D — Interview questions

🎯 "What is journalctl?" / the logging follow-up to "What is systemd?" — GeeksforGeeks (2026) states "Journalctl displays logs managed by systemd" as part of its systemd/logging coverage

journalctl queries the systemd journal — the central, structured, indexed log of every service's output plus kernel and boot messages. Key usage: -u name (one service), -f (follow live), --since/--until (time windows), -p err (by priority), -b (this boot). It replaces hunting through scattered /var/log files: the journal tags every entry with its source service, timestamp, and priority, making "errors from the database service in the last hour" a one-line query.

The details that separate candidates: structured/indexed vs plain-text log files (filter by unit and priority, not grep); that status shows only a teaser tail while journalctl -u has the full story; the retention caveat (journals rotate and may be non-persistent — Storage=persistent, and ship to central logging for real retention); and that your own services get journaled for free by writing to stdout/stderr, so you no longer manage per-service log files.

🎯 Corpus note — timers and unit files

The published banks cover systemd's existence and systemctl basics well (quoted throughout this module) but barely touch writing unit files, timers, or journalctl querying as dedicated questions — yet these are exactly what a hands-on systemd interview probes: "write me a service unit for this script", "how would you schedule a backup on a modern system?", "a service keeps crashing — walk me through debugging it". Prepare the artifacts: a minimal .service from memory (the three sections and the key directives), the write → verify → daemon-reload → enable --now lifecycle, a timer as the cron alternative with its two files, and the status → journalctl -u --since debugging flow. That's the live systemd interview, and the corpus gap makes fluency here a clear differentiator.

Part E — Toolkit

Official docs: Part E is reference material — every source it draws on is linked in the E3 documentation table below.

E1. Production practice — symptoms and fixes

SymptomWhat is really happeningWhat to runThe fix
Service works now, gone after rebootIt was started but never enabledsystemctl is-enabled name → disabledsudo systemctl enable --now name
Edited a unit file, change had no effectsystemd is still running the in-memory copy of the old unitsystemctl cat name (shows loaded config)sudo systemctl daemon-reload, then restart the service
Service active but not actually workingProcess is up but wedged; status's state word hides itjournalctl -u name --since "1 hour ago"Read the real logs, not just the status line; restart or fix per the errors
App crashes at boot, works when started laterRequires=db without After=db — it races the dependencysystemctl cat name — check for After=Add After=db.service (and network-online.target if it needs the network up)
Config change caused a brief outageUsed restart (stops the process) where reload would doCheck if the service supports reloadsudo systemctl reload name for config-only changes
Scheduled job (timer) never ran / ran at wrong timeWrong OnCalendar=, or the timer isn't enabledsystemd-analyze calendar 'expr'; systemctl list-timersVerify the expression, enable --now the .timer (not the .service)
systemctl fails with "Failed to connect to bus"Not booted with systemd as PID 1 (a minimal container)ps -p 1 -o comm=Use a real VM for live commands; systemd-analyze verify/calendar still work

E2. Capstone — four tickets

Work these like real tickets: read the ticket, write the unit/commands and the explanation you would send, then open the worked answer. Everything needed was taught in this module.
🎫 Ticket 1 — "Our app runs fine when we start it by hand but vanishes after every reboot"

Ticket text: "The metrics-collector app runs great — we systemctl start metrics after each deploy — but every maintenance reboot it comes up missing and someone has to start it again. Fix it permanently."

Worked answer: the direct cause is the enable-vs-start distinction — start runs it now but never registers it for boot, so every reboot brings the box up without it. Confirm: systemctl is-enabled metrics → disabled (while is-active → active, the classic split). Fix: sudo systemctl enable metrics (it's already running, so no --now needed — but enable --now is the safe habit that makes the two states impossible to desync). Then verify the fix survives what it's meant to: on a maintenance window, actually reboot and confirm is-active comes back active, because "enabled" is a promise you should test once. The deeper process fix for the ticket: deploys should enable --now, never bare start, so this can't recur — and monitoring should alert on is-enabled disabled for services that must persist.

🎫 Ticket 2 — "I updated nginx's config and my change isn't taking effect"

Ticket text: "Edited /etc/systemd/system/nginx.service to add a memory limit, ran systemctl restart nginx, but systemctl show nginx still reports the old limit. The file is definitely saved. What's wrong?"

Worked answer: systemd is running its in-memory copy of the unit, loaded at last boot or last reload — editing the file on disk doesn't reach the running manager until you tell it to re-read. systemctl cat nginx will show systemd still holds the old content. Fix: sudo systemctl daemon-reload (re-read all unit files), then sudo systemctl restart nginx (apply). The order matters — reload updates systemd's model, restart applies it to the process. systemd even warns about this: after editing a unit it often prints "Warning: The unit file changed on disk. Run 'systemctl daemon-reload'". The habit to install team-wide: edit unit → daemon-reload → restart, always in that order; and prefer systemctl edit nginx (which creates an override and reloads for you) over hand-editing the file, to make the reload impossible to forget.

🎫 Ticket 3 — "New service crashes on boot but starts fine if I run it manually 30 seconds later"

Ticket text: "Our app.service fails immediately at boot with a database connection error, but systemctl start app by hand a moment after boot works perfectly. The unit has Requires=postgresql.service. Diagnose."

Worked answer: textbook Requires= without After=. Requires=postgresql.service pulls Postgres in but says nothing about ordering, so systemd — parallel by default — launches app and Postgres simultaneously at boot; app wins the race, tries to connect to a database still starting up, and crashes. Starting by hand "later" works only because Postgres is up by then — masking the real bug. Confirm with systemctl cat app (a Requires= with no matching After=) and journalctl -u app -b (connection-refused at the exact boot timestamp). Fix: add After=postgresql.service to [Unit] (keep the Requires= too — you want both: pulled in and ordered). Consider also Restart=on-failure so a transient race self-heals, and if the app needs the network, After=network-online.target with Wants=network-online.target. The one-sentence lesson for the postmortem: in systemd, "depends on" and "starts after" are separate switches, and a real dependency needs both.

🎫 Ticket 4 — "Move our nightly backup off cron onto something we can actually monitor"

Ticket text: "Our backup is a cron line that failed silently for a week last month (Module 10's incident). Leadership wants it on something visible and catch-up-capable. Build it with systemd."

Worked answer: convert to a timer + service pair. The service (backup.service): [Service] Type=oneshot, ExecStart=/usr/local/bin/backup.sh (absolute path), User=backup — a plain run-once unit whose output the journal captures automatically (solving cron's silent-failure trap: journalctl -u backup shows every run). The timer (backup.timer): [Timer] OnCalendar=*-*-* 02:30:00, Persistent=true (runs a missed backup after the machine comes back — the catch-up cron lacked), [Install] WantedBy=timers.target. Then: systemd-analyze calendar '*-*-* 02:30:00' to prove it fires at 02:30, daemon-reload, sudo systemctl enable --now backup.timer (enable the timer, not the service), and systemctl list-timers to confirm its next run is visible. Monitoring hooks into systemctl is-failed backup.service or the journal. The framing to deliver: this gives leadership exactly what they asked — visibility (list-timers, journal), catch-up (Persistent), and the same script — with the silent-failure mode designed out rather than papered over.

E3. Documentation reference

TopicAuthoritative sourceVerified link
systemd itself, and controlsystemd(1), systemctl(1)systemd(1) · systemctl(1)
Unit filessystemd.unit(5), systemd.service(5), systemd.special(7)systemd.unit(5) · systemd.service(5) · systemd.special(7)
Logsjournalctl(1)journalctl(1)
Timerssystemd.timer(5)systemd.timer(5)
The boot sequence beneath this moduleCompanion trackUntitled

E4. Self-assessment

Answer out loud, without notes. The section number tells you where to re-read.

  1. What are systemd's two roles, and what is it as a process? (A1)
  2. What is a "unit"? Name four types and where unit files live (three directories, priority order). (A2)
  3. Walk the boot process from power button to login prompt, naming each handoff. (A1 interview)
  4. start vs enable — what does each control, and what's the classic mistake? (B2)
  5. restart vs reload — the difference, and when each is correct on a busy production service. (B1)
  6. Read a systemctl status block aloud: what do Active:, Loaded: … enabled, Main PID, and CGroup each tell you? (B1)
  7. Write a minimal .service unit from memory — the three sections and the key directive in each. (C1)
  8. What is the full lifecycle from editing a unit file to a running, boot-persistent service? Which step is silently skipped most often? (C1)
  9. Requires= vs After= — why do you almost always need both, and what breaks if you have only Requires=? (C2)
  10. Where do a service's logs go, and why is systemctl status not enough to debug a crash-loop? (D1)
  11. Name three reasons to prefer a systemd timer over cron, and the one reason to still use cron. (D2)
  12. How would you turn a script into a crash-supervised, boot-starting, journaled service — command by command? (C-part synthesis)

E5. Sources

Interview-question sources used in this module (fetched and quoted verbatim during research, September 2026):

GeeksforGeeks — Linux Interview Questions (70+) (updated July 2026) · Turing — 100+ Linux Interview Questions (2025) · InterviewBit — Linux Interview Questions (2025).

Corpus honesty note: the banks cover systemd's fundamentals well — "what is systemd", "manage services with systemctl", the boot process, runlevels-as-targets, daemons — all quoted above. They largely skip the hands-on craft — writing unit files, dependency directives, journalctl querying, and timers — which is precisely where a practical systemd interview goes. This module teaches those to the live "write me a unit / debug this service" format the printed corpus doesn't capture, and flags the gap honestly.

All documentation links on this page were fetched and confirmed reachable on 2 September 2026. systemctl --version, systemd-analyze verify, and systemd-analyze calendar outputs were generated on systemd 255 (Ubuntu 24.04); the live systemctl status/journalctl outputs are the canonical format from a booted Ubuntu server, since this authoring environment's container does not run systemd as PID 1.

Next: services run and are supervised. Time to give them somewhere to store data — Module 12 — Storage, Disks, and Filesystems.
Spotted a mistake or want something added? Send me a note.