Post

Hardening OpenSSH

SSH (Secure Shell) is a critical service that provides encrypted remote access to systems. Properly hardening SSH is essential for protecting your servers from unauthorized access and potential attacks. This guide covers best practices for securing your SSH configuration.

Hardening OpenSSH

Comprehensive Guide to SSH Hardening

If you’re running a server, SSH is likely your lifeline—but it’s also a popular target for attackers. Our OpenSSH Hardening Guide walks you through practical, battle-tested steps to tighten your SSH configuration. From disabling password logins to limiting access and enabling robust logging, this guide helps you reduce risk and keep your systems secure. Whether you’re a seasoned sysadmin or just starting out, it’s time to give your SSH the armor it deserves.

Considerations before proceeding:

  • Add your public key to the server, so that you can disable password login
  • Add an IP address or range to the whitelist (if you implement psad youll understand)
  • Blacklist IPs && Countries (using fail2ban)
    • uses geodb*

SSH Key Authentication

You’ll need to add your public key to the remote server to ensure you do not get yourself locked out.

To copy your SSH public key to a remote server and enable key-based authentication, you’ve got a few easy options. Here’s how to do it:


This is the simplest and safest method.

Step-by-Step:

  1. Generate a key pair (if you haven’t already):
    1
    
    ssh-keygen -t ed25519
    
  2. Copy your public key to the remote server:
    1
    
    ssh-copy-id username@remote_ip
    

You’ll be prompted to enter your password one last time. After that, key-based auth will work.

Where it goes:
This command appends your public key (~/.ssh/id_ed25519.pub by default) to the remote file:

1
~/.ssh/authorized_keys

Manual Method (if ssh-copy-id isn’t available)

  1. Print your public key:
    1
    
    cat ~/.ssh/id_ed25519.pub
    
  2. On the remote server, make sure ~/.ssh exists:
    1
    2
    
    mkdir -p ~/.ssh
    chmod 700 ~/.ssh
    
  3. Append your public key to authorized_keys: On your local machine:
    1
    
    cat ~/.ssh/id_ed25519.pub | ssh username@remote_ip "cat >> ~/.ssh/authorized_keys"
    
  4. Set permissions on the remote server:
    1
    
    chmod 600 ~/.ssh/authorized_keys
    

Test the Connection

After copying the key, test logging in:

1
ssh username@remote_ip

If everything’s good, it should log you in without asking for a password.


Always test your SSH hardening measures from a separate session to avoid locking yourself out. A properly hardened SSH configuration is a strong defense against unauthorized access while maintaining the functionality needed for legitimate users.

Comprehensive OpenSSH Server Setup and Security Hardening Guide

1. Introduction

This guide provides detailed instructions for setting up, configuring, and securing an OpenSSH server on Linux. It covers installation, basic configuration, advanced security settings, key authentication, monitoring, and troubleshooting. Each section includes explanatory comments to help you understand why certain configurations are recommended and how they enhance security.

2. Prerequisites

  • A Linux server with root or sudo access
  • Network connectivity
  • Basic knowledge of Linux command line and text editors
  • Understanding of firewall concepts
  • Basic knowledge of public key cryptography

3. Installation

Debian/Ubuntu-based distributions

1
2
3
4
5
6
7
8
# Update package lists to ensure we get the latest version
sudo apt update

# Upgrade existing packages for security patches 
sudo apt upgrade -y

# Install the OpenSSH server package
sudo apt install openssh-server -y

RHEL/CentOS/Fedora-based distributions

1
2
3
4
5
# Update all packages to latest versions with security patches
sudo dnf update -y

# Install the OpenSSH server package
sudo dnf install openssh-server -y

Arch Linux

1
2
3
4
5
6
# Sync package database and perform full system upgrade
# The -Syu flags: S=sync, y=refresh package database, u=upgrade installed packages
sudo pacman -Syu

# Install OpenSSH package
sudo pacman -S openssh

SUSE/openSUSE

1
2
3
4
5
# Refresh package repositories to get latest information
sudo zypper refresh

# Install the OpenSSH package
sudo zypper install openssh

4. Initial Configuration

Service Management

