Linux login history: view SSH logins, auth logs, and check attempts

Auditing who reached a host—and whether SSH authentication succeeded or failed—usually means combining two kinds of evidence: session records (who opened a login session and when) and auth records (what sshd and PAM wrote about each attempt). On Linux the first often comes from last and wtmp; the second from login logs such as /var/log/auth.log or /var/log/secure, or from journalctl command for the SSH unit.

Tested the commands below on Ubuntu 25.04, kernel 6.14.0-37-generic, OpenSSH log lines from journalctl and /var/log/auth.log (2026-06-13). Adjust paths if you run RHEL, AlmaLinux, or Fedora (/var/log/secure).


Linux view login history with last and who

last reads /var/log/wtmp and shows recent logins, terminals, remote hosts, and session start times. It answers “who logged in successfully recently?” without reading sshd line by line.

bash
last -n 20

Example (hostnames and times are from this environment):

text
golinuxcloud pts/0        2026-06-13 10:35   still logged in    10.0.2.2

lastb lists bad login attempts from /var/log/btmp (often root-only). who and w show who is on the system right now.

These tools are quick for a high-level view of successful sessions; they do not replace auth logs or the journal when you need every failed password attempt.


Linux login log files: auth.log, secure, and journalctl

Where login history is stored depends on the distro and logging stack:

  • Debian / Ubuntu: /var/log/auth.log (rotated files may be auth.log.1, auth.log.*.gz).
  • RHEL / AlmaLinux / Rocky / Fedora: /var/log/secure.
  • systemd: journalctl can show the same events even when you prefer not to open files directly.

To follow SSH logins live:

bash
sudo journalctl -u ssh -f

On some installs the unit is sshd instead of ssh command.

Recent lines look like this (timestamps and keys shortened):

text
Jun 13 10:35:55 server1 sshd-session[21026]: Accepted password for golinuxcloud from 10.0.2.2 port 61787 ssh2
Jun 13 10:35:55 server1 sshd-session[21026]: pam_unix(sshd:session): session opened for user golinuxcloud(uid=1000) by golinuxcloud(uid=0)

RFC3339-style lines in auth.log are common on newer Debian/Ubuntu:

text
2026-06-07T14:27:41.146672+05:30 server1 sshd-session[14020]: Accepted publickey for golinuxcloud from 10.0.2.2 port 54547 ssh2: RSA SHA256:…

For filtering concepts across services, see view logs with journalctl.


Linux check login history with grep

Typical auth log lines for SSH failures and successes look like:

text
sshd[3217]: Failed password for root from 192.168.0.102 port 53720 ssh2
sshd[4881]: Accepted password for root from 192.168.0.106 port 49920 ssh2

Examples below use auth.log; substitute secure on the RHEL family:

bash
sudo grep 'Failed password' /var/log/auth.log | tail -n 5
sudo grep -E 'Accepted (password|publickey|keyboard-interactive)' /var/log/auth.log | tail -n 5

Or via the journal:

bash
sudo journalctl -u ssh --since "today" | grep -E 'Failed password|Accepted '

Tighten scope to one user:

bash
sudo grep 'sshd' /var/log/auth.log | grep 'golinuxcloud' | tail -n 20

Script: summarize SSH attempts from a log file

Below is an updated version of the original idea: it defaults to /var/log/secure for RHEL-style layouts, but you can pass /var/log/auth.log as the first argument on Debian/Ubuntu. It expects plain log text (not binary wtmp), similar to what grep would read from those files.

Changes worth noting:

  • Uses grep -E instead of deprecated egrep.
  • Extracts the username by finding the word for (works for both classic syslog and RFC3339 timestamps, unlike a fixed $(NF-5) field index).
  • Calls host "$ip" without hard-coding a public resolver.

Drop duplicate adjacent lines with sort -u or pipe through uniq; see the sort command for stable ordering before deduplication.

bash
#!/usr/bin/env bash
# intruder_detect.sh — summarize SSH success/failure lines from auth log text
set -euo pipefail
AUTHLOG=/var/log/secure

if [[ -n ${1-} ]]; then
  AUTHLOG=$1
  echo "Using log file: $AUTHLOG"
fi

if [[ ! -r $AUTHLOG ]]; then
  echo "Cannot read $AUTHLOG (try sudo or pass a copied log)." >&2
  exit 1
