When a user or a process tries to run something with sudo, the action is recorded in the system journal. Because sudo writes to the journal, you can filter on the SYSLOG_IDENTIFIER or MESSAGE fields to spot failed attempts, audit who tried what, and even trigger alerts.
Below is a step‑by‑step guide that shows how to:
- Identify failed
sudoattempts in real time - Pull historical data for a specific user or time window
- Distinguish between legitimate and suspicious activity
- Automate the process with a small script
- Understand trade‑offs and common pitfalls
The examples assume a recent systemd‑based distro (Debian 12, Ubuntu 24.04, Fedora 40, etc.) and that sudo is compiled with the default --syslog flag (enabled since 2019). If your system uses a custom sudo build or a different logging backend, the commands will need adjustment.
Why the Journal is Better than /var/log/auth.log
| Feature | Journal | /var/log/auth.log |
|---|---|---|
| Structured fields | Yes | Plain text |
| Indexing & search | Fast, binary | Linear scan |
| Retention policy | Configurable via systemd‑journald.conf | Rotated by logrotate |
| Remote collection | Built‑in systemd-journal-remote | Requires custom setup |
Because the journal keeps each entry as a key/value map, you can filter on any field without regex gymnastics. That makes it ideal for security‑focused queries like “who failed sudo twice in the last 10 minutes?”
1. Inspecting a Single Failed Attempt
A typical failed sudo entry looks like this:
$ journalctl -u sudo -n 1
-- Logs begin at Thu 2026-11-09 10:12:34 UTC, end at Thu 2026-11-09 10:13:12 UTC. --
Nov 09 10:13:12 server sudo[12345]: pam_unix(sudo:auth): authentication failure; logname= uid=0 euid=0 tty=pts/2 ruser=alice rhost=192.168.1.42
Nov 09 10:13:12 server sudo[12345]: pam_unix(sudo:auth): authentication failure; logname= uid=0 euid=0 tty=pts/2 ruser=alice rhost=192.168.1.42
...
Each failure is a separate entry. To pull only the failures, use:
journalctl -u sudo --priority=err
--priority=err filters on the PRIORITY field, which sudo sets to err for authentication failures. If you want to see the full message, add -o cat:
journalctl -u sudo --priority=err -o cat
2. Filtering by User, Host, or Time
2.1 By Username
sudo logs the requesting user in the MESSAGE field as ruser=…. Filter with SYSLOG_IDENTIFIER=sudo and a MESSAGE regex:
journalctl -u sudo \
-o short-iso \
-g 'ruser=alice' \
--priority=err
-g performs a case‑insensitive grep on the message. The short-iso output shows a concise timestamp, PID, and message.
2.2 By Remote IP
If you suspect a brute‑force attack from a specific IP, filter on rhost=…:
journalctl -u sudo \
-o short-iso \
-g 'rhost=192.168.1.42' \
--priority=err
2.3 By Time Window
The journal supports relative and absolute timestamps. For the last 15 minutes:
journalctl -u sudo --since "15 minutes ago" --priority=err
Or for a specific date:
journalctl -u sudo --since "2026-11-08 00:00:00" --until "2026-11-08 23:59:59" --priority=err
3. Real‑Time Monitoring
If you want to watch the journal as failures happen, use -f (follow):
journalctl -u sudo --priority=err -f
Add -o cat to suppress the timestamp header:
journalctl -u sudo --priority=err -o cat -f
This is handy when you’re troubleshooting a live login attempt or when you want to confirm that a policy change (e.g., tightening sudoers) is effective.
4. Aggregating Attempts per User
A single user may try many passwords in a short period. To spot potential brute‑force attacks, aggregate counts:
journalctl -u sudo --priority=err \
-o cat | grep -oP 'ruser=\K[^ ]+' | sort | uniq -c | sort -nr
Explanation:
-o catstrips the journal header.grep -oP 'ruser=\K[^ ]+'extracts the username.sort | uniq -ccounts occurrences.sort -nrorders by frequency.
The output might look like:
12 alice
4 bob
1 charlie
If a user appears dozens of times, that’s a red flag.
5. Exporting to a CSV for SIEM Integration
Many security teams ingest logs into SIEMs. You can export a structured CSV:
journalctl -u sudo --priority=err -o json-pretty | \
jq -r '. | [.__REALTIME_TIMESTAMP, .MESSAGE] | @csv' > sudo_failures.csv
jq is a lightweight JSON processor; the __REALTIME_TIMESTAMP field is the raw epoch timestamp. The CSV can be imported into Splunk, ELK, or any other log aggregator.
6. Automating Alerts
6.1 A Simple Bash Wrapper
Create /usr/local/bin/alert_sudo_fail.sh:
#!/usr/bin/env bash
# alert_sudo_fail.sh
# Send an email when a user fails sudo 3 times in 5 minutes
threshold=3
window="5 minutes"
# Count failures in the window
count=$(journalctl -u sudo --since "$window" --priority=err \
-o cat | grep -c 'ruser=')
if (( count >= threshold )); then
mail -s "Sudo failure alert" admin@example.com <<EOF
There have been $count sudo failures in the last $window.
$(journalctl -u sudo --since "$window" --priority=err -o short-iso)
EOF
fi
Make it executable and add a cron job:
chmod +x /usr/local/bin/alert_sudo_fail.sh
crontab -e
# Every 5 minutes
*/5 * * * * /usr/local/bin/alert_sudo_fail.sh
6.2 Using systemd‑path Units
If you prefer a systemd‑native solution, create /etc/systemd/system/sudo-fail.path:
[Path]
PathChanged=/var/log/journal
Unit=sudo-fail.service
And /etc/systemd/system/sudo-fail.service:
[Service]
Type=oneshot
ExecStart=/usr/local/bin/alert_sudo_fail.sh
Reload systemd and enable the path unit:
systemctl daemon-reload
systemctl enable --now sudo-fail.path
The path unit watches the journal directory and triggers the service whenever a new entry is added.
7. Trade‑offs and Caveats
| Issue | Impact | Mitigation |
|---|---|---|
| Journal size | Large systems can fill the journal quickly. | Configure SystemMaxUse= in /etc/systemd/journald.conf or rotate with systemd-journal-remote. |
| Privacy | The journal stores raw password attempts (no password). | Ensure sudo is compiled with --syslog. |
| False positives | Legitimate users may hit the limit during password resets. | Tune threshold per user or per IP. |
| Performance | Complex filters on a busy system can be CPU‑heavy. | Use --output=short-iso and limit the time window. |
| Retention | Default retention is 7 days; older events may be purged. | Set SystemMaxRetentionSec= or use remote journaling. |
8. Common Mistakes to Avoid
- Using
journalctl -u sudoalone – this shows allsudoevents, including successful runs. Always add--priority=error a message filter. - Relying on
grepwithout-F– regexes can misinterpret special characters in usernames. Prefer-For-oPwith explicit patterns. - Ignoring
SYSLOG_IDENTIFIER– other services may also writesudo‑like messages. Filter onSYSLOG_IDENTIFIER=sudoto be precise. - Assuming
sudologs to/var/log/auth.log– on systemd systems, the journal is the canonical source. If you still see auth.log entries, your distro may be configured to duplicate logs.
9. Extending Beyond sudo
The same techniques apply to other PAM‑based services:
su– filter onSYSLOG_IDENTIFIER=su.ssh–SYSLOG_IDENTIFIER=sshdandMESSAGEcontainsFailed password.login–SYSLOG_IDENTIFIER=login.
You can create a generic audit script that accepts a service name and threshold, then applies the same logic.
10. Staying Ahead of the Curve
In 2026, many distros ship sudo with the --syslog flag enabled by default, and systemd’s journal has become the de‑facto log aggregator. By mastering journalctl filters, you gain:
- Immediate visibility into authentication failures.
- Fine‑grained control over who, when, and from where attempts occur.
- Automation hooks that integrate with existing monitoring stacks.
If you’re still relying on legacy logrotate pipelines, consider migrating to journal‑based monitoring. It reduces noise, speeds up queries, and aligns with the rest of the systemd ecosystem.
See also
- Why my SSH jump host was silently dropping connections after an OpenSSH 9.5 update and how I fixed it
- When systemd‑resolved silently drops a local /etc/hosts entry and the service you can’t reach
- When resolvectl Caches Fail: Quick Steps to Clear Stale DNS Entries Without Restarting NetworkManager
- When ss Shows No Open TCP Ports Even Though Services Are Running
- When nftables silently drops HTTP traffic: a step‑by‑step port‑filter investigation