Last updated: March 22, 2026

Secure Shell Hardening Beyond SSH Config

SSH configuration (/etc/ssh/sshd_config) is the first step. key-only auth, no root login, specific ciphers. But the session that follows SSH authentication has its own attack surface. This guide covers what happens after the connection is established: PAM controls, sudo hardening, session logging, and reducing SSH exposure entirely.

PAM - Restrict Who Can Log In

PAM (Pluggable Authentication Modules) controls authentication and session setup.

/etc/security/access.conf. allow only specific users/groups
Format - permission : user/group : origin
Deny all except members of the sshusers group

/etc/pam.d/sshd. add this line near the top:
account    required     pam_access.so
/etc/security/access.conf
Allow members of sshusers from anywhere
+ : (sshusers) : ALL

Allow specific user from specific IP range
+ : alice : 10.0.0.0/24

Deny everyone else
- : ALL : ALL
Create the sshusers group and add users
sudo groupadd sshusers
sudo usermod -aG sshusers alice
sudo usermod -aG sshusers bob

Test PAM access (does not actually log in)
sudo pamtester login alice authenticate

Time-Based Login Restrictions

Limit SSH access to business hours using PAM time:

/etc/security/time.conf
Format - service;tty;users;time
Deny SSH for contractor account on weekends

sshd;*;contractor;!Sa-Su0000-2400
sshd;*;contractor;Wk0800-1800   # weekdays 8am-6pm only
/etc/pam.d/sshd. add:
account    required     pam_time.so

Session Timeouts and Idle Lockout

/etc/profile.d/autologout.sh. set for all users
readonly TMOUT=600   # 10-minute idle timeout, disconnect
export TMOUT

For SSH specifically (belt-and-suspenders with sshd_config):
ClientAliveInterval 300
ClientAliveCountMax 2
These are in sshd_config, not profile. but document both
Lock screen on inactivity using vlock (for console sessions)
sudo apt install vlock
Users can run - vlock -a  to lock all virtual consoles

For remote sessions, TMOUT in /etc/profile.d/ is the primary control

sudo Hardening

/etc/sudoers.d/hardened. do not edit /etc/sudoers directly
Always - sudo visudo -f /etc/sudoers.d/hardened

Require password every time (disable sudo timestamp caching)
Defaults        timestamp_timeout=0

Log all sudo commands (default) and log to both syslog and a file
Defaults        logfile="/var/log/sudo.log"
Defaults        log_input, log_output
Defaults        iolog_dir="/var/log/sudo-io/%{user}"

Require full path for commands
Defaults        secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

Prevent sudo from running interactive shells (su escalation)
Defaults        !visiblepw
Defaults        always_set_home
Defaults        env_reset

Per-user - allow specific commands only, no shell
alice   ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx, /usr/bin/systemctl reload nginx
bob     ALL=(ALL) PASSWD: /usr/bin/apt-get update, /usr/bin/apt-get upgrade
Test sudo policy
sudo -l -U alice   # list what alice can do
sudo -l            # list what you can do

Review sudo logs
sudo journalctl -u sudo
tail -f /var/log/sudo.log

Login Banner and Legal Warning

A proper login banner establishes that unauthorized access is prohibited. required for many compliance frameworks.

/etc/issue.net. displayed before SSH login
sudo tee /etc/issue.net << 'EOF'
*
AUTHORIZED USE ONLY

Unauthorized access to this system is prohibited. All activity is monitored
and logged. Disconnect immediately if you do not have authorization.

By continuing, you consent to monitoring in accordance with company policy.
*
EOF

Enable in sshd_config:
Banner /etc/issue.net

Local console banner (shown before login prompt)
/etc/issue. same content
sudo cp /etc/issue.net /etc/issue

SSH Session Logging with script

Every command run in a SSH session can be recorded:

/etc/profile.d/session-log.sh
Logs all sessions to /var/log/sessions/

LOGDIR="/var/log/sessions"
mkdir -p "$LOGDIR"

if [ -n "$SSH_CONNECTION" ] && [ -z "$SCRIPT" ]; then
    LOGFILE="${LOGDIR}/$(date +%Y%m%d-%H%M%S)-$(whoami)-$(echo $SSH_CONNECTION | awk '{print $1}').log"
    export SCRIPT="$LOGFILE"
    exec script -qf "$LOGFILE"
fi

This records a full typescript of every SSH session. Combine with logrotate:

/etc/logrotate.d/sessions
/var/log/sessions/*.log {
    weekly
    rotate 12
    compress
    delaycompress
    missingok
    notifempty
}

fail2ban: Beyond Default SSH Protection

The default fail2ban SSH jail bans after 5 failures. Tighten it:

/etc/fail2ban/jail.local
[DEFAULT]
bantime  = 3600      # 1 hour (default is 600 seconds)
findtime = 600
maxretry = 3         # reduced from 5

Permanent ban after repeated banning
[recidive]
enabled  = true
filter   = recidive
logpath  = /var/log/fail2ban.log
maxretry = 3
findtime = 86400     # 24 hours
bantime  = 604800    # 7 days

[sshd]
enabled  = true
port     = ssh
filter   = sshd
logpath  = /var/log/auth.log
maxretry = 3
bantime  = 3600

Block scan tools that send malformed SSH packets
[sshd-ddos]
enabled  = true
port     = ssh
filter   = sshd-ddos
logpath  = /var/log/auth.log
maxretry = 2
bantime  = 7200
sudo systemctl restart fail2ban

Check current bans
sudo fail2ban-client status sshd
sudo fail2ban-client status recidive

Manually unban an IP
sudo fail2ban-client set sshd unbanip 1.2.3.4

Port Knocking - Hide SSH from Scanners

Port knocking keeps port 22 closed until a correct sequence of connection attempts unlocks it. Effective against automated scanners.

Install knockd
sudo apt install knockd

/etc/knockd.conf
[options]
    UseSyslog

[openSSH]
    sequence    = 7000,8000,9000
    seq_timeout = 5
    command     = /sbin/iptables -I INPUT -s %IP% -p tcp --dport 22 -j ACCEPT
    tcpflags    = syn

[closeSSH]
    sequence    = 9000,8000,7000
    seq_timeout = 5
    command     = /sbin/iptables -D INPUT -s %IP% -p tcp --dport 22 -j ACCEPT
    tcpflags    = syn
Default block SSH in iptables
sudo iptables -A INPUT -p tcp --dport 22 -j DROP

sudo systemctl enable --now knockd

To connect from a remote machine:
knock your-server 7000 8000 9000
ssh user@your-server
knock your-server 9000 8000 7000   # close the port again

Auditing Current Login Activity

Who is logged in right now
who
w

Recent successful logins
last -n 20

Recent failed logins
lastb -n 20

All authentication events
sudo journalctl -u ssh --since "24 hours ago" | grep -E "(Accepted|Failed|Invalid)"

IP addresses that attempted login
sudo grep "Failed password" /var/log/auth.log | awk '{print $(NF-3)}' | sort | uniq -c | sort -rn | head -20

Related Articles