Log Analysis with journalctl and rsyslog
At a glance#
- Purpose: Find, filter and interpret system and application logs during troubleshooting and incident response.
- Applies to: Any systemd-based Linux distribution.
- Risk: None for reading; Low when changing retention settings.
- Time: 15–45 minutes.
Overview#
Modern Linux keeps logs in two places, and knowing which to search saves a lot of time:
| systemd journal | rsyslog | |
|---|---|---|
| Location | /var/log/journal/ (binary) | /var/log/*.log (plain text) |
| Read with | journalctl | grep, less, tail |
| Holds | Everything systemd captures — services, kernel, boot | What rsyslog is configured to write |
| Survives reboot | Only if persistent storage is enabled | Yes |
Most distributions run both. The journal is authoritative for services; the text files are easier to search with standard tools and are what gets shipped to a central collector.
journalctl#
Basics#
# everything, newest last
sudo journalctl
# follow live
sudo journalctl -f
# last 100 lines
sudo journalctl -n 100
# this boot only
sudo journalctl -b
# the previous boot - essential after an unexplained reboot
sudo journalctl -b -1
# list known boots
sudo journalctl --list-bootsjournalctl -b -1 is the first command to run after a server rebooted unexpectedly. It shows what the machine was doing immediately before it went down.
Filtering#
# by service
sudo journalctl -u nginx
sudo journalctl -u nginx -u php-fpm # several at once
# by priority
sudo journalctl -p err # errors and worse
sudo journalctl -p warning..err # a range
# by time
sudo journalctl --since "1 hour ago"
sudo journalctl --since "2026-08-11 09:00" --until "2026-08-11 10:30"
sudo journalctl --since today
sudo journalctl --since yesterday --until today
# kernel messages only
sudo journalctl -k
# by process, user or executable
sudo journalctl _PID=1234
sudo journalctl _UID=1000
sudo journalctl /usr/sbin/sshdPriority levels, lowest number is most severe:
| Level | Name | Meaning |
|---|---|---|
| 0 | emerg | System unusable |
| 1 | alert | Immediate action required |
| 2 | crit | Critical failure |
| 3 | err | Error |
| 4 | warning | Warning |
| 5 | notice | Normal but significant |
| 6 | info | Informational |
| 7 | debug | Debug detail |
Combining filters#
The real value is in narrowing quickly:
# errors from one service in the last hour
sudo journalctl -u nginx -p err --since "1 hour ago"
# everything around an incident window, no pager, for copying into a ticket
sudo journalctl --since "2026-08-11 14:00" --until "2026-08-11 14:30" --no-pager
# output as JSON for scripting
sudo journalctl -u nginx -o json-pretty -n 5Useful output formats#
sudo journalctl -o short-precise # microsecond timestamps
sudo journalctl -o verbose # every field, useful for finding filter keys
sudo journalctl -o cat # message text only, no metadataJournal persistence and size#
By default some distributions keep the journal in memory only, so it is lost on reboot — which is exactly when you need it.
Check:
sudo journalctl --disk-usage
ls -d /var/log/journal 2>/dev/null || echo "journal is volatile - not persisted"Make it persistent:
sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journaldCap the size so it cannot fill the disk:
sudo nano /etc/systemd/journald.conf[Journal]
Storage=persistent
SystemMaxUse=1G
SystemKeepFree=2G
MaxRetentionSec=30daysudo systemctl restart systemd-journaldManual cleanup:
sudo journalctl --vacuum-size=500M
sudo journalctl --vacuum-time=14dWarning: An uncapped journal on a busy server will consume the root filesystem. Set SystemMaxUse as part of every server build.
Traditional log files#
Even with the journal, these remain important:
| File | Contents |
|---|---|
/var/log/messages (RHEL) / /var/log/syslog (Debian) | General system messages |
/var/log/secure (RHEL) / /var/log/auth.log (Debian) | Authentication, sudo, SSH |
/var/log/cron | Cron execution |
/var/log/maillog | Mail transport |
/var/log/nginx/, /var/log/httpd/ | Web server access and errors |
/var/log/audit/audit.log | auditd records |
Searching effectively#
# failed SSH logins
sudo grep "Failed password" /var/log/secure | tail -20
# count failures by source IP - a brute force shows up immediately
sudo grep "Failed password" /var/log/secure | \
grep -oE "from [0-9.]+" | awk '{print $2}' | sort | uniq -c | sort -rn | head
# successful logins
sudo grep "Accepted" /var/log/secure | tail -20
# sudo usage
sudo grep "sudo:" /var/log/secure | tail -20
# a time window
sudo awk '/Aug 11 14:00/,/Aug 11 14:30/' /var/log/messages
# follow several files at once
sudo tail -f /var/log/messages /var/log/secureCompressed rotated logs#
sudo zgrep "error" /var/log/messages-*.gz
sudo zcat /var/log/messages-20260801.gz | grep -i "oom"Searching only the current file is a common mistake — if the incident was three days ago, it is in a rotated archive.
Log rotation#
# global configuration
cat /etc/logrotate.conf
# per-service rules
ls /etc/logrotate.d/
# test a rule without applying it
sudo logrotate -d /etc/logrotate.d/nginx
# force a rotation
sudo logrotate -f /etc/logrotate.d/nginxA custom rule for an application log:
sudo nano /etc/logrotate.d/myapp/var/log/myapp/*.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
create 0640 myapp myapp
sharedscripts
postrotate
systemctl reload myapp > /dev/null 2>&1 || true
endscript
}Note: Withoutpostrotatereloading the service, the application keeps writing to the deleted file handle. Disk space is never released and the new log stays empty.copytruncateis an alternative where the app cannot reopen its log.
Central log forwarding#
Logs on a compromised host cannot be trusted. Forward them off the machine:
sudo nano /etc/rsyslog.d/50-forward.conf# forward everything over TCP to the log collector
*.* @@logserver.example.local:514sudo systemctl restart rsyslog@@ is TCP, a single @ is UDP. Use TCP so messages are not silently dropped.
Incident investigation sequence#
When something broke and you do not know why:
# 1. anything failed?
systemctl --failed
# 2. errors across the system in the relevant window
sudo journalctl -p err --since "2 hours ago" --no-pager
# 3. did the machine reboot?
last reboot | head -5
uptime
# 4. what happened just before the last boot
sudo journalctl -b -1 -p err --no-pager | tail -50
# 5. kernel-level events - OOM, disk, hardware
sudo dmesg -T | grep -iE "error|fail|oom|i/o"
# 6. authentication activity
sudo grep -E "Failed|Accepted|sudo" /var/log/secure | tail -30
# 7. resource exhaustion at the time
sar -u -s 14:00:00 -e 15:00:00Verification#
# journal is persistent and capped
sudo journalctl --disk-usage
grep -E "Storage|SystemMaxUse" /etc/systemd/journald.conf
# rotation is working - expect dated, compressed files
ls -lh /var/log/nginx/
# forwarding reaching the collector
sudo logger "test message from $(hostname)"
# then confirm it arrived on the log serverTroubleshooting#
| Symptom | Cause and fix |
|---|---|
journalctl shows nothing after reboot | Journal is volatile. Create /var/log/journal and restart journald. |
No journal files were found | Not running journald, or permissions. Add your user to systemd-journal. |
| Disk filling with logs | No SystemMaxUse set, or rotation broken. Cap it and check logrotate. |
| Log file exists but stays empty | Application writing to a deleted handle after rotation. Add a postrotate reload. |
df and du disagree in /var/log | Deleted log held open. Find with lsof +L1. |
| Timestamps in the wrong timezone | Journal stores UTC. Use journalctl --utc, or fix with timedatectl. |
| Cannot find an event from last week | It is in a rotated archive. Use zgrep. |
| Rotation not running | logrotate.timer disabled. Check systemctl status logrotate.timer. |