Start and enable the SSH service

1
2
3
4
5
6
# Start the SSH service immediately
sudo systemctl start sshd

# Enable the SSH service to start automatically on system boot
# This ensures SSH will be available after server reboots
sudo systemctl enable sshd

Verify service status

1
2
3
# Check if the service is running properly and view recent log entries
# Look for "Active: active (running)" in the output
sudo systemctl status sshd

Service management on older systems without systemd

1
2
3
4
5
6
7
8
9
10
# Start the SSH service on legacy init systems
sudo service sshd start

# Enable service autostart on RHEL/CentOS with SysV init
# This adds appropriate symlinks in /etc/rc*.d/ directories
sudo chkconfig sshd on  # For RHEL/CentOS

# Enable service autostart on Debian/Ubuntu with SysV init
# "defaults" means start in runlevels 2,3,4,5 and stop in 0,1,6
sudo update-rc.d ssh defaults  # For Debian/Ubuntu

Firewall Configuration

For UFW (Ubuntu’s Uncomplicated Firewall)

1
2
3
4
5
6
7
# Allow SSH traffic through the default port (22)
# UFW will automatically open the port and create appropriate rules
sudo ufw allow ssh  # Default port 22

# If you changed SSH to use a custom port (recommended for security)
# Replace 2222 with your chosen port number
sudo ufw allow 2222/tcp

For firewalld (RHEL/CentOS/Fedora)

1
2
3
4
5
6
7
8
9
10
# Add a permanent rule allowing the default SSH service
# This automatically allows port 22/tcp and adds it to the configuration
sudo firewall-cmd --permanent --add-service=ssh

# If using a custom port (e.g., 2222), add that port instead
# This is more secure than using the default port
sudo firewall-cmd --permanent --add-port=2222/tcp

# Reload the firewall to apply the changes without disrupting existing connections
sudo firewall-cmd --reload

For iptables (lower-level firewall used on many distributions)

1
2
3
4
5
6
7
8
9
10
# Append (-A) a rule to the INPUT chain allowing TCP traffic on port 22
# -j ACCEPT means the traffic will be accepted if it matches this rule
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT

# If using custom port (recommended for better security)
sudo iptables -A INPUT -p tcp --dport 2222 -j ACCEPT

# Save the rules to make them persistent after reboot
# The directory path may vary depending on your distribution
sudo iptables-save > /etc/iptables/rules.v4  # Save rules

5. Core Security Configuration

Backup the original configuration

Always backup your configuration before making changes:

1
2
3
4
5
6
7
# Create a backup of the original configuration file
# This allows you to revert if something goes wrong
# The .bak extension helps identify it as a backup file
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak

# You can also create a dated backup for multiple configuration versions
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date +%Y%m%d)

Using sshd_config.d for modular configuration (OpenSSH 8.2+)

Newer versions of OpenSSH support modular configuration:

1
2
3
4
5
6
7
# Create the directory for modular config files if it doesn't exist
# The -p flag creates parent directories as needed
sudo mkdir -p /etc/ssh/sshd_config.d

# Create a new configuration file specifically for security settings
# This modular approach makes it easier to manage different aspects separately
sudo nano /etc/ssh/sshd_config.d/security.conf

Ensure this line is uncommented in your main sshd_config:

