When SSH public key authentication fails, the server typically logs entries like:
May 10 14:23:42 server1 sshd[25917]: Connection closed by 192.168.1.100 [preauth]
This differs from password authentication failures which generate more explicit messages. The challenge is crafting a failregex that properly matches these "silent" authentication failures.
Most default sshd jail configurations in fail2ban versions prior to 0.9.1 don't include patterns for public key failures. The outdated regex might look like:
failregex = ^%(__prefix_line)s(?:error: PAM: )?[aA]uthentication (?:failure|error) for .* from <HOST>(?: via \w+)?\s*$
^%(__prefix_line)s(?:error: )?Failed (?:password|publickey) for .* from <HOST>(?: port \d+)?(?: ssh\d*)?\s*$
Edit /etc/fail2ban/filter.d/sshd.conf
and add this pattern:
failregex = ^%(__prefix_line)sConnection closed by <HOST> $$preauth$$\s*$
^%(__prefix_line)s(?:error: PAM: )?[aA]uthentication (?:failure|error) for .* from <HOST>(?: via \w+)?\s*$
^%(__prefix_line)s(?:error: )?Failed (?:password|publickey) for .* from <HOST>(?: port \d+)?(?: ssh\d*)?\s*$
Use fail2ban-regex to verify your pattern works:
fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf
You should see matches for lines containing "Connection closed by [preauth]". Example successful output:
Running tests
=============
Use failregex file : /etc/fail2ban/filter.d/sshd.conf
Use log file : /var/log/auth.log
Results
=======
Failregex: 3 total
|- #) [# of hits] regular expression
| 1) [2] ^%(__prefix_line)sConnection closed by <HOST> $$preauth$$\s*$
| 2) [1] ^%(__prefix_line)s(?:error: PAM: )?[aA]uthentication (?:failure|error) for .* from <HOST>(?: via \w+)?\s*$
-
Ignoreregex: 0 total
Date template hits:
|- [# of hits] date format
| [3] {^LN-BEG}ExYear(?P<_sep>[-/.])Month(?P=_sep)Day(?:T| ?)24hour:Minute:Second(?:[.,]Microseconds)?(?:\s*%z)?
-
Lines: 3 lines, 0 ignored, 3 matched, 0 missed
1. The %(__prefix_line)s
variable handles system-specific log prefixes automatically
2. For Debian-based systems, you might need to adjust date patterns in sshd.conf
3. Consider rate-limiting to prevent false positives from legitimate users
4. Always restart fail2ban after configuration changes:
systemctl restart fail2ban
When monitoring SSH logs, I noticed fail2ban wasn't catching failed public key authentication attempts. These appear in logs as:
Jun 15 10:23:45 myserver sshd[25917]: Connection closed by 192.168.1.100 [preauth]
The default Debian repository version of fail2ban (prior to 0.9.1) doesn't include patterns to match these messages, leaving a security gap in brute-force protection.
The sshd filter in /etc/fail2ban/filter.d/sshd.conf
uses several predefined variables:
%(__prefix_line)s
: Matches the syslog prefix (timestamp and hostname)\S+
: Non-whitespace pattern for process names/numbers- Various IP address patterns
To properly catch these failed attempts, we need to add a new pattern to the failregex
section:
failregex = ^%(__prefix_line)s(?:error: )?Connection closed by <HOST>(?: port \d+)? $$preauth$$\s*$
^%(__prefix_line)s(?:error: )?Received disconnect from <HOST>(?: port \d+)?:\s*$
^%(__prefix_line)s(?:error: )?Did not receive identification string from <HOST>\s*$
This regex will match:
- The standard [preauth] closure message
- Disconnect messages
- Missing identification cases
After modifying the filter, test it with fail2ban-regex:
fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf
Example successful output should show matches from your log file against the new pattern.
For Debian's default sshd jail, edit /etc/fail2ban/jail.local
:
[sshd]
enabled = true
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
findtime = 10m
bantime = 1h
After restarting fail2ban, verify it's catching the new patterns:
tail -f /var/log/fail2ban.log
You should see entries like:
2023-06-15 10:25:03,886 fail2ban.filter [25917]: INFO [sshd] Found 192.168.1.100