Phase 1 · FOUNDATIONS
Packages & services — apt, systemd, journalctl
By the end of today
- Install software the right way with apt update and apt install
- Read a service's state with systemctl status and is-enabled
- Know the difference between start and enable, and read journalctl -u
From package to running service
Two programs run the show on an Ubuntu server, and today you meet both. apt installs software; systemd runs it. Almost every service you will ever operate — a web server, a database, the SSH daemon — arrives through the first and is kept alive by the second.
apt (Advanced Package Tool) is Ubuntu’s package manager. Instead of downloading a program from some website and hoping it works, you ask apt and it fetches the software, every library it depends on, and a signed record of where it came from, straight from Ubuntu’s repositories. Two commands cover almost all daily use: apt update refreshes the local list of what is available and at which version (it installs nothing), and apt install <name> installs a package plus its dependencies. Running update before install is the habit that avoids fetching a stale version. Both change system files, so you prefix them with sudo.
Installing a service package does more than copy files. The package ships a unit file — a small text description of how to run the program — and registers it with systemd, the very first process the kernel starts (PID 1) and the supervisor of every service after it. You drive systemd through systemctl:
systemctl status <unit>— is it running, when did it start, last log lines.systemctl start/stop/restart— control it right now, this boot.systemctl enable/disable— whether it starts automatically on boot.
That start/enable split trips up everyone once. They are independent: a service you start but never enable runs now but is gone after a reboot; one you enable but never start will not come up until the next boot. In production you almost always want both — systemctl enable --now <unit> does them together.
Real world: Think of
aptas the hiring agency andsystemdas the shift manager. The agency brings a qualified worker onto the premises with all their tools. The shift manager decides when they clock in (start), whether they are on the permanent rota that shows up every morning (enable), and keeps the attendance log (journalctl). Hiring someone does not put them on the rota, and being on the rota does not mean they are working right now — two separate decisions, exactly like start and enable.
The last piece is logs. Systemd services do not scatter their own files; they write to a central journal, and you read one service’s slice of it with journalctl -u <unit> — add -e to jump to the newest lines or -f to follow live. It is the modern replacement for hunting through /var/log by hand.
A named example ties it together. When you apt install nginx, the nginx package drops an nginx.service unit into place and asks systemd to track it. From that moment systemctl status nginx tells you if the web server is up, systemctl enable nginx makes it survive reboots, and journalctl -u nginx shows exactly why it refused to start when a config typo breaks it.
Hands-On Lab
Budget about 25 minutes. Open your WSL2 Ubuntu 24.04 terminal. systemctl and journalctl need systemd running as PID 1 — modern WSL2 enables it by default; if step 4 says systemd is not booted, see the first Common Error below. Type each command yourself and read every line of output.
# 1. Refresh the package index. update fetches lists only — it installs nothing.
sudo apt update
# Output (trimmed; your mirror URLs and counts differ):
# Hit:1 http://archive.ubuntu.com/ubuntu noble InRelease
# Get:2 http://archive.ubuntu.com/ubuntu noble-updates InRelease [126 kB]
# Fetched 126 kB in 1s (98.2 kB/s)
# Reading package lists... Done
# Building dependency tree... Done
# Reading state information... Done
# 12 packages can be upgraded. Run 'apt list --upgradable' to see them.
# 2. Before installing, ask apt what it knows about the package.
apt policy nginx
# Output (your exact point version differs):
# nginx:
# Installed: (none) <- not installed yet
# Candidate: 1.24.0-2ubuntu7.5 <- what apt install would fetch
# Version table:
# 1.24.0-2ubuntu7.5 500
# 500 http://archive.ubuntu.com/ubuntu noble-updates/main amd64 Packages
# 3. Install nginx and its dependencies. -y answers "yes" to the prompt.
sudo apt install -y nginx
# Output (trimmed to the lines that matter):
# The following NEW packages will be installed:
# nginx nginx-common nginx-core
# 0 upgraded, 3 newly installed, 0 to remove and 0 not upgraded.
# ...
# Setting up nginx-core (1.24.0-2ubuntu7.5) ...
# Setting up nginx (1.24.0-2ubuntu7.5) ...
# Processing triggers for man-db (2.12.0-4build2) ...
# 4. What did that give us? status reads the unit's live state.
# On Ubuntu, installing a service package also STARTS and ENABLES it.
systemctl status nginx
# Output (PIDs and the timestamp are yours; press q to quit the pager):
# ● nginx.service - A high performance web server and a reverse proxy server
# Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled)
# Active: active (running) since Thu 2026-07-09 10:15:42 UTC; 8s ago
# Main PID: 1237 (nginx)
# Tasks: 2 (limit: 9345)
# CGroup: /system.slice/nginx.service
# ├─1237 "nginx: master process /usr/sbin/nginx ..."
# └─1238 "nginx: worker process"
# Tasks counts the processes: 1 master + 1 worker = 2 here. nginx's worker
# count follows worker_processes auto, so on a multi-core box you'd see one
# worker per core and a higher Tasks number.
# 5. Prove it is actually serving. -I asks for headers only.
curl -I http://localhost
# Output:
# HTTP/1.1 200 OK
# Server: nginx/1.24.0 (Ubuntu)
# Content-Type: text/html
# Content-Length: 615
# 6. Stop it (no output on success), then confirm the state changed.
sudo systemctl stop nginx
systemctl is-active nginx
# Output:
# inactive
# 7. curl now fails — the service really is down, not just "quiet".
curl -I http://localhost
# Output:
# curl: (7) Failed to connect to localhost port 80 after 0 ms: Couldn't connect to server
# 8. Start it again for this boot.
sudo systemctl start nginx
systemctl is-active nginx
# Output:
# active
# 9. Will it survive a reboot? is-enabled answers the boot question, not the "now" question.
systemctl is-enabled nginx
# Output:
# enabled
# 10. Read nginx's own log slice from the journal. -n 6 = last 6 lines, --no-pager prints inline.
journalctl -u nginx -n 6 --no-pager
# Output (hostname trimmed; your timestamps differ):
# Jul 09 10:15:42 systemd[1]: Started nginx.service - A high performance web server...
# Jul 09 10:16:20 systemd[1]: Stopping nginx.service...
# Jul 09 10:16:20 systemd[1]: nginx.service: Deactivated successfully.
# Jul 09 10:16:20 systemd[1]: Stopped nginx.service.
# Jul 09 10:16:28 systemd[1]: Starting nginx.service - A high performance web server...
# Jul 09 10:16:28 systemd[1]: Started nginx.service - A high performance web server...
Before you close the terminal, say the lifecycle back out loud: apt brought the software in, systemctl status and is-active told you it was running now, is-enabled told you it survives a reboot, and journalctl -u showed you the exact log line for every start and stop you just triggered.
Common Errors & Fixes
These three catch almost everyone in their first week with services. Read the error text slowly — parsing it is the actual skill.
Common error: Running
systemctl status nginxin a WSL2 shell that never booted systemd prints:System has not been booted with systemd as init system (PID 1). Can't operate. Failed to connect to bus: Host is downWhy:
systemctlis only a client — it talks tosystemd, which must be running as PID 1. If WSL2 started your distro without systemd, there is nothing forsystemctlto connect to.Fix: Add a
[boot]section withsystemd=trueto/etc/wsl.conf, then runwsl --shutdownin PowerShell and reopen the terminal. Recent WSL2 does this by default; older setups need the flag.How you’d spot it in prod: You’ll hit the identical message inside a plain Docker container — and there it is expected, not a bug. Containers run one process directly as PID 1 with no init, which is exactly why you never run
systemctlin a Dockerfile.
Common error: Installing a package without
sudo—apt install nginxas a normal user — fails immediately:E: Could not open lock file /var/lib/dpkg/lock-frontend - open (13: Permission denied) E: Unable to acquire the dpkg frontend lock (/var/lib/dpkg/lock-frontend), are you root?Why:
aptwrites to root-owned system files under/var/lib/dpkgand/etc, and it takes a lock so two installs can’t run at once. A normal user can’t open that lock file, so apt refuses before touching anything.Fix: Prefix with
sudo:sudo apt install nginx. Neverchmodthe lock file — the fix is privilege, granted deliberately per command.How you’d spot it in prod: The same
dpkglock error in a CI or provisioning log usually means the install step isn’t running as root, or a backgroundunattended-upgradesjob already holds the lock. Run install steps as root and let the other apt process finish before retrying.
Common error: Starting a service but forgetting to enable it — so after a reboot
systemctl status nginxshows:○ nginx.service - A high performance web server and a reverse proxy server Loaded: loaded (/usr/lib/systemd/system/nginx.service; disabled; preset: enabled) Active: inactive (dead)Why:
systemctl startonly affects the current boot; it never touches boot-time behaviour. Because nobody ranenable, theLoaded:line readsdisabledand systemd never started the service when the machine came back up.Fix:
sudo systemctl enable --now nginx—enablecreates the boot symlink and--nowalso starts it immediately, so both the “now” and the “on boot” questions are answered at once.How you’d spot it in prod: The classic report is “the app works after we start it by hand, but it disappears every time the box reboots.” That sentence is a missing
enable, ten times out of ten — check theLoaded:line fordisabled.
Packages & Services Interview Questions
Cover the answers below and say your own version out loud first — explain start-versus-enable, and what apt update does, before you reveal each answer. Recalling before revealing is what makes these stick when an interviewer asks them cold. The four questions and answers render right after this note.
Go Deeper
Optional extras if you have ~20 more minutes today:
- 5 min — Run
systemctl list-units --type=service --state=runningto see every service alive on your box right now. That list is the machine’s job description. - 5 min — Run
journalctl -u nginx -fin one terminal, thensudo systemctl restart nginxin another, and watch the log update live.Ctrl+Cto stop following. - 10 min — Read the “Managing services” and “Package management” sections of the Linux for DevOps guide for how apt and systemd fit the wider server picture.
What is the difference between systemctl start and systemctl enable? Both
They are two separate decisions and people conflate them constantly. start launches the service right now, this boot — the moment you run it, the process comes up, and it stays up until you stop it or the machine reboots. enable is about the future: it creates the boot-time symlink so systemd starts the service automatically every time the machine boots. So a service you start but never enable runs now and is gone after a reboot; one you enable but never start won't come up until the next boot. In production you almost always want both, which is why systemctl enable --now does start and enable in a single command.
What does apt update do, and how is it different from apt upgrade? Both
apt update refreshes the local index of what packages exist and at which version — it talks to the repositories and downloads the lists, but it installs and changes nothing on your system. apt upgrade is the one that actually acts: it downloads and installs newer versions of packages you already have, using the index that update just refreshed. The habit is to run update first, then install or upgrade, so you're working from a current list rather than a stale one. A common gotcha in Dockerfiles and provisioning scripts is skipping update, then apt install fails to find a package or pulls an outdated one.
A service failed to start. How do you find out why? Service
Two commands, in order. First systemctl status <unit> — it shows whether the unit is active or failed, the exit code of the last start attempt, and the final few log lines, which is often enough to see the problem. If it isn't, journalctl -u <unit> shows that service's full log slice; I add -e to jump to the newest entries or -n 50 for the last fifty lines. For a config-driven service like nginx I'd also run its own validator — nginx -t — because the journal will usually point at a bad config line. The key idea is that systemd centralises logs, so you never have to guess which file in /var/log belongs to the service.
Why can't you use systemctl inside a typical Docker container? Product
Because systemctl is a client that talks to systemd, and systemd has to be running as PID 1. A normal container doesn't boot an init system; it runs your one application process directly as PID 1, so there's no systemd for systemctl to connect to, and you get 'System has not been booted with systemd.' That's by design: containers are meant to run a single foreground process, and the container runtime handles restart and lifecycle instead of systemd. So inside a container you start your app directly in the Dockerfile's CMD, and you rely on the orchestrator — Docker or Kubernetes — for the enable-on-boot and restart behaviour systemd would give you on a VM.
Mark Day 10 complete
Your service is running — tomorrow you learn how it talks to the world: IP addresses, DNS, ports and your first curl.
Stuck on today’s lab? Ask in Mission 90 Q&A