Viewing Logs with journalctl
When something breaks on a Linux server, the first question is always “what do the logs say?” On modern Ubuntu (22.04 and 24.04 LTS), most of your answers live in the systemd journal — a single, structured log store that journalctl reads. Instead of hunting through dozens of plain-text files, you ask one tool to show you exactly the logs you want: a specific service, the last 10 minutes, only errors, or only this boot. This page teaches you to read that journal like a pro so you can debug failed services fast.
First, two terms. systemd is the program that starts and manages services (background programs) on Ubuntu — it is the very first thing that runs after the kernel boots. The journal is the log database that systemd keeps, and journalctl (“journal control”) is the command you use to read it.
The systemd journal vs traditional /var/log files
For decades, Linux programs wrote logs as plain text into files under /var/log (a folder full of log files). A separate program called rsyslog sorted those messages into files like /var/log/syslog and /var/log/auth.log. That still happens on Ubuntu today, but systemd adds a second, smarter store: the binary journal, kept in /var/log/journal/ or /run/log/journal/.
The journal is “structured,” meaning every log line carries metadata fields — which service wrote it, the priority, the exact timestamp, the process ID — not just text. That is why journalctl can filter so precisely.
| Feature | journalctl (systemd journal) | /var/log text files |
|---|---|---|
| Format | Binary, structured fields | Plain text |
| Filter by service | Built in (-u) | Manual grep |
| Filter by time / priority | Built in (--since, -p) | Manual / awkward |
| Read with | journalctl only | cat, less, grep, tail |
| Survives reboot | Only if persistent storage enabled | Usually yes |
| Best for | Services, debugging, modern apps | App-specific logs (nginx access logs, etc.) |
By default Ubuntu stores the journal in
/run, which is RAM, so logs are wiped on every reboot. To keep them, runsudo mkdir -p /var/log/journal && sudo systemctl restart systemd-journald. After that the journal persists across reboots — essential for debugging a crash that happened before you logged in.
When to use journalctl: for anything managed by systemd (your app service, ssh, nginx via systemd, cron). When to use /var/log files instead: for application-specific logs that a program writes itself, like nginx’s /var/log/nginx/access.log — those do not go to the journal.
Reading logs for one service with -u
The single most useful flag is -u (short for “unit” — a systemd unit is a service, timer, or socket). It shows only the logs from one service.
journalctl -u nginx.service
You can drop the .service suffix; journalctl -u nginx works too. The output opens in a pager (a scrollable viewer) — press the down arrow to scroll and q to quit.
Output:
Jun 15 09:12:03 web01 systemd[1]: Starting nginx - high performance web server...
Jun 15 09:12:03 web01 systemd[1]: Started nginx - high performance web server.
Jun 15 14:31:55 web01 nginx[8123]: 2026/06/15 14:31:55 [error] connect() failed
Following logs live with -f
The -f flag means “follow.” It keeps the log open and prints new lines as they appear — exactly like tail -f on a text file. This is what you want while reproducing a bug or watching a deploy.
journalctl -u nginx -f
Press Ctrl + C to stop following. Combine it with -u to watch just one service, or run plain journalctl -f to watch everything on the system at once.
Filtering by time with —since and —until
--since and --until accept friendly phrases or exact timestamps, so you can zoom in on the moment something went wrong.
journalctl -u nginx --since "10 minutes ago"
journalctl -u ssh --since "2026-06-15 14:00" --until "2026-06-15 14:30"
journalctl --since today
journalctl --since yesterday --until "03:00"
When to use this: an alert fired at 2 PM — --since "1:55 PM" --until "2:05 PM" shows only the relevant window instead of pages of noise.
Filtering by priority with -p
Every log line has a priority (how serious it is), borrowed from the old syslog scale. -p shows only messages at that level or worse (more severe).
| Number | Name | Meaning |
|---|---|---|
| 0 | emerg | System is unusable |
| 1 | alert | Act immediately |
| 2 | crit | Critical condition |
| 3 | err | Error |
| 4 | warning | Warning |
| 5 | notice | Normal but notable |
| 6 | info | Informational |
| 7 | debug | Debug detail |
journalctl -p err
journalctl -u myapp -p warning --since today
The first command shows every error, critical, alert, and emergency message across the whole system. Lower number = more severe, and -p includes everything at that number and below.
Limiting to the current boot with -b
-b (“boot”) restricts logs to the current boot session — everything since the machine last started. This cuts out history from previous runs.
journalctl -b
journalctl -b -1
journalctl --list-boots
journalctl -b is the current boot, -b -1 is the previous boot, and --list-boots shows all stored boots (only useful if you enabled persistent storage). When to use this: a server rebooted unexpectedly and you want to see what it logged just before the crash — check -b -1.
Debugging a failed service step by step
Imagine your custom app service won’t start. Here is the exact workflow.
First, confirm the failure:
sudo systemctl status myapp.service
Output:
× myapp.service - My Web App
Loaded: loaded (/etc/systemd/system/myapp.service; enabled)
Active: failed (Result: exit-code) since Mon 2026-06-15 14:40:11 UTC
Main PID: 9211 (code=exited, status=203/EXEC)
status shows the last few lines, but to see the full picture, read the journal for that service since the last boot, errors only:
journalctl -u myapp -b -p err -e
The -e flag jumps to the end (the newest lines). Here you might find:
Output:
Jun 15 14:40:11 web01 systemd[1]: myapp.service: Failed to execute /usr/local/bin/myapp: No such file or directory
Jun 15 14:40:11 web01 systemd[1]: myapp.service: Main process exited, code=exited, status=203/EXEC
Status 203/EXEC plus “No such file or directory” tells you the binary path in the service file is wrong. Fix the ExecStart= line in the unit file, then reload and restart:
sudo systemctl daemon-reload
sudo systemctl restart myapp
journalctl -u myapp -f
Watch the live output to confirm it stays up.
Best Practices
- Always pair
-u <service>with-band-p errwhen debugging — it strips the noise down to “what broke this boot.” - Enable persistent storage (
/var/log/journal) on real servers so logs survive reboots; the default RAM storage loses them. - Use
--since/--untilwith real timestamps to match the exact moment an alert fired, instead of scrolling. - Use
journalctl -fwhile reproducing a bug rather than reading after the fact — you see cause and effect in real time. - Cap journal disk usage with
SystemMaxUse=in/etc/systemd/journald.conf(e.g.SystemMaxUse=500M) so logs never fill the disk. - Remember application logs (nginx access logs, PostgreSQL logs) usually stay in
/var/log— check there when the journal looks empty.