The sshd_config illusion: how to actually ensure SSH authentication security on cPanel and CloudLinux
Back to blog

The sshd_config illusion: how to actually ensure SSH authentication security on cPanel and CloudLinux

6/7/2026 · 3 min · Infrastructure

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:

  1. Drop-in Files (sshd_config.d/): Modern versions of OpenSSH load configuration modules from /etc/ssh/sshd_config.d/*.conf. If a file (such as 50-cloudlinux.conf or bootstrap directives injected by cloud providers) declares PasswordAuthentication yes, it overrides the main /etc/ssh/sshd_config declaration depending on the order of the Include directive.
  2. Cloud-Init Overrides: Cloud provisioning tools frequently rewrite SSH directives during early boots, silently restoring password authentication.
  3. WHM API Limitations: The whmapi1 edits 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 script
Gist ↗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:

CC BY-NC

This post is licensed under CC BY-NC.

Comments

Join the discussion below.

0 comments