๐๏ธ Lesson 8: Services and Logs: Starting, Stopping and Reading What Your System Says
How the programs that run in the background are managed, and where to look when one of them misbehaves
What is a service?
Some programs are not meant to be opened and closed by you. A service (also called a daemon, pronounced "demon", which sounds scarier than it is) is a program that runs quietly in the background, waiting to do a job for someone else: a web server answering visitors, an SSH server letting you log in from afar, a scheduler running timed jobs. Services start when the machine boots and keep running whether anyone is logged in or not.
Something has to start them, restart them if they crash, and stop them politely when the machine shuts down. On Ubuntu that something is systemd. It is process number 1, the parent of everything (lesson 7), and the command you use to talk to it is systemctl.
๐ก A different practice machine for this lesson. The tiny container from lesson 1 has no systemd, so it cannot run services. For this lesson you need a full Ubuntu: a virtual machine, a real computer, or Windows' WSL with systemd switched on (see Microsoft's documentation; from the documentation, not run here). The examples below, and the video, were run on a full Ubuntu 24.04 LTS (a systemd container) that was set up for this lesson, with a user called ubuntu who is allowed to use sudo and whose password is practice. Your times, process numbers and sizes will differ.
The manager is process number 1
ubuntu@practice:~$ ps -p 1
PID TTY TIME CMD
1 ? 00:00:00 systemd
ubuntu@practice:~$ systemctl list-units --type=service --state=running --no-pager
UNIT LOAD ACTIVE SUB DESCRIPTION
cron.service loaded active running Regular background program processing daemon
dbus.service loaded active running D-Bus System Message Bus
rsyslog.service loaded active running System Logging Service
systemd-journald.service loaded active running Journal Service
systemd-resolved.service loaded active running Network Name Resolution
Legend: LOAD โ Reflects whether the unit definition was properly loaded.
ACTIVE โ The high-level unit activation state, i.e. generalization of SUB.
SUB โ The low-level unit activation state, values depend on unit type.
5 loaded units listed.
Compare this with lesson 7, where process 1 was bash. Here process 1 is systemd. systemctl list-units --type=service --state=running lists the services running right now. The word unit is systemd's name for anything it manages, and a service is one kind of unit. The columns:
- UNIT is the name, always ending in .service for a service.
- LOAD says whether its definition was read properly (loaded).
- ACTIVE and SUB give its state: active running means it is alive and working.
- DESCRIPTION says what it is for in plain words.
You can see cron (the scheduler), dbus (the message bus that programs use to talk to each other), rsyslog and systemd-journald (which keep the logs) and systemd-resolved (which looks up names, lesson 9). A desktop or a busy server lists many more. The --no-pager option told systemctl to print everything at once. Without it, when the output is longer than your screen, systemctl opens it in the less viewer from lesson 6: press the Space bar for the next page and q to quit.
Ask a service how it is: status
ubuntu@practice:~$ systemctl status cron --no-pager
โ cron.service - Regular background program processing daemon
Loaded: loaded (/usr/lib/systemd/system/cron.service; enabled; preset: enabled)
Active: active (running) since Thu 2026-10-22 11:14:28 UTC; 28s ago
Docs: man:cron(8)
Main PID: 80 (cron)
Tasks: 1 (limit: 5197)
Memory: 348.0K (peak: 1.4M)
CPU: 12ms
CGroup: /system.slice/docker-304dd929d678615f8d7e3d5ec1a8a9b7b96e3c0f2237165fad3cb907f9938ee3.scope/system.slice/cron.service
โโ80 /usr/sbin/cron -f -P
ubuntu@practice:~$ systemctl is-active cron
active
ubuntu@practice:~$ systemctl is-enabled cron
enabled
ubuntu@practice:~$ systemctl is-active nosuchservice
inactive
ubuntu@practice:~$ systemctl status nosuchservice --no-pager
Unit nosuchservice.service could not be found.
systemctl status cron is the command you will run most often. (On a real screen the first line starts with a coloured dot, green for healthy. The dot is the โ you see below.) The long CGroup name is because the lab runs inside a container; on a normal machine it is short, like /system.slice/cron.service. Reading it from the top:
- Loaded tells you which file defines the service, and that it is enabled, which means it starts at boot (more on that below).
- Active: active (running) since ... 28s ago is the key line: it is working, and has been for a while.
- Main PID: 80 (cron) is the process number of the service, the same PID you would see in ps (lesson 7).
- Tasks, Memory and CPU show what it is using, and CGroup shows the processes that belong to it.
Two shortcuts give a one-word answer, which is handy in scripts: systemctl is-active cron says active, and systemctl is-enabled cron says enabled. If you ask about a service that does not exist, you get a clear message (Unit nosuchservice.service could not be found.), and is-active says inactive.
Start, stop and restart
ubuntu@practice:~$ systemctl stop cron
Failed to stop cron.service: Access denied
See system logs and 'systemctl status cron.service' for details.
ubuntu@practice:~$ sudo systemctl stop cron
[sudo] password for ubuntu:
ubuntu@practice:~$ systemctl is-active cron
inactive
ubuntu@practice:~$ sudo systemctl start cron
ubuntu@practice:~$ systemctl is-active cron
active
ubuntu@practice:~$ sudo systemctl restart cron
ubuntu@practice:~$ systemctl status cron --no-pager
โ cron.service - Regular background program processing daemon
Loaded: loaded (/usr/lib/systemd/system/cron.service; enabled; preset: enabled)
Active: active (running) since Thu 2026-10-22 11:15:40 UTC; 4s ago
Docs: man:cron(8)
Main PID: 285 (cron)
Tasks: 1 (limit: 5197)
Memory: 344.0K (peak: 464.0K)
CPU: 8ms
CGroup: /system.slice/docker-304dd929d678615f8d7e3d5ec1a8a9b7b96e3c0f2237165fad3cb907f9938ee3.scope/system.slice/cron.service
โโ285 /usr/sbin/cron -f -P
- A normal user is not allowed to stop a system service. systemctl stop cron failed with "Access denied". (On a desktop you may instead see "Interactive authentication required". Either way, it means you need administrator rights; the desktop wording is from the documentation, not run here.)
- sudo systemctl stop cron worked. sudo asked for the password (nothing is shown as you type, lesson 6), and then is-active said inactive: the service is stopped.
- sudo systemctl start cron brought it back, and sudo systemctl restart cron stopped and started it in one go. Look at the final status: it is a new process, 285, and has been running for 4 seconds. A restart means a brand new copy of the program.
- There is also reload, which tells a service to re-read its settings without restarting and dropping its connections. Not every service supports it (from the documentation, not run here).
A real example: a web server
Services get interesting when they do something you can see. Let us install a web server. Lesson 6 showed you how, and the output is shortened here (on this machine the package list was already fresh, so there is no sudo apt update first; if yours is old, run it before the install, as in lesson 6. curl, a tool that fetches web pages from the command line, is asked for too, and it was already installed):
ubuntu@practice:~$ sudo apt install -y nginx curl Reading package lists... Done Building dependency tree... Done Reading state information... Done curl is already the newest version (8.5.0-2ubuntu10.16). Suggested packages: fcgiwrap nginx-doc ssl-cert The following NEW packages will be installed: nginx nginx-common 0 upgraded, 2 newly installed, 0 to remove and 3 not upgraded. Need to get 570 kB of archives. After this operation, 1602 kB of additional disk space will be used. Get:1 http://archive.ubuntu.com/ubuntu noble-updates/main amd64 nginx-common all 1.24.0-2ubuntu7.18 [45.1 kB] Get:2 http://archive.ubuntu.com/ubuntu noble-updates/main amd64 nginx amd64 1.24.0-2ubuntu7.18 [525 kB] Fetched 570 kB in 2s (288 kB/s) [... the middle of the output is left out to keep this page short ...] ubuntu@practice:~$ systemctl is-active nginx active ubuntu@practice:~$ curl -sI localhost HTTP/1.1 200 OK Server: nginx/1.24.0 (Ubuntu) Date: Thu, 22 Oct 2026 11:16:04 GMT Content-Type: text/html Content-Length: 615 Last-Modified: Thu, 22 Oct 2026 11:15:55 GMT Connection: keep-alive ETag: "6ad9f06b-267" Accept-Ranges: bytes
The important part is what you did not have to do: nobody started nginx. Installing a service package starts it, and enables it for next time. Look at the rest of the output above. It checked the service with systemctl is-active nginx, which said active, and then asked the web server for a page with curl. The -s option means "silent" (no progress chatter) and -I asks only for the headers, not the page. localhost is the name every computer gives itself, so this means "ask the web server on this machine".
The reply is HTTP/1.1 200 OK, which means "found it, here you go", and Server: nginx/1.24.0 (Ubuntu) shows who answered. systemctl status nginx would also list its main process (the master) and the workers that do the actual work, which is how nginx is built (not shown here).
Now stop it, and watch the website go dark and come back:
ubuntu@practice:~$ sudo systemctl stop nginx ubuntu@practice:~$ curl -I localhost curl: (7) Failed to connect to localhost port 80 after 0 ms: Couldn't connect to server ubuntu@practice:~$ sudo systemctl start nginx ubuntu@practice:~$ curl -sI localhost | head -n 1 HTTP/1.1 200 OK
- After the stop, curl -I localhost failed: "Failed to connect to localhost port 80 ... Couldn't connect to server". Nobody was listening on port 80, so the website was down. This is exactly what a visitor would see if a web server crashed.
- After sudo systemctl start nginx, the same request worked again.
Start now, or start at boot? enable and disable
Starting a service and enabling it are different things. Start means "run it now". Enable means "run it every time the machine boots". You can do either without the other:
ubuntu@practice:~$ systemctl is-enabled nginx enabled ubuntu@practice:~$ sudo systemctl disable nginx Synchronizing state of nginx.service with SysV service script with /usr/lib/systemd/systemd-sysv-install. Executing: /usr/lib/systemd/systemd-sysv-install disable nginx Removed "/etc/systemd/system/multi-user.target.wants/nginx.service". ubuntu@practice:~$ systemctl is-enabled nginx disabled ubuntu@practice:~$ systemctl is-active nginx active ubuntu@practice:~$ sudo systemctl enable nginx Synchronizing state of nginx.service with SysV service script with /usr/lib/systemd/systemd-sysv-install. Executing: /usr/lib/systemd/systemd-sysv-install enable nginx Created symlink /etc/systemd/system/multi-user.target.wants/nginx.service โ /usr/lib/systemd/system/nginx.service. ubuntu@practice:~$ systemctl is-enabled nginx enabled
- is-enabled nginx said enabled.
- sudo systemctl disable nginx removed it from the boot list (the message says it removed a link, which is how systemd keeps its boot list: one small link per enabled service).
- Now is-enabled says disabled, but is-active still says active. Disabling does not stop a running service. It only means it will not come back after the next reboot.
- sudo systemctl enable nginx created the link again. (You can combine the two jobs with sudo systemctl enable --now nginx, which enables and starts at once; from the documentation, not run here.)
Logs: where to look when something goes wrong
Every service writes down what it is doing: when it started, what it did, what went wrong. Those notes are logs, and reading them is the most important troubleshooting skill you can have. There are two places to look.
The journal, which systemd keeps for everything, and which you read with journalctl:
ubuntu@practice:~$ journalctl -u cron -n 5 --no-pager
Hint: You are currently not seeing messages from other users and the system.
Users in groups 'adm', 'systemd-journal' can see all messages.
Pass -q to turn off this notice.
-- No entries --
ubuntu@practice:~$ sudo journalctl -u cron -n 5 --no-pager
Oct 22 11:15:40 practice systemd[1]: Stopped cron.service - Regular background program processing daemon.
Oct 22 11:15:40 practice systemd[1]: Started cron.service - Regular background program processing daemon.
Oct 22 11:15:40 practice (cron)[285]: cron.service: Referenced but unset environment variable evaluates to an empty string: EXTRA_OPTS
Oct 22 11:15:40 practice cron[285]: (CRON) INFO (pidfile fd = 3)
Oct 22 11:15:40 practice cron[285]: (CRON) INFO (Skipping @reboot jobs -- not system startup)
ubuntu@practice:~$ sudo journalctl -u nginx --no-pager
Oct 22 11:15:56 practice systemd[1]: Starting nginx.service - A high performance web server and a reverse proxy server...
Oct 22 11:15:56 practice systemd[1]: Started nginx.service - A high performance web server and a reverse proxy server.
Oct 22 11:16:09 practice systemd[1]: Stopping nginx.service - A high performance web server and a reverse proxy server...
Oct 22 11:16:09 practice systemd[1]: nginx.service: Deactivated successfully.
Oct 22 11:16:09 practice systemd[1]: Stopped nginx.service - A high performance web server and a reverse proxy server.
Oct 22 11:16:16 practice systemd[1]: Starting nginx.service - A high performance web server and a reverse proxy server...
Oct 22 11:16:16 practice systemd[1]: Started nginx.service - A high performance web server and a reverse proxy server.
ubuntu@practice:~$ sudo tail -n 3 /var/log/nginx/access.log
::1 - - [22/Oct/2026:11:16:04 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/8.5.0"
::1 - - [22/Oct/2026:11:16:21 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/8.5.0"
ubuntu@practice:~$ ls /var/log
README dmesg nginx
alternatives.log dpkg.log private
apt faillog syslog
auth.log journal wtmp
bootstrap.log kern.log
btmp lastlog
ubuntu@practice:~$ sudo journalctl -p err -b --no-pager
-- No entries --
- The first attempt, journalctl -u cron -n 5 without sudo, printed a hint and No entries. Ordinary users cannot read the whole journal: you need sudo, or to be in the adm or systemd-journal group, as the hint says.
- With sudo, it works. -u cron means "only this unit" and -n 5 means "only the last 5 lines".
- sudo journalctl -u nginx tells nginx's whole story, and it matches what we did: Starting, Started, then Stopping and Stopped when we stopped it, then Starting and Started again. Every line has a time, the machine name, and the message.
- sudo journalctl -p err -b shows only the errors (-p err) since the machine booted (-b). Here it printed No entries: nothing has gone wrong since this machine booted. On your own machine you may see messages. Seeing a few is normal, and what matters is whether one of them explains your problem.
- journalctl -f follows the log live, adding new lines as they happen, until you press Ctrl+C (from the documentation, not run here).
**Plain text log files in /var/log**, which many programs write themselves:
- ls /var/log lists them: auth.log records logins, dpkg.log and apt record installs, syslog is the general system log, and there is a folder for nginx.
- sudo tail -n 3 /var/log/nginx/access.log shows the last 3 lines of nginx's access log: one line for every request. These two lines are our two curl requests: the address they came from (::1 means this very machine), the time, the request (HEAD / HTTP/1.1), the result 200, and the program that asked (curl/8.5.0). A log like this is what tells a website owner who visited and what broke. (The statistics dashboard on this very site reads a file just like it.)
Finally, if a service failed to start at boot, it shows up in the failed list, and an empty list is the good result:
ubuntu@practice:~$ systemctl --failed --no-pager UNIT LOAD ACTIVE SUB DESCRIPTION 0 loaded units listed.
A simple troubleshooting recipe
When a service is not working, follow the same four steps every time:
- Is it running? systemctl status NAME
- What does it say? sudo journalctl -u NAME -n 30 for the last 30 lines, and look for the word "error" or "failed".
- Fix what it says (most often a setting file, which lesson 10 teaches you to edit).
- Restart it and check again: sudo systemctl restart NAME, then systemctl status NAME.
It had too many unresolved connections.
๐งช Try it yourself
On your full Ubuntu machine:
1. Is the ssh service active and enabled? (Hint: it is named ssh.) 2. Stop nginx, show that curl -I localhost fails, start it again, and show that it works. 3. Disable nginx. Check is-enabled and is-active. What does each say? Then enable it again. 4. Show the last 10 lines of the SSH service's journal. 5. Which line in the nginx access log was written by your last curl? Show the last line of the log.
The cheat sheet
- systemctl status NAME how a service is doing. is-active and is-enabled give one-word answers.
- sudo systemctl start|stop|restart NAME run it now, stop it, or restart it. reload re-reads its settings.
- sudo systemctl enable|disable NAME start it at boot, or not. They do not start or stop it right now.
- systemctl list-units --type=service --state=running what is running. systemctl --failed what failed.
- Add --no-pager to print everything. In a pager, Space is the next page and q quits.
- sudo journalctl -u NAME -n 30 the last 30 log lines of a service. -p err errors only. -b since the last boot. -f follow live.
- /var/log holds the plain text logs. sudo tail -n 20 FILE shows the last lines of one.
← Lesson 7 · ↑ All lessons · Lesson 9: Disk, Memory and Network Checks →