Those working in the trenches of high-level systems administration (SysAdmin / SRE roles) know that daily operations are filled with false senses of security. Recently, I led a hardening audit (Origin Event: #3104798) on the cPanel servers running CloudLinux under my administration. The goal was elementary: disabling password authentication (PasswordAuthentication no) and forcing exclusively SSH cryptographic keys.
What appeared to be a straightforward configuration tweak revealed a brutal operational disparity between the declarative statements in /etc/ssh/sshd_config and the actual effective state of the running SSH daemon.
When the configurations and APIs mislead you#
Initially, configuration changes were triggered using native cPanel command-line utilities via the WHM API 1 (whmapi1):
# 1. Check the current SSHD configuration registered by WHM
whmapi1 listsshdconfig
# 2. Disable password authentication via the WHM API
# (Note: depending on the WHM version, the API may use alternative keys such as PasswordAuth or fail to propagate to drop-ins; the direct file configuration + sshd -T method is the safest)
whmapi1 setserviceconfig service=sshd key=PasswordAuthentication value=no
The Technical Bottleneck: Although the call returns success and the main /etc/ssh/sshd_config file reflects the change, the server may continue to accept password connections. This is due to three factors in the ecosystem:
- Drop-in Files (
sshd_config.d/): Modern versions of OpenSSH load configuration modules from/etc/ssh/sshd_config.d/*.conf. If a file (such as50-cloudlinux.confor bootstrap directives injected by cloud providers) declaresPasswordAuthentication yes, it overrides the main/etc/ssh/sshd_configdeclaration depending on the order of theIncludedirective. - Cloud-Init Overrides: Cloud provisioning tools frequently rewrite SSH directives during early boots, silently restoring password authentication.
- WHM API Limitations: The
whmapi1edits the core configuration file but often fails to scan conflicting drop-ins or properly restart the service with the appropriate hangup signal (SIGHUP).
Finding conflicting drop-in files#
To audit whether there are hidden configuration files interfering with the global SSH rules:
# List all active drop-in files
ls -la /etc/ssh/sshd_config.d/
# Search for the authentication directive in all SSH configuration files
grep -r -i "PasswordAuthentication" /etc/ssh/sshd_config.d/
# Verify the Include directive in the main file
grep -i "^Include" /etc/ssh/sshd_config
# Quick script to map the files and their declared values
for f in /etc/ssh/sshd_config.d/*.conf; do
echo "=== File: $f ==="
grep -i "PasswordAuthentication" "$f" 2>/dev/null || echo "(directive missing)"
done
Step-by-step correction procedure#
If you identify configuration discrepancies, follow the safe correction flow below:
1. Mandatory security backups#
Before making any changes to the SSH daemon, create backups of the entire configuration tree to prevent severe misconfigurations from locking you out of the server:
# Backup the main configuration file
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date +%Y%m%d)
# Backup the drop-in directory
sudo cp -r /etc/ssh/sshd_config.d/ /root/sshd_config.d.bak.$(date +%Y%m%d)/
# Unified tarball backup
sudo tar czf /root/ssh-backup-$(date +%Y%m%d).tar.gz /etc/ssh/
2. SSH key configuration (local client to server)#
Before disabling password authentication, ensure your public key is properly authorized on the remote server.
On your local machine (client):
# Generate a secure key pair using the ED25519 algorithm
ssh-keygen -t ed25519 -C "[email protected]"
# Copy the public key to the remote server
ssh-copy-id -p 22 username@server-ip-address
Test the passwordless login by starting a test session:
ssh -p 22 username@server-ip-address
3. Clearing drop-ins and conflicting settings#
Access the server as root. If you find instances of PasswordAuthentication yes in the drop-in directories, edit them to define the correct value or remove them if they are redundant:
# Change all PasswordAuthentication occurrences in drop-ins to "no"
sudo sed -i 's/PasswordAuthentication yes/PasswordAuthentication no/g' /etc/ssh/sshd_config.d/*.conf 2>/dev/null || true
In the main /etc/ssh/sshd_config file, ensure the option is turned off:
sudo sed -i 's/^PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
4. Additional SSHD security hardening#
Take the opportunity to ensure other critical security directives are active:
# Disable direct root password login
PermitRootLogin prohibit-password
# Force public key authentication
PubkeyAuthentication yes
# Disable keyboard-interactive authentication to block PAM password bypasses
ChallengeResponseAuthentication no
# Limit maximum failed login attempts per connection
MaxAuthTries 3
# Tolerance window to complete authentication
LoginGraceTime 30
5. Syntax validation#
Never restart the SSH service without validating configuration syntax first:
sudo sshd -t
If this command outputs any errors, do not restart SSH! Inspect the line indicated in the logs.
6. Safe testing in a separate session (lockout prevention)#
SysAdmin Golden Rule: Never close your active SSH session. With your current terminal open, open a new terminal on your local machine and run the following connection test:
# Force a connection attempt using public key authentication only
ssh -o "BatchMode=yes" -o "ConnectTimeout=5" username@server-ip-address echo "Secure Connection OK"
If the output is "Secure Connection OK", your key-based access is working perfectly.
7. Applying changes and restarting the daemon#
Now, restart the SSH service to load the new settings in memory:
sudo systemctl restart sshd
Validating the effective in-memory state (correct one-liner)#
The definitive way to audit the server is to interrogate the daemon loaded in memory using the sshd -T utility (extended test mode). It resolves all recursive includes and prints the final operating state.
Use the corrected Bash one-liner below for validation:
# Validate that PasswordAuthentication is indeed disabled
sshd -T | grep -qi '^passwordauthentication no$' && echo "CONFORMITY CHECK PASS: PasswordAuthentication=no (Enforced)" || echo "NON_CONFORMITY ALERT: PasswordAuthentication!=no (Failed)"
To list all effective parameters related to SSH security:
sshd -T | grep -iE "^(passwordauthentication|pubkeyauthentication|permitrootlogin|challengeresponseauthentication|usepam)"
Auditing SSH logs#
To ensure brute force password attacks are blocked and to debug authentication events, monitor the authentication logs (/var/log/secure on RHEL/CloudLinux/cPanel systems or /var/log/auth.log on Debian/Ubuntu):
# Monitor SSH authentication logs in real time
tail -f /var/log/secure | grep sshd
# List the last 20 failed or invalid login attempts
sudo grep -i "failed\|invalid" /var/log/secure | tail -20
# Check successful public key logins
sudo grep -i "accepted publickey" /var/log/secure | tail -20
Emergency rollback procedure#
If you lose access or the SSH service fails to start, log in to the VPS using the provider's emergency console terminal (VNC/IPMI) and revert the settings:
# 1. Clear modified configuration files
sudo rm -rf /etc/ssh/sshd_config.d/
sudo rm -f /etc/ssh/sshd_config
# 2. Restore backup files created at the start
sudo cp /etc/ssh/sshd_config.bak.* /etc/ssh/sshd_config
sudo cp -r /root/sshd_config.d.bak.*/ /etc/ssh/sshd_config.d/
# 3. Validate syntax
sudo sshd -t
# 4. Restart the service
sudo systemctl restart sshd
Full SSH audit script#
Below is the complete shell script for automatic compliance auditing:
audit-ssh.sh — Advanced SSH compliance and security audit scriptGist ↗Expand code
#!/bin/bash
# audit-ssh.sh - Advanced SSH compliance and security audit
set -euo pipefail
# 0. Privilege elevation check to inspect SSH keys and configurations
if [ "${EUID:-$(id -u)}" -ne 0 ]; then
echo "⚠️ Notice: This script requires root privileges to inspect host keys and drop-ins (/etc/ssh/)."
if command -v sudo >/dev/null 2>&1; then
echo " Elevating with sudo..."
exec sudo bash "$0" "$@"
else
echo "❌ Error: sudo not found and user is not root. Please run as root." >&2
exit 1
fi
fi
echo "=== STARTING SSH AUDIT ==="
echo ""
# 1. Effective In-Memory State
echo "[1] Checking Effective Parameters (sshd -T):"
if SSHD_OUT=$(sshd -T 2>&1); then
PASS_AUTH=$(echo "$SSHD_OUT" | grep -i "^passwordauthentication" | awk '{print $2}' || echo "unknown")
if [ "$PASS_AUTH" = "no" ]; then
echo " ✅ PasswordAuthentication: NO (Secure)"
else
echo " ❌ PasswordAuthentication: $PASS_AUTH (Insecure - Password login allowed)"
fi
ROOT_LOGIN=$(echo "$SSHD_OUT" | grep -i "^permitrootlogin" | awk '{print $2}' || echo "unknown")
if [ "$ROOT_LOGIN" = "no" ] || [ "$ROOT_LOGIN" = "prohibit-password" ]; then
echo " ✅ PermitRootLogin: $ROOT_LOGIN (Secure)"
else
echo " ⚠️ PermitRootLogin: $ROOT_LOGIN (Insecure - Root password login allowed)"
fi
CHALLENGE=$(echo "$SSHD_OUT" | grep -i "^challengeresponseauthentication" | awk '{print $2}' || echo "unknown")
if [ "$CHALLENGE" = "no" ]; then
echo " ✅ ChallengeResponseAuthentication: NO (Secure)"
else
echo " ⚠️ ChallengeResponseAuthentication: $CHALLENGE (PAM password bypass risk)"
fi
else
echo " ❌ Failed to execute sshd -T:"
echo "$SSHD_OUT" | sed 's/^/ /'
fi
# 2. Drop-ins and Include conflicts in sshd_config.d/
echo ""
echo "[2] Checking Drop-in Files and Includes:"
DROPINS_COUNT=$(find /etc/ssh/sshd_config.d/ -maxdepth 1 -type f -name "*.conf" 2>/dev/null | wc -l | tr -d ' ' || echo 0)
echo " Total active drop-ins: $DROPINS_COUNT"
CONFLICT_LINES=$(grep -rnEI '^[[:space:]]*PasswordAuthentication' /etc/ssh/sshd_config.d/ 2>/dev/null || true)
if [ -n "$CONFLICT_LINES" ]; then
CONFLICTS=$(echo "$CONFLICT_LINES" | wc -l | tr -d ' ')
echo " ⚠️ Found $CONFLICTS PasswordAuthentication conflicts in drop-ins:"
echo "$CONFLICT_LINES" | sed 's/^/ /'
else
echo " ✅ No PasswordAuthentication conflicts in drop-in files."
fi
echo ""
echo "=== AUDIT COMPLETE ==="Was this article helpful?
Leave a quick reaction to help prioritize future technical guides:
This post is licensed under CC BY-NC.



Comments
Join the discussion below.
0 comments