Three Layers of Login Defense in Linux
Every day, an open public SSH server can be hit with anywhere from 5,000 to 20,000 automated password-guessing attempts from botnets. Beyond SSH, servers expose multiple other attack vectors: sudo/su commands executed within a terminal, SFTP services, or direct physical console logins. To counter this threat, system administrators generally rely on three approaches:
- Approach 1: Individual daemon configuration (Application-level). Modifying settings directly inside
sshd_config(such asMaxAuthTries 3) orvsftpd.conf. While easy to implement, this approach is fragmented and leaves internal access vectors unprotected. - Approach 2: Log monitoring with Fail2ban. Fail2ban monitors
/var/log/auth.log. When an IP address exceeds the threshold of failed attempts, it dynamically inserts a blocking rule for that IP into iptables or nftables. - Approach 3: Enforcing security at the core authentication layer (PAM – Pluggable Authentication Modules). PAM serves as the central identity checkpoint in Linux userspace. Whether a user enters a password via SSH, switches accounts using
su, or runs commands withsudo, all applications delegate the authentication request to PAM to be evaluated against a unified set of rules.
A Practical Comparison: When Does Fail2ban Fall Short of PAM?
Each solution targets a distinct stage in the attack chain:
1. Decentralized Service-Level Configuration
- Pros: Fast and straightforward. You only need to tweak one or two lines in a familiar configuration file and reload the daemon.
- Cons: Leaves security gaps. While you can restrict failed attempts to 3 for SSH, the same account can still be brute-forced without limits via Webmin interfaces, FTP ports, or local
sucommands.
2. Software Firewall (Fail2ban)
- Pros: Drops malicious network traffic at the perimeter, saving CPU cycles from processing unnecessary SSH handshakes.
- Cons: Ineffective against distributed brute-force attacks. If an attacker uses a rotating proxy network of 1,000 distinct IP addresses—attempting just one login per IP before switching—Fail2ban will never hit its jail threshold. Moreover, Fail2ban is completely useless if an attacker already has internal shell access and is brute-forcing root via
su.
3. Centralized Control with PAM
- Pros: Locks the user identity directly, regardless of source IP address or protocol. PAM allows you to enforce strict policies: lock an account for 15 minutes after 5 consecutive failures, enforce a minimum 12-character password length, and reject passwords containing the username.
- Risks: Modifying PAM configurations requires caution. A single syntax error or misordered module can easily lock you out of your own system.
Defense-in-Depth Strategy
You shouldn’t choose one over the other. The standard blueprint for production environments is a layered defense: deploy Fail2ban at the network perimeter to filter out 95% of automated scanning traffic, and enforce PAM internally as the ultimate safeguard for user accounts.
Detailed Step-by-Step PAM Configuration: pam_faillock and pam_pwquality
Before executing any commands below, ensure you have two separate, active SSH sessions open as root. Do not close these sessions until you have verified successful logins from a third terminal.
Step 1: Install Required Modules
Starting with Ubuntu 20.04/22.04 and RHEL/CentOS 8+, pam_faillock has fully superseded the deprecated pam_tally2 module.
On Ubuntu/Debian:
sudo apt update
sudo apt install -y libpam-modules libpam-pwquality
On RHEL/Rocky Linux/AlmaLinux:
sudo dnf install -y pam pam-devel libpwquality
Step 2: Configure Automatic Account Lockout with pam_faillock
The safest modern management method is configuring via the central file /etc/security/faillock.conf:
sudo nano /etc/security/faillock.conf
Uncomment and configure the following standard values:
# Maximum failed attempts before lockout
deny = 5
# Failure counter reset interval (seconds)
fail_interval = 900
# Account lockout duration: 900 seconds (15 minutes)
unlock_time = 900
# Do not specify rejection reason to prevent user enumeration
silent
Next, enable faillock within the system authentication stack.
On Ubuntu/Debian, open /etc/pam.d/common-auth:
sudo nano /etc/pam.d/common-auth
Declare pam_faillock.so surrounding the pam_unix.so check line:
auth required pam_faillock.so preauth
auth [success=1 default=ignore] pam_unix.so nullok
auth [default=die] pam_faillock.so authfail
auth sufficient pam_faillock.so authsucc
Also open /etc/pam.d/common-account and append this line at the top of the file:
account required pam_faillock.so
Note for RHEL/Rocky Linux: Do not edit PAM configuration files manually. Instead, run
sudo authselect enable-feature with-faillockfollowed byauthselect apply-changesto avoid your modifications being overwritten.
Step 3: Enforce Password Complexity with pam_pwquality
Open the password policy configuration file at /etc/security/pwquality.conf:
sudo nano /etc/security/pwquality.conf
Update the following directives:
# Minimum password length
minlen = 12
# Require at least 1 digit
dcredit = -1
# Require at least 1 lowercase letter
lcredit = -1
# Require at least 1 uppercase letter
ucredit = -1
# Require at least 1 special character
ocredit = -1
# Disallow passwords containing the username
usercheck = 1
# Disallow more than 3 consecutive identical characters
maxrepeat = 3
Using negative values (such as -1) sets a strict mandatory requirement, whereas positive values merely add weighted bonus credits when a user creates a password.
Step 4: Check Logs and Unlock Accounts with the faillock Command
If an employee mistakenly exceeds 5 failed attempts, the account will automatically be locked. Use root privileges to inspect the failure logs:
# View timestamps and source IPs for failed attempts
sudo faillock --user deploy_user
Sample terminal output:
deploy_user:
When Type Source Valid
2026-03-15 09:12:01 RHOST 192.168.1.50 V
2026-03-15 09:12:08 RHOST 192.168.1.50 V
2026-03-15 09:12:15 RHOST 192.168.1.50 V
2026-03-15 09:12:22 RHOST 192.168.1.50 V
2026-03-15 09:12:30 RHOST 192.168.1.50 V
To perform an emergency unlock without waiting for the 15-minute timer to expire:
sudo faillock --user deploy_user --reset
Step 5: Test the Workflow
Keep your existing root terminal active. Open a new terminal session to run the test:
- Execute
ssh deploy_user@<ip_server>and deliberately enter the wrong password 5 consecutive times. - On the 6th attempt, even if you supply the correct password, the system will reject the session with a Permission denied error.
- Return to the root terminal and run
sudo faillock --user deploy_userto confirm the locked state. - Execute
sudo faillock --user deploy_user --reset. The user can now log in again immediately.

