Introduction: why reading logs matters
Every program you run on Linux writes messages to a log file or the system journal. Those messages tell you what the software is doing, when it fails, and how to fix it. If you can read and filter those logs quickly, you’ll spend far less time guessing and more time solving problems.
Who this is for
- Beginners who have never opened a log file before.
- Intermediate developers who want a reliable, repeatable workflow for debugging services.
By the end of this tutorial you’ll have six go‑to commands that let you:
- Follow a log as it grows (
tail -f). - Scroll back and search inside a log (
less). - Pull out only the lines you care about (
grep). - Work with the systemd journal (
journalctl).
Part 1 – The basics of text‑based logs
| Command | What it does | Typical use case |
|---|---|---|
tail |
Shows the last n lines of a file. | Quick glance at recent activity. |
less |
Pager that lets you scroll forward and backward. | Browse a large log without loading it all into memory. |
grep |
Filters lines that match a pattern. | Find errors, warnings, or specific IDs. |
journalctl |
Reads the binary systemd journal. | View logs from services that use systemd (most modern daemons). |
All four tools work on plain‑text files, but journalctl reads a binary store, so its syntax differs a little.
1.1 tail – the fastest way to see the newest entries
# Show the last 10 lines (default)
tail /var/log/syslog
# Show the last 20 lines
tail -n 20 /var/log/syslog
Follow mode (-f) keeps the file open and prints new lines as they appear—perfect for watching a live process.
tail -f /var/log/syslog
Press Ctrl‑C to stop.
1.2 less – scroll, search, and filter on the fly
less /var/log/syslog
Inside less you can:
Space– page downb– page up/pattern– search forward for pattern?pattern– search backward&pattern– filter view: only show lines that match pattern (press&again to clear).
When you’re done, type q.
1.3 grep – cut out the noise
# Show only lines that contain the word "error"
grep -i error /var/log/syslog
-i– case‑insensitive-n– prefix each line with its line number-C 2– show 2 lines of context before and after each match
grep -i -n -C 2 "failed login" /var/log/auth.log
1.4 journalctl – the modern log daemon
Systemd stores logs in a binary format under /run/log/journal/. Use journalctl to query them.
# Show the most recent 20 journal entries
journalctl -n 20
# Follow the journal like `tail -f`
journalctl -f
# Show only entries from a specific service
journalctl -u nginx.service
# Filter by priority (0=emerg, 3=err, 6=info)
journalctl -p err
journalctl also understands time ranges:
journalctl --since "2024-10-01 09:00" --until "2024-10-01 10:00"
Part 2 – Combining the tools
The real power comes when you pipe output from one command into another.
| Combination | What you get |
|---|---|
tail -f file | grep pattern |
Live view of only matching lines. |
journalctl -f | grep -i timeout |
Follow the system journal and watch for timeouts. |
less file → &pattern |
Interactive filter without leaving the pager. |
grep -i error file | less |
Paginated error list, searchable with /. |
2.1 Live filtering with tail and grep
tail -f /var/log/nginx/error.log | grep -i "client sent malformed request"
This command will only print new lines that contain the phrase, letting you focus on the problem as it happens.
2.2 Using journalctl with grep for service‑specific debugging
journalctl -u postgresql.service -f | grep -i "deadlock"
You now see every new PostgreSQL log entry that mentions “deadlock”.
2.3 Interactive filtering inside less
less /var/log/apache2/access.log
Press & then type 200 to view only successful HTTP responses. Press & again (empty input) to clear the filter.
Part 3 – Practical tips for clean log handling
| Tip | Command | Why it helps |
|---|---|---|
| Limit output size | journalctl -n 100 |
Prevents scrolling through thousands of lines. |
| Save filtered output | grep -i error file > errors.txt |
Keeps a permanent copy for later analysis. |
| Colorize matches | grep --color=auto pattern file |
Highlights the matched text, easier to scan. |
Show timestamps in tail |
tail -f file | ts (install moreutils) |
Adds a readable timestamp to each line. |
| Rotate logs safely | logrotate (system service) |
Prevents logs from growing indefinitely. |
Hands‑on example: track a failing web request
Assume you have an Nginx server and you want to watch for 500‑status responses in real time.
# 1. Follow the Nginx access log
tail -f /var/log/nginx/access.log \
| # 2. Keep only lines with status code 500
grep -E " 500 " \
| # 3. Show the line number and highlight the match
grep --color=auto -n "500"
Explanation
tail -fstreams new log lines as they arrive.grep -E " 500 "uses a regular expression to match the exact status code (the spaces avoid matching5000).- The second
grepadds line numbers (-n) and colors the500text for quick visual scanning.
You can pipe the result into less if you prefer a scrollable view:
tail -f /var/log/nginx/access.log | grep -E " 500 " | less
To keep a record for later review:
tail -f /var/log/nginx/access.log | grep -E " 500 " > /tmp/500-errors.log
Now you have a file that contains only the problematic requests.
Common mistakes & troubleshooting
-
Mistake: Using
tail -fon a rotated log file (the file disappears after rotation).
Fix: Usetail -F(capital F). It follows the file name across rotations. -
Mistake: Forgetting to escape special characters in
greppatterns.
Fix: Quote the pattern or use-Ffor fixed‑string matching:grep -F "error: 404". - Mistake: Running
journalctlwithout sufficient permissions, getting “Permission denied”.
Fix: Prefix withsudoor add your user to thesystemd-journalgroup:sudo usermod -aG systemd-journal $USER -
Mistake: Overwhelming the terminal with too much output, causing lag.
Fix: Limit the number of lines (-n 100) or add a time filter (--since "5 min ago"). - Mistake: Using
grepon a binary journal file (/var/log/journal/...).
Fix: Always query the journal withjournalctl; do not feed the binary file togrep.
Try it yourself
- Open a terminal.
- Run
journalctl -fin one pane (or a separate tab). - In another pane, intentionally trigger a failed
sshlogin (e.g.,ssh invalid@localhost). - Use
grepto filter the journal for the string “Failed password” while the login attempt is happening:
journalctl -f | grep -i "failed password"
You should see the new log line appear instantly.
What’s next
- Log rotation deep dive – how
logrotateworks and custom configuration. - Structured logging – parsing JSON logs with
jq. - Alerting basics – piping log output to
mailornotify-send. - Performance profiling – using
straceandlsofalongside log analysis.
Bookmark this page and keep the six commands handy; they’ll become your daily debugging companions.