Counts failed SSH password attempts and shows the top source IPs and usernames tried. Use it to see whether a server is being brute-forced and who from.
Tested
The script
#!/usr/bin/env bash
# ssh-failed-logins.sh: summarise failed SSH password logins by IP and user.
#
# Usage: ssh-failed-logins.sh [-n COUNT] [LOGFILE]
# -n COUNT rows to show in each table (default 10)
# LOGFILE an auth log such as /var/log/auth.log or /var/log/secure.
# Without it, reads the systemd journal for the ssh/sshd units.
#
# You may need sudo (or membership of the adm group) to read the logs.
# Read-only.
set -euo pipefail
usage() { sed -n '4,7p' "$0" | sed 's/^# \{0,1\}//'; }
count=10
while getopts ':n:h' opt; do
case "$opt" in
n) count="$OPTARG" ;;
h) usage; exit 0 ;;
*) usage >&2; exit 2 ;;
esac
done
shift $((OPTIND - 1))
[[ "$count" =~ ^[1-9][0-9]*$ ]] || { echo "Error: -n must be a positive number" >&2; exit 2; }
if [[ $# -gt 0 ]]; then
[[ -r "$1" ]] || { echo "Error: cannot read $1" >&2; exit 1; }
log=$(cat -- "$1")
else
log=$(journalctl -u ssh -u sshd --no-pager -o short 2>/dev/null) || {
echo "Error: could not read the journal. Try sudo, or pass a log file." >&2; exit 1; }
fi
# Matches "Failed password for root from 1.2.3.4" and "... for invalid user bob from ..."
failures=$(printf '%s\n' "$log" | grep -E 'Failed password for' |
sed -E 's/.*Failed password for (invalid user )?([^ ]+) from ([^ ]+).*/\3 \2/' || true)
if [[ -z "$failures" ]]; then
echo "No failed SSH password logins found."
[[ $# -gt 0 ]] || echo "Note: without sudo or the systemd-journal group, the journal may hide sshd entries." >&2
exit 0
fi
echo "Total failed logins: $(printf '%s\n' "$failures" | wc -l)"
echo
echo "Top source IPs:"
printf '%s\n' "$failures" | cut -d' ' -f1 | sort | uniq -c | sort -rn | head -n "$count"
echo
echo "Top usernames tried:"
printf '%s\n' "$failures" | cut -d' ' -f2 | sort | uniq -c | sort -rn | head -n "$count"
If you pass a file, the script reads it. Otherwise it runs journalctl -u ssh -u sshd, which covers both Debian/Ubuntu (ssh) and Red Hat/Arch (sshd) unit names. If the journal cannot be read at all you get a hint to use sudo or pass a file.
Pull out the failures
grep -E 'Failed password for' keeps only failed password attempts. sed -E then reduces each line to two words: the IP and the username. The (invalid user )? part handles both real accounts and usernames that do not exist.
Count them
Each table is the classic pipeline: cut -d' ' picks the column, sort groups identical values, uniq -c counts them, sort -rn puts the biggest count first and head -n trims the list.
What it does not count
It only counts password failures. Rejected keys and connections dropped before a password was tried are not included. If the journal gives no results and you are not using sudo, the script says so, because the journal hides system entries from ordinary users.
Cookies
No adverts, and nothing that follows you to another site. What we set is what signing in
and the forum need. We count page views with Umami, which sets no cookie and identifies nobody. The one exception is Google reCAPTCHA, which checks our forms are being used by
a person, and that one is yours to decline — nothing is loaded from Google until you
say so.
No adverts, and nothing that follows you to another site. We count page views without a cookie and identify nobody. Google reCAPTCHA checks our forms, and that one is yours to decline.
Cookie Policy ·
Privacy Policy