1
2
3
# This directive tells SSH to include all .conf files from this directory
# It allows for modular configuration management without editing the main file
Include /etc/ssh/sshd_config.d/*.conf

Essential Security Settings

Edit the main SSH configuration file:

1
2
3
# Open the SSH config file in a text editor (nano in this example)
# This is the main configuration file that controls SSH server behavior
sudo nano /etc/ssh/sshd_config

Apply these critical security settings:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# Protocol version (only protocol 2 should be used)
# Protocol 1 has known vulnerabilities and should never be used
Protocol 2

# Change default port (increases security by obscurity, reducing automated attacks)
# Choose a non-standard port between 1024-65535 that's not in common use
# This helps avoid automated scanning/brute force attempts targeting port 22
Port 2222

# Limit login attempts and timeouts
# Restricts to 3 failed authentication attempts before disconnecting
MaxAuthTries 3
# Terminates the connection if authentication isn't completed in 30 seconds
# Prevents hanging connections and reduces DoS attack surface
LoginGraceTime 30s

# Disable empty passwords and password authentication (use keys only)
# Never allow users to have empty passwords
PermitEmptyPasswords no
# Disable password authentication entirely - this forces key-based auth
# This is one of the most important security settings as it eliminates
# the possibility of brute force password attacks
PasswordAuthentication no

# Disable root login
# Prevents direct root login, forcing attackers to compromise a regular
# user account first and then escalate privileges
PermitRootLogin no

# Only allow specific users or groups
# Whitelist approach - only these users can connect via SSH
# Replace with your actual usernames
AllowUsers user1 user2 user3
# OR - allow all users in these groups
# This is useful for role-based access control
AllowGroups ssh-users admin-group

# Deny specific users or groups
# Additional blacklist for specific users you want to block
# Useful for service accounts that should never log in interactively
DenyUsers user4 user5
# Block entire groups from SSH access
DenyGroups blocked-group

# Restrict to specific interfaces (if server has multiple)
# Only listen on specific IP addresses rather than all interfaces
# Useful for servers with public and private network interfaces
ListenAddress 192.168.1.100
# You can have multiple ListenAddress lines for multiple interfaces
# ListenAddress 10.0.0.15

# Authentication settings
# Check ownership and permissions of key files before allowing login
# Prevents use of insecure permissions on key files
StrictModes yes  
# Enable public key authentication (this should always be yes)
PubkeyAuthentication yes
# Path to authorized keys file relative to user's home directory
AuthorizedKeysFile .ssh/authorized_keys

# Disable unused authentication methods
# Kerberos authentication - disable if not specifically needed
KerberosAuthentication no
# GSSAPI (used by Kerberos) - disable if not needed
GSSAPIAuthentication no
# Host-based authentication is less secure and rarely used
HostbasedAuthentication no
# Pluggable Authentication Modules - usually needed for system integration
# Leave enabled unless you know exactly why you're disabling it
UsePAM yes  

# Disable X11 forwarding if not needed
# If you don't need GUI applications, disable this to reduce attack surface
X11Forwarding no

# Disable TCP port forwarding if not needed
# Prevents users from creating tunnels through your SSH server
# This can prevent pivoting attacks to internal networks
AllowTcpForwarding no
# Disable local socket forwarding
AllowStreamLocalForwarding no
# Disable remote hosts connecting to local forwarded ports
GatewayPorts no
# Disable TUN/TAP virtual interface forwarding (used for VPNs)
PermitTunnel no

# Set secure ciphers, MACs, and key exchange algorithms
# Strong, modern algorithms only - removes weak or deprecated options
# Encryption algorithms in order of preference
Ciphers [email protected],[email protected],[email protected],aes256-ctr,aes192-ctr,aes128-ctr
# Message Authentication Codes (MACs) ensure data integrity
# ETM (encrypt-then-mac) modes are preferred for better security
MACs [email protected],[email protected],hmac-sha2-512,hmac-sha2-256
# Key exchange algorithms - how the initial shared secret is established
# curve25519 is currently considered the most secure option
KexAlgorithms curve25519-sha256,[email protected],diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256
# Types of host keys the server will use
# Ed25519 keys are preferred for their security and performance
HostKeyAlgorithms ssh-ed25519,[email protected],rsa-sha2-512,[email protected],rsa-sha2-256,[email protected]

# Banner and login information disclosure
# Display a warning message before authentication
Banner /etc/ssh/banner.txt
# Don't display the motd (message of the day) after login
PrintMotd no
# Do show when the user last logged in (security best practice)
PrintLastLog yes
# Don't show the "Debian" banner version information
# Reduces information disclosure about your system
DebianBanner no

# Idle timeout settings (disconnect idle sessions)
# Server will send a keepalive message every 5 minutes
ClientAliveInterval 300
# After 2 consecutive failed responses (10 minutes total), disconnect the client
# Prevents abandoned sessions from staying open indefinitely
ClientAliveCountMax 2

# Limit users' SSH environment options
# Prevents users from setting custom environment variables
# This could be used for exploits, so best to disable
PermitUserEnvironment no

# Chroot directory for SFTP (if using SFTP)
# These commented lines show how to set up a chroot jail for SFTP users
# Uncomment and customize if you need isolated SFTP access
# Subsystem sftp internal-sftp
# Match Group sftp-only
#    ChrootDirectory /home/%u
#    ForceCommand internal-sftp
#    AllowTcpForwarding no
#    X11Forwarding no

# Disable agent forwarding if not needed
# SSH agent forwarding can be a security risk if not needed
# Prevents lateral movement in case of compromised session
AllowAgentForwarding no

# Maximum SSH sessions per connection
# Limits to 2 sessions per connection to prevent resource exhaustion
MaxSessions 2

# Disable challenge-response authentication
# Another form of password authentication that should be disabled
# when using key-based authentication
ChallengeResponseAuthentication no

# Compression (disable if not needed, or restrict to after authentication)
# "delayed" means compression only happens after authentication
# Protects against compression oracle attacks
Compression delayed

# Logging level (VERBOSE logs more information including failed login attempts)
# Important for security monitoring and incident response
LogLevel VERBOSE

# Disable .rhosts files
# .rhosts files are a legacy and insecure authentication method
IgnoreRhosts yes

# Specify which address family should be used
# inet = IPv4 only, inet6 = IPv6 only, any = both
# Use "inet" if you don't use IPv6 to reduce attack surface
AddressFamily any

# Maximum startups (prevents connection flooding)
# Format: start:rate:full
# start: max unauthenticated connections allowed without dropping
# rate: percentage chance of dropping connections when at "start" limit
# full: hard maximum where all new connections are dropped
# This setting means: allow 10 unauthenticated connections, then start
# randomly dropping 30% of connections, and when we hit 60 connections,
# drop all new connection attempts
MaxStartups 10:30:60

Create a warning banner

1
2
3
# Create a legal warning banner that will be displayed to connecting users
# This serves both as a deterrent and as a legal notice
sudo nano /etc/ssh/banner.txt

Example banner content:

1
2
3
4
5
6
**************************************************************************
                         AUTHORIZED ACCESS ONLY
 This system is restricted to authorized users. All activities on this 
 system are logged and monitored. Unauthorized access will be fully 
 investigated and reported to the appropriate law enforcement agencies.
**************************************************************************

The banner serves several important purposes:

  • Legal notice that unauthorized access is prohibited
  • Informs users that their activities are being logged
  • May help with legal prosecution in case of break-ins
  • Displays no system information that could be useful to attackers

6. SSH Key Authentication Setup

Generate SSH Key Pair (on client machine)

1
2
3
4
5
# Generate an Ed25519 key pair (most secure current option)
# -t ed25519: Specifies the key type (Ed25519 - modern, secure algorithm)
# -a 100: Sets KDF rounds for passphrase protection (higher = more secure)
# -C "comment": Adds a comment (usually email) to identify the key
ssh-keygen -t ed25519 -a 100 -C "[email protected]"

RSA keys (use if compatibility with older systems is needed)

1
2
3
4
5
6
# Generate an RSA key pair (more widely compatible with older systems)
# -t rsa: Specifies RSA key type
# -b 4096: Sets key size to 4096 bits (stronger than default 2048)
# -a 100: Sets KDF rounds as above
# -C "comment": Adds identifying comment
ssh-keygen -t rsa -b 4096 -a 100 -C "[email protected]"

The -a flag specifies the number of KDF (Key Derivation Function) rounds, increasing resistance to brute-force password cracking. Higher values make passphrase cracking significantly slower without affecting normal use.

Transfer the Public Key to the Server

Using ssh-copy-id (easiest method)

1
2
3
4
# Copies your public key to the server's authorized_keys file
# -i: Specifies the identity/public key file to use
# This automatically sets correct permissions on the server
ssh-copy-id -i ~/.ssh/id_ed25519.pub username@server_ip

Manual method if ssh-copy-id is not available

1
2
3
4
5
6
7
8
# This one-liner accomplishes the same thing as ssh-copy-id:
# 1. Outputs your public key
# 2. Pipes it to an SSH session on the server
# 3. Creates ~/.ssh directory if it doesn't exist
# 4. Sets proper 700 permissions on the directory
# 5. Appends your key to authorized_keys file
# 6. Sets proper 600 permissions on the file
cat ~/.ssh/id_ed25519.pub | ssh username@server_ip "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"

Verify key-based authentication

1
2
3
4
5
# Test SSH connection explicitly using your key
# -i: Specifies which private key to use for authentication
# If successful, you should connect without password prompt
# (You will still need to enter your key passphrase if set)
ssh -i ~/.ssh/id_ed25519 username@server_ip

Set proper permissions on the server

1
2
3
4
5
6
7
# Restrict directory permissions to owner only (rwx------)
# SSH will refuse to use key files if permissions are too open
chmod 700 ~/.ssh

# Restrict file permissions to owner only (rw-------)
# This prevents other users from reading your authorized_keys
chmod 600 ~/.ssh/authorized_keys

Passphrase protection for SSH keys

Always use a strong passphrase to protect your private key. This provides two-factor authentication:

  • Something you have (the key file)
  • Something you know (the passphrase)

Without a passphrase, anyone who obtains your private key file can immediately use it to authenticate as you. A strong passphrase ensures that even if your private key is stolen, the attacker would still need to crack the passphrase to use it.

Using SSH Agent for convenience

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Start the SSH agent in the current shell and set required environment variables
# The eval command ensures the environment variables are properly set
eval $(ssh-agent)

# Add your private key to the agent
# This will prompt for your passphrase once, then keep the key in memory
# You won't need to enter the passphrase again for each connection
ssh-add ~/.ssh/id_ed25519

# To verify keys in the agent
ssh-add -l

# To set a timeout (example: 1 hour = 3600 seconds)
# After this time, the key will be automatically removed from the agent
ssh-add -t 3600 ~/.ssh/id_ed25519

The SSH agent allows you to:

  • Enter your passphrase just once per session
  • Maintain security with a strong passphrase
  • Connect to multiple servers without re-entering your passphrase each time
  • Forward your authentication to other systems without exposing your private key

7. Advanced Security Enhancements

Two-Factor Authentication (2FA)

Install Google Authenticator PAM module

Debian/Ubuntu:

1
2
3
4
# Install the Google Authenticator PAM module
# This provides time-based one-time password (TOTP) functionality
# libpam-google-authenticator integrates with Linux's PAM system
sudo apt install libpam-google-authenticator

RHEL/CentOS/Fedora:

1
2
3
# Install Google Authenticator and PAM development package
# The pam package provides development files needed
sudo dnf install google-authenticator pam

Configure Google Authenticator for a user

Run as the user that needs 2FA (not as root):

1
2
3
4
# Initialize Google Authenticator for the current user
# IMPORTANT: Must be run as the user, not as root!
# This will create a ~/.google_authenticator file with the secrets
google-authenticator

Follow the prompts and scan the QR code with your authenticator app.

  • Answer “y” to time-based tokens
  • Answer “y” to update the ~/.google_authenticator file
  • Answer “y” to disallow multiple uses of the same token
  • Answer “y” to increase the time window (helpful if server/client clocks are slightly off)
  • Answer “y” to rate limiting (prevents brute force attacks)

Configure fail2ban to protect against brute force

Install fail2ban

1
2
3
4
5
6
7
8
9
# Debian/Ubuntu
sudo apt install fail2ban

# RHEL/CentOS
sudo dnf install epel-release
sudo dnf install fail2ban

# Arch Linux
sudo pacman -S fail2ban

Configure fail2ban for SSH

1
2
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local

Add or modify the SSH jail section:

1
2
3
4
5
6
7
8
[sshd]
enabled = true
port = 2222  # Your custom SSH port
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600  # 1 hour (in seconds)
findtime = 600  # 10 minutes (in seconds)

Create a custom filter for aggressive banning

1
sudo nano /etc/fail2ban/filter.d/sshd-aggressive.conf

Add this content:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[Definition]
failregex = ^%(__prefix_line)s(?:error: PAM: )?Authentication failure for .* from <HOST>
            ^%(__prefix_line)s(?:error: PAM: )?User not known to the underlying authentication module for .* from <HOST>
            ^%(__prefix_line)sFailed password for .* from <HOST>
            ^%(__prefix_line)sROOT LOGIN REFUSED.* FROM <HOST>
            ^%(__prefix_line)s[iI](?:llegal|nvalid) user .* from <HOST>
            ^%(__prefix_line)sUser .+ from <HOST> not allowed because not listed in AllowUsers
            ^%(__prefix_line)sUser .+ from <HOST> not allowed because listed in DenyUsers
            ^%(__prefix_line)sUser .+ from <HOST> not allowed because not in any group
            ^%(__prefix_line)srefused connect from \S+ \(<HOST>\)
            ^%(__prefix_line)sReceived disconnect from <HOST>: 3: \S+: Auth fail
            ^%(__prefix_line)sUser .+ from <HOST> not allowed because a group is listed in DenyGroups
            ^%(__prefix_line)sUser .+ from <HOST> not allowed because none of user's groups are listed in AllowGroups
ignoreregex =

Create a jail for the aggressive filter

1
sudo nano /etc/fail2ban/jail.d/sshd-aggressive.conf

Add this content:

1
2
3
4
5
6
7
8
[sshd-aggressive]
enabled = true
port = 2222  # Your custom SSH port
filter = sshd-aggressive
logpath = /var/log/auth.log
maxretry = 1
bantime = 86400  # 24 hours (in seconds)
findtime = 3600  # 1 hour (in seconds)

Start and enable fail2ban

1
2
sudo systemctl enable fail2ban
sudo systemctl start fail2ban

Intrusion Detection with AIDE (Advanced Intrusion Detection Environment)

Install AIDE

1
2
3
4
5
# Debian/Ubuntu
sudo apt install aide

# RHEL/CentOS
sudo dnf install aide

Configure AIDE

1
sudo nano /etc/aide/aide.conf

Ensure these SSH directories are monitored:

1
2
/etc/ssh p+i+n+u+g+s+m+c+acl+selinux+xattrs+sha512
/etc/ssh/sshd_config p+i+n+u+g+s+m+c+acl+selinux+xattrs+sha512

Initialize AIDE database

1
2
3
4
sudo aideinit
# OR on some distributions
sudo aide --init
sudo mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db

Setup daily checks

1
sudo nano /etc/cron.daily/aide

Add:

1
2
#!/bin/bash
/usr/bin/aide --check | mail -s "AIDE report for $(hostname)" root@localhost

Make it executable:

1
sudo chmod +x /etc/cron.daily/aide

TCP Wrappers (on systems that support it)

Some modern Linux distributions are phasing this out, but it’s still useful on many systems.

1
sudo nano /etc/hosts.allow

Add:

1
sshd: 192.168.1.0/24 10.0.0.0/8
1
sudo nano /etc/hosts.deny

Add:

1
sshd: ALL

Port Knocking with knockd

Add an extra layer of obscurity by hiding your SSH port until a specific sequence of connections is made.

1
2
3
4
5
# Debian/Ubuntu
sudo apt install knockd

# RHEL/CentOS
sudo dnf install knock knockd

Configure knockd:

1
sudo nano /etc/knockd.conf

Example configuration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[options]
        UseSyslog

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

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

Enable and start knockd:

1
2
sudo systemctl enable knockd
sudo systemctl start knockd

8. SSH Configuration Hardening Tests

Use tools to evaluate your SSH security posture:

ssh-audit

1
2
3
4
5
# Install
pip3 install ssh-audit

# Run audit
ssh-audit hostname_or_ip

Lynis Security Audit

1
2
3
4
5
# Debian/Ubuntu
sudo apt install lynis

# Run audit
sudo lynis audit system

Manual sshd configuration testing

1
sudo sshd -t

9. Monitoring and Logging

Enable verbose SSH logging

1
sudo nano /etc/ssh/sshd_config

Set:

1
LogLevel VERBOSE

Configure centralized logging

For larger environments, consider sending SSH logs to a central log server using rsyslog:

1
sudo nano /etc/rsyslog.conf

Add:

1
auth,authpriv.*                 @logserver.example.com:514

Log visualization with GoAccess

1
2
3
4
5
# Install
sudo apt install goaccess

# Generate report
sudo goaccess /var/log/auth.log -o /var/www/html/ssh-report.html --log-format=COMBINED

Configure email alerts for failed logins

Create a custom script:

1
sudo nano /usr/local/bin/ssh-alert.sh

Add:

1
2
3
4
5
6
#!/bin/bash
LOGFILE="/var/log/auth.log"
KEYWORDS="Failed password|Invalid user|Failed publickey"
MAILTO="[email protected]"

/bin/grep -E "$KEYWORDS" $LOGFILE | /bin/grep "sshd" | tail -n 20 | mail -s "SSH Alert: Possible intrusion on $(hostname)" $MAILTO

Make it executable and add to cron:

1
2
sudo chmod +x /usr/local/bin/ssh-alert.sh
sudo crontab -e

Add:

1
0 * * * * /usr/local/bin/ssh-alert.sh  # Run hourly

10. Additional Security Measures

Rate limiting SSH connections with iptables

1
2
3
sudo iptables -A INPUT -p tcp --dport 2222 -m state --state NEW -m recent --set
sudo iptables -A INPUT -p tcp --dport 2222 -m state --state NEW -m recent --update --seconds 60 --hitcount 4 -j DROP
sudo iptables-save > /etc/iptables/rules.v4

SSH connection multiplexing for efficiency

On the client side, edit ~/.ssh/config:

1
2
3
4
5
6
7
Host server_alias
  HostName server_hostname_or_ip
  User username
  Port 2222
  ControlMaster auto
  ControlPath ~/.ssh/control:%h:%p:%r
  ControlPersist 10m

Configure an SSH jump host/bastion

1
2
3
4
5
6
7
8
9
10
Host private_server
  HostName 10.0.0.10
  User username
  ProxyJump jump_host
  IdentityFile ~/.ssh/id_ed25519

Host jump_host
  HostName bastion.example.com
  User jumpuser
  IdentityFile ~/.ssh/id_ed25519

Geo-IP filtering with xtables-addons

Block SSH connections from countries you don’t need access from:

1
2
3
4
5
6
7
8
9
10
11
12
# Debian/Ubuntu
sudo apt install xtables-addons-common libtext-csv-xs-perl unzip

# Download GeoIP database
sudo mkdir -p /usr/share/xt_geoip
cd /usr/share/xt_geoip
sudo wget -O GeoIP-legacy.csv.gz https://mailfud.org/geoip-legacy/GeoIP-legacy.csv.gz
sudo gunzip GeoIP-legacy.csv.gz
sudo /usr/lib/xtables-addons/xt_geoip_build -D /usr/share/xt_geoip GeoIP-legacy.csv

# Block all countries except allowed ones
sudo iptables -A INPUT -p tcp --dport 2222 -m geoip ! --src-cc US,CA,GB -j DROP

OpenSSH Certificate Authority for enhanced security

Instead of managing individual authorized_keys files, use SSH certificates:

1
2
3
4
5
6
7
8
9
10
# On the CA machine
cd ~/ssh-ca
ssh-keygen -t ed25519 -f ca_key -C "SSH CA Key"

# Sign user's public key
ssh-keygen -s ca_key -I "user_identifier" -n "username" -V +52w id_ed25519.pub

# On SSH servers, configure trust for the CA
sudo cp ca_key.pub /etc/ssh/ca.pub
sudo nano /etc/ssh/sshd_config

Add:

1
TrustedUserCAKeys /etc/ssh/ca.pub

This simplifies key management in large environments.

11. Troubleshooting

Common SSH Problems and Solutions

Connection refused

1
2
3
4
5
6
7
8
9
10
11
12
# Check if SSH is running
sudo systemctl status sshd

# Check firewall status
sudo ufw status
sudo firewall-cmd --list-all

# Check network connectivity
nc -vz server_ip 2222

# Check for TCP wrappers denial
sudo grep sshd /var/log/secure

Authentication failures

1
2
3
4
5
6
7
8
9
10
# Verify file permissions
ls -la ~/.ssh
ls -la ~/.ssh/authorized_keys

# Check SSH server logs
sudo tail -f /var/log/auth.log  # Debian/Ubuntu
sudo tail -f /var/log/secure    # RHEL/CentOS

# Debug SSH connection
ssh -v user@server_ip  # Add more v's for more verbosity (-vv or -vvv)

SSH keys not working

1
2
3
4
5
6
7
8
9
# Verify key permissions
chmod 700 ~/.ssh
chmod 600 ~/.ssh/private_key

# Ensure server configuration allows key authentication
grep PubkeyAuthentication /etc/ssh/sshd_config

# Test with explicit key
ssh -i ~/.ssh/private_key user@server_ip

Log Analysis for SSH Issues

Use these commands to analyze SSH logs:

1
2
3
4
5
6
7
8
9
10
11
# Find failed login attempts
grep "Failed password" /var/log/auth.log

# Check for successful logins
grep "Accepted" /var/log/auth.log

# Look for unusual authentication attempts
grep "Invalid user" /var/log/auth.log

# Check for connection issues
grep "Connection closed" /var/log/auth.log

12. Maintenance Best Practices

Regular updates

1
2
3
4
5
6
# Debian/Ubuntu
sudo apt update
sudo apt upgrade

# RHEL/CentOS
sudo dnf update

Periodic security audits

1
2
# Run SSH audit weekly
sudo crontab -e

Add:

1
0 0 * * 0 ssh-audit localhost > /root/ssh-audit-$(date +\%Y\%m\%d).log

Rotating SSH host keys

1
2
3
4
5
6
7
8
9
10
11
# Backup old keys
sudo cp /etc/ssh/ssh_host_* /root/ssh_backup/

# Remove old keys
sudo rm /etc/ssh/ssh_host_*

# Generate new keys
sudo ssh-keygen -A

# Restart SSH service
sudo systemctl restart sshd

Reviewing users with SSH access

1
2
3
4
5
6
7
8
# Check users with shell access
grep -v '/nologin\|/false' /etc/passwd

# Review sudo users
sudo grep -Po '^sudo.+:\K.*$' /etc/group

# List authorized SSH keys
for dir in /home/*/.ssh/; do echo "$dir"; cat "${dir}authorized_keys"; echo; done

