Quickly Pinpointing Unauthorised sudo Attempts with Journalctl Filters

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 sudo attempts 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

FeatureJournal/var/log/auth.log
Structured fieldsYesPlain text
Indexing & searchFast, binaryLinear scan
Retention policyConfigurable via systemd‑journald.confRotated by logrotate
Remote collectionBuilt‑in systemd-journal-remoteRequires 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:

  1. -o cat strips the journal header.
  2. grep -oP 'ruser=\K[^ ]+' extracts the username.
  3. sort | uniq -c counts occurrences.
  4. sort -nr orders 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

IssueImpactMitigation
Journal sizeLarge systems can fill the journal quickly.Configure SystemMaxUse= in /etc/systemd/journald.conf or rotate with systemd-journal-remote.
PrivacyThe journal stores raw password attempts (no password).Ensure sudo is compiled with --syslog.
False positivesLegitimate users may hit the limit during password resets.Tune threshold per user or per IP.
PerformanceComplex filters on a busy system can be CPU‑heavy.Use --output=short-iso and limit the time window.
RetentionDefault retention is 7 days; older events may be purged.Set SystemMaxRetentionSec= or use remote journaling.

8. Common Mistakes to Avoid

  1. Using journalctl -u sudo alone – this shows all sudo events, including successful runs. Always add --priority=err or a message filter.
  2. Relying on grep without -F – regexes can misinterpret special characters in usernames. Prefer -F or -oP with explicit patterns.
  3. Ignoring SYSLOG_IDENTIFIER – other services may also write sudo‑like messages. Filter on SYSLOG_IDENTIFIER=sudo to be precise.
  4. Assuming sudo logs 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 on SYSLOG_IDENTIFIER=su.
  • ssh – SYSLOG_IDENTIFIER=sshd and MESSAGE contains Failed 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