Tested script

Summarise failed SSH logins by IP and user (Bash)

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"

Run it

sudo ./ssh-failed-logins.sh
./ssh-failed-logins.sh -n 5 /var/log/auth.log

How it works

Where the log comes from

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.