fi

FAILED_LOG=$(mktemp)
SUCCESS_LOG=$(mktemp)
trap 'rm -f "$FAILED_LOG" "$SUCCESS_LOG"' EXIT

grep -hE 'Failed password' "$AUTHLOG" >"$FAILED_LOG" || true
grep -hE 'Accepted (password|publickey|keyboard-interactive)' "$AUTHLOG" >"$SUCCESS_LOG" || true

extract_user() {
  awk '
    /Failed password/ { for (i = 1; i < NF; i++) if ($i == "for") { print $(i + 1); next } }
    /Accepted (password|publickey|keyboard-interactive)/ { for (i = 1; i < NF; i++) if ($i == "for") { print $(i + 1); next } }
  '
}

failed_users=$(extract_user <"$FAILED_LOG" | sort -u)
success_users=$(extract_user <"$SUCCESS_LOG" | sort -u)
failed_ip_list=$(grep -hoE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' "$FAILED_LOG" | sort -u)
success_ip_list=$(grep -hoE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' "$SUCCESS_LOG" | sort -u)

printf '%-10s|%-12s|%-10s|%-15s|%-20s|%s\n' "Status" "User" "Attempts" "IP address" "Host" "Time range"

for ip in $failed_ip_list; do
  for user in $failed_users; do
    attempts=$(grep -F "$ip" "$FAILED_LOG" | grep -F " $user " | wc -l)
    attempts=${attempts//[[:space:]]/}
    if [[ "$attempts" -ne 0 ]]; then
      first_time=$(grep -F "$ip" "$FAILED_LOG" | grep -F " $user " | head -1 | cut -c1-24)
      time=$first_time
      if [[ "$attempts" -gt 1 ]]; then
        last_time=$(grep -F "$ip" "$FAILED_LOG" | grep -F " $user " | tail -1 | cut -c1-24)
        time="$first_time -> $last_time"
      fi
      HOST=$(host "$ip" 2>/dev/null | tail -1 | awk '{ print $NF }')
      printf '%-10s|%-12s|%-10s|%-15s|%-20s|%s\n' "Failed" "$user" "$attempts" "$ip" "${HOST:-?}" "$time"
    fi
  done
done

for ip in $success_ip_list; do
  for user in $success_users; do
    attempts=$(grep -F "$ip" "$SUCCESS_LOG" | grep -F " $user " | wc -l)
    attempts=${attempts//[[:space:]]/}
    if [[ "$attempts" -ne 0 ]]; then
      first_time=$(grep -F "$ip" "$SUCCESS_LOG" | grep -F " $user " | head -1 | cut -c1-24)
      time=$first_time
      if [[ "$attempts" -gt 1 ]]; then
        last_time=$(grep -F "$ip" "$SUCCESS_LOG" | grep -F " $user " | tail -1 | cut -c1-24)
        time="$first_time -> $last_time"
      fi
      HOST=$(host "$ip" 2>/dev/null | tail -1 | awk '{ print $NF }')
      printf '%-10s|%-12s|%-10s|%-15s|%-20s|%s\n' "Success" "$user" "$attempts" "$ip" "${HOST:-?}" "$time"
    fi
  done
done

Make it executable (chmod u+x intruder_detect.sh), then run ./intruder_detect.sh on RHEL-style hosts or ./intruder_detect.sh /var/log/auth.log on Debian/Ubuntu (often needs sudo for read permission). The table layout matches the original article’s intent: user, attempt counts, IPv4, forward/reverse DNS hint, and a coarse time range.

The script only matches IPv4 addresses in the summary table. IPv6 logins happen; extending the script would mean replacing the IPv4-only grep -hoE pattern with something that also captures IPv6 literals.

For ad-hoc awk on log columns, see awk examples.


Summary

Linux login history spans both session records (last, who, wtmp) and authentication traces (auth.log or /var/log/secure, plus journalctl for SSH). For a quick view of recent successful logins, start with last. To inspect denied passwords and accepted keys or passwords, grep the sshd lines or use journalctl -u ssh (or -u sshd). The script section is optional: it aggregates repeated IPv4 + user pairs from a text auth log; use grep and journalctl first if you only need a fast snapshot.

Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)