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

Tech reviewed: Deepak Prasad
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 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.

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.
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 over a decade 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, …

  • Red Hat Certified System Administrator in Red Hat OpenStack
  • Certified Kubernetes Application Developer (CKAD)
  • Red Hat Certified Specialist in Ansible Automation
  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security