13. Advanced SSH Use Cases

SSH Tunneling

Local port forwarding (access remote services locally)

1
ssh -L 8080:remote_server:80 user@gateway_server

Remote port forwarding (expose local services remotely)

1
ssh -R 8080:localhost:80 user@remote_server

Dynamic port forwarding (SOCKS proxy)

1
ssh -D 1080 user@remote_server

SFTP Configuration for Secure File Transfer

For users who need file transfer only, configure chroot environments:

1
sudo nano /etc/ssh/sshd_config

Add:

1
2
3
4
5
6
7
8
9
10
# SFTP configuration
Subsystem sftp internal-sftp

# Create a new group for SFTP users
Match Group sftp-users
    ChrootDirectory /sftp/%u
    ForceCommand internal-sftp
    AllowTcpForwarding no
    X11Forwarding no
    PermitTunnel no

Create the necessary directories:

1
2
3
sudo groupadd sftp-users
sudo useradd -m -g sftp-users -s /bin/false sftpuser
sudo passwd sftpuser

Set up the chroot environment:

1
2
3
4
5
sudo mkdir -p /sftp/sftpuser/files
sudo chown root:root /sftp/sftpuser
sudo chown sftpuser:sftp-users /sftp/sftpuser/files
sudo chmod 755 /sftp/sftpuser
sudo chmod 775 /sftp/sftpuser/files

SSH as a VPN Alternative with sshuttle

1
2
3
4
5
# Install sshuttle
sudo apt install sshuttle

# Create a simple VPN
sshuttle --dns -r user@remote_server 0.0.0.0/0

14. References and Resources

Configuration guides

Security testing tools

Additional reading

This post is licensed under CC BY 4.0 by the author.