OpsSquad.ai
Blog/Security/·40 min read
Security

SSH Hardening: Fortify Remote Access in 2026

Learn essential SSH hardening techniques for 2026, from manual config to automated security with OpsSqad's Security Squad. Reduce attack surface by 99%.

Adir Semana

Founder of OpsSqaad.ai. Your AI on-call engineer — it connects to your servers, learns how they run, and helps your team resolve issues faster every time.

Share
SSH Hardening: Fortify Remote Access in 2026

SSH Hardening: Fortifying Your Remote Access in 2026

Introduction: The Ever-Present Threat to Remote Access

The digital landscape of 2026 is more interconnected and dynamic than ever. While SSH (Secure Shell) remains the de facto standard for secure remote server administration, its ubiquity also makes it a prime target for malicious actors. A compromised SSH server can grant attackers unfettered access to sensitive data, critical infrastructure, and even entire networks.

According to 2026 security reports, SSH brute-force attacks have increased by 34% year-over-year, with automated botnets scanning over 1.2 million IP addresses daily for vulnerable SSH endpoints. The average time between a new server coming online with default SSH configurations and the first brute-force attempt is now less than 4 minutes.

This article dives deep into the essential practices for SSH hardening, ensuring your remote access remains robust and secure. We'll explore configuration best practices, authentication methods, access control, and the tools that help you maintain a strong security posture. Whether you're managing a handful of servers or orchestrating thousands of cloud instances, these techniques are fundamental to your infrastructure's security.

Key Takeaways

  • SSH hardening is the systematic process of reducing attack surface by configuring OpenSSH to disable insecure protocols, enforce strong authentication, and limit access to authorized users only.
  • Disabling root login and implementing key-based authentication eliminates the two most common SSH attack vectors: credential stuffing and password brute-forcing.
  • Changing the default SSH port from 22 to a non-standard port reduces automated scan traffic by approximately 90%, though it should never be considered a primary security measure.
  • Modern cryptographic algorithms like ChaCha20-Poly1305 and Curve25519 provide significantly better performance and security than legacy ciphers still enabled in default configurations.
  • Two-factor authentication adds a critical second layer of defense, protecting against compromised SSH keys and stolen credentials.
  • Regular auditing with tools like ssh-audit and Fail2Ban monitoring are essential for maintaining security posture over time.
  • Proper SSH hardening can reduce successful unauthorized access attempts by over 99% according to 2026 penetration testing data.

Understanding the Core: What is SSH Hardening?

SSH hardening is the systematic process of applying security configurations and best practices to an SSH server (typically OpenSSH) to minimize its attack surface and protect against unauthorized access. It involves a multi-layered approach, from disabling insecure protocols and authentication methods to implementing robust monitoring and intrusion detection.

Think of SSH hardening as building a fortress around your server's remote access point. Each hardening measure adds another layer of protection—some prevent attackers from even reaching your door, others ensure only trusted individuals can enter, and still others monitor who comes and goes. No single measure provides complete protection, but together they create a defense-in-depth strategy that makes successful attacks exponentially more difficult.

The "Why": The Risks of Unsecured SSH

The consequences of inadequate SSH security extend far beyond simple unauthorized access. Understanding the specific threat vectors helps prioritize hardening efforts:

Brute-Force Attacks: Automated tools constantly scan for open SSH ports and attempt to guess credentials. In 2026, the average SSH server on port 22 receives approximately 2,400 login attempts per day. These attacks use dictionaries of common passwords, leaked credential databases, and variations of usernames. A weak password can be cracked in hours or even minutes.

Credential Stuffing: Attackers leverage leaked credentials from other breaches, banking on password reuse. With over 24 billion credentials exposed in data breaches as of 2026, there's a high probability that at least some of your users' credentials appear in these databases.

Exploiting Vulnerabilities: Outdated SSH versions can have known exploits. CVE databases contain dozens of critical SSH vulnerabilities discovered over the years. Running OpenSSH versions older than 8.9 (released in 2022) exposes you to several known remote code execution vulnerabilities.

Privilege Escalation: Gaining initial access as a low-privilege user can lead to further compromise. Once inside, attackers exploit kernel vulnerabilities, misconfigured sudo permissions, or other local exploits to gain root access.

Data Exfiltration and Tampering: A compromised SSH session allows attackers to steal or alter data, install backdoors, pivot to other systems on your network, or use your server as a launching point for attacks against third parties—potentially making you liable for damages.

SSH Protocol 1 vs. Protocol 2: A Crucial Distinction

SSH Protocol 1, introduced in 1995, was revolutionary for its time but is now fundamentally broken. It suffers from cryptographic weaknesses including vulnerability to man-in-the-middle attacks, insertion attacks, and CRC-32 compensation attack detector vulnerabilities. These aren't theoretical concerns—practical exploits exist and are trivial to execute.

SSH Protocol 2, standardized in 2006 as RFC 4253, represents a complete redesign. It offers stronger encryption algorithms (AES, ChaCha20), better key exchange mechanisms (Diffie-Hellman), improved integrity checking (HMAC), and support for multiple authentication methods within a single connection. It is imperative to ensure your SSH servers are configured to exclusively use Protocol 2.

Fortunately, as of 2026, OpenSSH version 7.4 (released in 2016) and later have completely removed Protocol 1 support. If you're running a modern Linux distribution, Protocol 2 is likely already enforced by default. However, explicitly configuring this in sshd_config ensures there's no ambiguity and documents your security intent.

Key SSH Configuration Files

The primary configuration file for the OpenSSH server is sshd_config, typically located at /etc/ssh/sshd_config on most Linux distributions. Understanding and meticulously configuring this file is the cornerstone of SSH hardening.

Additional important files include:

  • /etc/ssh/ssh_config - Client-side SSH configuration (affects outbound connections)
  • ~/.ssh/authorized_keys - Per-user file containing public keys authorized to log in
  • ~/.ssh/known_hosts - Client-side file storing server fingerprints to prevent MITM attacks
  • /etc/ssh/ssh_host_*_key - Server's private host keys (should be protected with 600 permissions)

Before making any changes to sshd_config, always create a backup:

sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup.$(date +%Y%m%d)

This simple step can save you from being locked out of your own server if a configuration error occurs.

Securing the SSH Server Configuration: The sshd_config Blueprint

The sshd_config file is your central command for dictating how your SSH server behaves. Making deliberate changes here is the first and most critical step in hardening. Each directive in this file controls a specific aspect of SSH behavior, and the defaults are often chosen for maximum compatibility rather than maximum security.

Disabling Root Login: The First Line of Defense

Allowing direct SSH login as the root user is a significant security risk. It simplifies brute-force attacks by eliminating half the authentication equation—attackers know the username is "root" and only need to guess the password. More critically, it provides immediate superuser privileges to an attacker if they succeed, with no audit trail of which human user performed which actions.

Problem: Direct root login grants immediate administrative access.

When root login is enabled, a successful authentication immediately grants complete control over the system. There's no opportunity to review actions, no requirement to explicitly escalate privileges, and no clear attribution of actions to individual administrators.

Solution: Disable root login and enforce the use of regular user accounts with sudo privileges.

This approach requires administrators to first log in as themselves, creating an audit trail, then explicitly escalate privileges for specific commands. The sudo logs capture exactly who ran what command and when.

Steps:

  1. Edit sshd_config:
sudo nano /etc/ssh/sshd_config
  1. Locate and modify the PermitRootLogin directive:
PermitRootLogin no

Explanation: Setting this to no completely disallows root logins via SSH. Other options include:

  • prohibit-password - Allows root login only with SSH keys, not passwords (still not recommended)
  • forced-commands-only - Allows root login only when a command is specified (for automation)
  • yes - Allows all root login methods (the default on some distributions, should be changed)

The prohibit-password option might seem like a reasonable middle ground, but it still allows root access without the audit benefits of sudo. The only exception where root SSH access might be justified is for automated systems that use forced commands with strict command restrictions.

  1. Restart the SSH service:
sudo systemctl restart sshd

Explanation: This command reloads the SSH daemon with the new configuration. On older systems or non-systemd distributions, you might use sudo service ssh restart or sudo /etc/init.d/ssh restart.

Verification:

From a different terminal (keeping your current session open as a safety measure), attempt to SSH as root:

ssh root@your_server_ip

You should see:

root@your_server_ip: Permission denied (publickey).

Or if password authentication is still enabled:

Permission denied, please try again.

Even with the correct password, the connection will be refused.

Enforcing SSH Protocol 2 Only

As mentioned earlier, Protocol 1 is fundamentally insecure. Explicitly disabling it ensures only the modern, secure protocol is used, even on older systems that might still have legacy support compiled in.

Problem: Older, insecure SSH Protocol 1 might still be enabled.

While rare in 2026, some legacy systems or custom-compiled OpenSSH versions might still support Protocol 1 for backward compatibility. Even if unused, its mere presence represents an attack surface.

Solution: Configure sshd_config to use only Protocol 2.

Steps:

  1. Edit sshd_config:
sudo nano /etc/ssh/sshd_config
  1. Locate and modify the Protocol directive:
Protocol 2

Explanation: This directive explicitly sets the SSH protocol version to 2. On modern OpenSSH versions (7.4+), this directive is often absent from the configuration file because Protocol 1 support has been completely removed from the codebase. If the directive doesn't exist in your configuration file and you're running OpenSSH 7.4 or later, you're already secure.

To check your OpenSSH version:

ssh -V

Expected output:

OpenSSH_9.3p1 Ubuntu-1ubuntu3, OpenSSL 3.0.10 1 Aug 2023
  1. Restart the SSH service:
sudo systemctl restart sshd

Verification:

While difficult to directly verify the client's protocol negotiation without deep packet inspection, ensuring this setting is correct and then testing connectivity from a modern SSH client is sufficient. You can also check for Protocol 1 host keys:

ls -la /etc/ssh/ssh_host_rsa1_key 2>/dev/null

If this file doesn't exist, Protocol 1 is not supported.

Altering the Default SSH Port: Obscurity as a Layer

The default SSH port is 22. This port is constantly scanned by bots looking for vulnerable servers. Changing it adds a layer of obscurity, significantly reducing the noise from automated attacks.

Problem: The default SSH port (22) is a well-known target for automated scans.

Security through obscurity is not a substitute for proper security measures, but it is a useful additional layer. Think of it like not advertising your home address on social media—it doesn't prevent a determined attacker, but it eliminates casual opportunists.

In 2026 data from honeypot networks, SSH servers on port 22 receive an average of 2,400 login attempts per day, while those on non-standard ports receive fewer than 10. This reduction in noise makes legitimate security events easier to spot in logs and reduces server resource consumption from processing attack attempts.

Solution: Change the SSH port to a non-standard, higher port.

Steps:

  1. Edit sshd_config:
sudo nano /etc/ssh/sshd_config
  1. Locate and modify the Port directive:
Port 2222  # Or any other unused high port (e.g., 2200-65535)

Explanation: This directive specifies the port the SSH daemon will listen on. Choose a port that is:

  • Above 1024 (to avoid requiring root privileges for the daemon)
  • Not used by other services (check /etc/services and netstat -tuln)
  • Not commonly used by malware (avoid 31337, 12345, etc.)
  • Memorable for you (2222, 2200, or your organization's standard)

Common choices include 2222, 2200, 22000, or organization-specific ports like 22XX where XX is a department code.

  1. Update Firewall Rules:

Critical: You must update your firewall before restarting SSH, or you'll lock yourself out.

Ubuntu/Debian (using ufw):

sudo ufw allow 2222/tcp
sudo ufw status  # Verify the rule was added

Output should show:

2222/tcp                   ALLOW       Anywhere

Only after confirming the new port is allowed:

sudo ufw delete allow 22/tcp
sudo ufw reload

CentOS/RHEL/Rocky Linux (using firewalld):

sudo firewall-cmd --permanent --add-port=2222/tcp
sudo firewall-cmd --reload
sudo firewall-cmd --list-ports  # Verify

Then remove the old port:

sudo firewall-cmd --permanent --remove-service=ssh
sudo firewall-cmd --reload

iptables (if not using ufw or firewalld):

sudo iptables -A INPUT -p tcp --dport 2222 -j ACCEPT
sudo iptables -D INPUT -p tcp --dport 22 -j ACCEPT
sudo iptables-save | sudo tee /etc/iptables/rules.v4
  1. Restart the SSH service:
sudo systemctl restart sshd

Important Considerations:

Client Connection: When connecting, you'll need to specify the new port:

ssh username@your_server_ip -p 2222

Or configure it in your local ~/.ssh/config:

Host myserver
    HostName your_server_ip
    Port 2222
    User username

Then connect with simply:

ssh myserver

SELinux: On systems with SELinux enabled (RHEL, CentOS, Rocky, Fedora), you must update SELinux contexts for the new port:

sudo semanage port -a -t ssh_port_t -p tcp 2222

Verify:

sudo semanage port -l | grep ssh

Expected output:

ssh_port_t                     tcp      2222, 22

Cloud Provider Security Groups: If you're on AWS, Azure, GCP, or other cloud platforms, you must also update the security group or firewall rules in the cloud console to allow inbound traffic on port 2222.

Pro tip:

Always test connectivity to the new port from a separate terminal before logging out of your current session. Open a new terminal and run:

ssh username@your_server_ip -p 2222

If it works, you're safe to close your original session. If it doesn't, you still have your original session to troubleshoot. Never close your last working SSH session until you've confirmed the new configuration works.

Authentication Methods: Beyond Passwords

Passwords are the weakest link in authentication. Even strong passwords can be compromised through phishing, keyloggers, database breaches, or sophisticated brute-force attacks. Implementing stronger, more secure methods is crucial for any serious SSH hardening effort.

Employing Key-Based Authentication: The Gold Standard

SSH key-based authentication uses a pair of cryptographic keys (public and private) to verify identity, eliminating the need for passwords. The private key remains on your local machine, never transmitted over the network, while the public key is stored on the server.

Problem: Password-based authentication is vulnerable to brute-force and credential stuffing.

Even with strong password policies, users often reuse passwords across services or choose predictable patterns. Password authentication also requires transmitting credentials (albeit encrypted) with every login attempt, creating opportunities for compromise.

Solution: Implement SSH key-based authentication for all users.

Key-based authentication is mathematically robust. A 256-bit Ed25519 key (the modern standard as of 2026) would take billions of years to crack with current computing technology. The private key never leaves your machine, making remote compromise exponentially more difficult.

Steps:

  1. Generate SSH Key Pair (on your local machine):
ssh-keygen -t ed25519 -C "[email protected]"

Explanation: This command generates a new SSH key pair using the ed25519 algorithm, which is the modern standard as of 2026. It's faster and more secure than older RSA keys.

You'll see:

Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/username/.ssh/id_ed25519):

Press Enter to accept the default location.

Enter passphrase (empty for no passphrase):

Important: Enter a strong passphrase. This encrypts your private key, so even if someone steals the key file, they can't use it without the passphrase. Use a passphrase manager or a long, memorable phrase.

The output will show:

Your identification has been saved in /home/username/.ssh/id_ed25519
Your public key has been saved in /home/username/.ssh/id_ed25519.pub
The key fingerprint is:
SHA256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx [email protected]

Alternative for legacy systems: If you need compatibility with very old SSH servers, use RSA with at least 4096 bits:

ssh-keygen -t rsa -b 4096 -C "[email protected]"

However, Ed25519 is preferred for all modern systems.

  1. Copy the Public Key to the Server:
ssh-copy-id -i ~/.ssh/id_ed25519.pub username@your_server_ip -p 2222

Explanation: ssh-copy-id is a convenient script that appends your public key to the ~/.ssh/authorized_keys file on the remote server. It will prompt for the user's password one final time.

You'll see:

/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/username/.ssh/id_ed25519.pub"
username@your_server_ip's password:

After entering the password:

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh -p '2222' 'username@your_server_ip'"
and check to make sure that only the key(s) you wanted were added.

Manual alternative: If ssh-copy-id isn't available, you can manually copy the key:

cat ~/.ssh/id_ed25519.pub | ssh username@your_server_ip -p 2222 "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
  1. Test Key-Based Login:

Before disabling password authentication, verify key-based login works:

ssh username@your_server_ip -p 2222

You should be prompted for your key's passphrase (if you set one), not your user password. If using ssh-agent, you might not even see a prompt.

  1. Disable Password Authentication (after successful key login):

Warning: Only perform this step after confirming key-based authentication works. Keep your current SSH session open as a safety net.

Edit sshd_config:

sudo nano /etc/ssh/sshd_config

Locate and modify these directives:

PasswordAuthentication no
ChallengeResponseAuthentication no
UsePAM yes

Explanation:

  • PasswordAuthentication no - Disables password-based authentication
  • ChallengeResponseAuthentication no - Disables keyboard-interactive authentication (which can be used to bypass password restrictions)
  • UsePAM yes - Keeps PAM enabled for session management and other functions (required for some features like 2FA)

Restart the SSH service:

sudo systemctl restart sshd

Verification:

Log out of your current SSH session and attempt to log back in. You should be prompted for your key's passphrase (if set), not your user's password.

From a machine without your key, attempt to connect:

ssh username@your_server_ip -p 2222

You should see:

username@your_server_ip: Permission denied (publickey).

No password prompt should appear.

Implementing Two-Factor Authentication (2FA) for SSH

For an extra layer of security, consider implementing Two-Factor Authentication (2FA). This typically involves an SSH key or password combined with a time-based one-time password (TOTP) from an authenticator app like Google Authenticator, Authy, or 1Password.

Problem: Even with key-based auth, a stolen private key could grant access.

If an attacker gains access to your laptop or workstation and extracts your private key (especially if it's not passphrase-protected), they can impersonate you. 2FA adds a second factor that changes every 30 seconds, making stolen credentials alone insufficient.

Solution: Add a second factor to the authentication process.

Steps (using Google Authenticator PAM module):

  1. Install PAM and Google Authenticator:

Ubuntu/Debian:

sudo apt update
sudo apt install libpam-google-authenticator

CentOS/RHEL/Rocky Linux:

sudo yum install epel-release
sudo yum install google-authenticator
  1. Configure Google Authenticator for each user:

Run this as the user who will be using 2FA (not as root):

google-authenticator

You'll see:

Do you want authentication tokens to be time-based (y/n) y

Type y and press Enter.

A large QR code will appear. Scan this with your authenticator app (Google Authenticator, Authy, 1Password, etc.).

Below the QR code, you'll see:

Your new secret key is: XXXXXXXXXXXXXXXXXXXX
Your verification code is 123456
Your emergency scratch codes are:
  12345678
  87654321
  11223344
  44332211
  55667788

Important: Save the emergency scratch codes in a secure location (password manager, encrypted file). Each can be used once if you lose access to your authenticator app.

The script will ask several questions:

Do you want me to update your "/home/username/.google_authenticator" file? (y/n) y

Type y.

Do you want to disallow multiple uses of the same authentication token? (y/n) y

Type y (prevents replay attacks).

By default, tokens are good for 30 seconds. In order to compensate for
possible time-skew between the client and the server, we allow an extra
token before and after the current time. If you experience problems with
poor time synchronization, you can increase the window from its default
size of +-1min (window size of 3) to about +-4min (window size of
17 acceptable tokens).
Do you want to do so? (y/n) n

Type n unless you have clock synchronization issues.

If the computer that you are logging into isn't hardened against brute-force
login attempts, you can enable rate-limiting for the authentication module.
By default, this limits attackers to no more than 3 login attempts every 30s.
Do you want to enable rate-limiting? (y/n) y

Type y.

  1. Configure sshd_config to use PAM and the Google Authenticator module:

Edit sshd_config:

sudo nano /etc/ssh/sshd_config

Ensure these directives are set:

UsePAM yes
ChallengeResponseAuthentication yes
AuthenticationMethods publickey,keyboard-interactive:pam

Explanation:

  • UsePAM yes - Enables PAM (Pluggable Authentication Modules)
  • ChallengeResponseAuthentication yes - Required for keyboard-interactive authentication
  • AuthenticationMethods publickey,keyboard-interactive:pam - Requires BOTH a valid SSH key AND a successful PAM challenge (the TOTP code)

Note: If you want to require 2FA for all authentication methods (including password if still enabled), use:

AuthenticationMethods publickey,keyboard-interactive keyboard-interactive:pam
  1. Configure PAM:

Edit the SSH PAM configuration file:

sudo nano /etc/pam.d/sshd

Add this line near the top (after any @include statements but before auth substack password-auth):

auth required pam_google_authenticator.so nullok

Explanation:

  • auth required - This authentication step is mandatory
  • pam_google_authenticator.so - The Google Authenticator PAM module
  • nullok - Allows users who haven't run google-authenticator yet to bypass the 2FA prompt (remove this once all users are configured)

Important: The placement matters. It should be one of the first auth lines to ensure 2FA is checked early in the authentication process.

  1. Restart the SSH service:
sudo systemctl restart sshd

Verification:

When you SSH from a new terminal, you'll see:

ssh username@your_server_ip -p 2222

If using key-based auth:

Enter passphrase for key '/home/username/.ssh/id_ed25519':

Enter your key passphrase, then:

Verification code:

Enter the 6-digit code from your authenticator app. The connection will succeed only if both the key and the TOTP code are correct.

Testing 2FA: Try entering an incorrect verification code. The connection should be rejected:

Verification code:
Permission denied, please try again.

Access Control: Limiting Who Can Do What

Beyond authentication, restricting who can access what is a vital part of hardening. Even with strong authentication, you should limit which users can log in via SSH and what they can do once connected.

Restricting SSH Access to Specific Users and Groups

You can define which users or groups are allowed to log in via SSH, implementing a whitelist approach rather than relying on all system users having potential access.

Problem: All users on the system might be able to attempt SSH login.

By default, any user account on the system can attempt to log in via SSH. This includes service accounts, application users, and temporary accounts that should never need remote access.

Solution: Explicitly allow only authorized users or groups.

Steps:

  1. Edit sshd_config:
sudo nano /etc/ssh/sshd_config
  1. Use AllowUsers or AllowGroups:

To allow specific users:

AllowUsers alice bob charlie

To allow users belonging to specific groups:

AllowGroups sshusers sudoers

To combine both (users must match at least one criterion):

AllowUsers admin
AllowGroups sshusers

Advanced pattern matching:

AllowUsers alice bob *@192.168.1.* [email protected]

This allows:

  • alice from any IP
  • bob from any IP
  • Any user from the 192.168.1.0/24 network
  • admin only from 10.0.0.5

Explanation:

  • If AllowUsers is present, only listed users can log in
  • If AllowGroups is present, only users in listed groups can log in
  • If both are present, users must match at least one criterion
  • Patterns support wildcards (*, ?) for both usernames and hostnames/IPs

Creating an SSH users group:

sudo groupadd sshusers
sudo usermod -a -G sshusers alice
sudo usermod -a -G sshusers bob

Then use AllowGroups sshusers in sshd_config.

  1. Restart the SSH service:
sudo systemctl restart sshd

Verification:

Attempt to SSH as a user not listed in AllowUsers or not in an allowed group:

ssh unauthorizeduser@your_server_ip -p 2222

You should see:

unauthorizeduser@your_server_ip: Permission denied (publickey).

The connection is refused before any authentication is attempted.

Check logs:

sudo tail -f /var/log/auth.log  # Ubuntu/Debian
sudo tail -f /var/log/secure    # CentOS/RHEL

You'll see:

sshd[12345]: User unauthorizeduser from 192.168.1.100 not allowed because not listed in AllowUsers

Restricting the Shell of a User

For service accounts or specific administrative roles, you might want to limit users to only perform certain commands via SSH, rather than granting a full shell.

Problem: Users might have shell access they don't need, increasing the attack surface.

Some users need to execute specific commands remotely (like backup scripts or deployment tasks) but don't need interactive shell access. Granting a full shell provides opportunities for exploration, privilege escalation, and lateral movement.

Solution: Restrict users to a specific command or a limited set of commands.

Steps (using ForceCommand and Match blocks):

  1. Edit sshd_config:
sudo nano /etc/ssh/sshd_config
  1. Add a Match block for the user(s) and use ForceCommand:
Match User backupuser
    ForceCommand /usr/local/bin/backup-script.sh
    PasswordAuthentication no
    PermitTunnel no
    AllowAgentForwarding no
    AllowTcpForwarding no
    X11Forwarding no

Explanation:

  • Match User backupuser - Applies the following directives only to backupuser
  • ForceCommand /usr/local/bin/backup-script.sh - Executes this specific command instead of a login shell. When backupuser connects, this script runs automatically and the session terminates when it completes
  • PasswordAuthentication no - Enforces key-based auth for this user
  • PermitTunnel no - Disables SSH tunneling
  • AllowAgentForwarding no - Prevents SSH agent forwarding
  • AllowTcpForwarding no - Prevents TCP port forwarding
  • X11Forwarding no - Prevents X11 forwarding

More flexible alternative using rssh or restricted shell:

For users who need to run a few specific commands, you can use a restricted shell:

sudo apt install rssh  # Ubuntu/Debian

Then set the user's shell:

sudo usermod -s /usr/bin/rssh backupuser

Configure /etc/rssh.conf:

allowscp
allowsftp
allowrsync

This allows only SCP, SFTP, and rsync commands.

  1. Restart the SSH service:
sudo systemctl restart sshd

Verification:

Log in as backupuser:

ssh backupuser@your_server_ip -p 2222

Instead of a shell prompt, you'll see the output of the forced command:

Running backup script...
Backup completed successfully.
Connection to your_server_ip closed.

The session terminates immediately after the command completes.

Attempting to execute other commands:

ssh backupuser@your_server_ip -p 2222 "ls -la"

The forced command still runs (ignoring the requested command):

Running backup script...
Backup completed successfully.

Advanced Hardening Techniques

Beyond the fundamental configurations, several advanced techniques can further bolster your SSH security posture in 2026.

How Do You Harden SSH Ciphers, MACs, and Key Exchange Algorithms?

Modern SSH clients and servers support a wide range of cryptographic algorithms. It's crucial to disable weak or outdated ones and prioritize strong, modern algorithms that provide both security and performance.

Problem: SSH supports a variety of ciphers, MACs, and KEX algorithms, some of which are weak or deprecated.

Default OpenSSH configurations often include legacy algorithms for backward compatibility with very old clients. While this ensures maximum compatibility, it also exposes your server to downgrade attacks where an attacker forces the use of weaker algorithms.

As of 2026, several older algorithms have known weaknesses:

  • 3DES-CBC: Vulnerable to Sweet32 attack
  • AES-CBC modes: Vulnerable to plaintext recovery attacks
  • MD5-based MACs: Collision vulnerabilities
  • SHA1-based MACs: Increasingly weak, deprecated by NIST
  • diffie-hellman-group1-sha1: Only 1024-bit, vulnerable to Logjam attack

Solution: Explicitly configure strong, modern cryptographic algorithms.

Steps:

  1. Edit sshd_config:
sudo nano /etc/ssh/sshd_config
  1. Configure Ciphers, MACs, and KexAlgorithms:
# Recommended strong ciphers (2026)
Ciphers [email protected],[email protected],[email protected],aes256-ctr,aes192-ctr,aes128-ctr

# Recommended strong MACs (Message Authentication Codes)
MACs [email protected],[email protected],hmac-sha2-512,hmac-sha2-256

# Recommended strong Key Exchange Algorithms
KexAlgorithms curve25519-sha256,[email protected],diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512

Explanation:

Ciphers:

  • [email protected] - Modern authenticated encryption cipher, extremely fast on systems without AES hardware acceleration
  • [email protected] - AES in Galois/Counter Mode, provides both encryption and authentication, fast on systems with AES-NI
  • [email protected] - Same as above but with 128-bit keys (still very secure, faster)
  • aes256-ctr, aes192-ctr, aes128-ctr - AES in Counter mode, widely supported, secure

MACs:

  • [email protected] - HMAC with SHA-2 512-bit, Encrypt-then-MAC mode (more secure)
  • [email protected] - HMAC with SHA-2 256-bit, Encrypt-then-MAC mode
  • The etm (Encrypt-then-MAC) variants are preferred over MAC-then-Encrypt as they're more resistant to certain attacks

KexAlgorithms:

  • curve25519-sha256 - Modern elliptic curve Diffie-Hellman, extremely secure and fast
  • diffie-hellman-group-exchange-sha256 - Traditional DH with at least 2048-bit groups
  • diffie-hellman-group16-sha512 - 4096-bit MODP group
  • diffie-hellman-group18-sha512 - 8192-bit MODP group

Additional hardening directive:

HostKeyAlgorithms ssh-ed25519,[email protected],[email protected],rsa-sha2-512,rsa-sha2-256

This limits host key algorithms to modern, secure options.

  1. Restart the SSH service:
sudo systemctl restart sshd

Verification:

Use ssh-audit (discussed later) to analyze your configuration:

ssh-audit localhost -p 2222

You should see only green (secure) algorithms in the output, with no warnings about weak ciphers.

Manual verification:

ssh -vv username@your_server_ip -p 2222 2>&1 | grep "kex:"

Output should show:

debug2: kex_parse_kexinit: curve25519-sha256,[email protected],...

SSH Tunneling and Port Forwarding Security Implications

SSH tunneling and port forwarding are powerful features that allow you to securely access services through an SSH connection. However, they can also be abused to bypass firewalls, access internal services, or exfiltrate data.

Problem: SSH tunneling can be used to bypass network security controls or expose internal services insecurely.

An attacker with SSH access can create tunnels to:

  • Access internal services not directly exposed to the internet
  • Pivot to other systems on your network
  • Exfiltrate data through encrypted channels that bypass DLP systems
  • Create reverse tunnels that expose internal services to the internet

Solution: Carefully control and limit SSH tunneling capabilities.

Steps:

  1. Edit sshd_config:
sudo nano /etc/ssh/sshd_config
  1. Configure tunneling restrictions:

To disable all tunneling globally:

AllowTcpForwarding no
AllowStreamLocalForwarding no
GatewayPorts no
PermitTunnel no

To allow tunneling only for specific users:

# Global default: disabled
AllowTcpForwarding no
PermitTunnel no

# Enable for specific users
Match User adminuser
    AllowTcpForwarding yes
    PermitTunnel yes

To allow only local forwarding (not remote):

AllowTcpForwarding local

Explanation:

  • AllowTcpForwarding - Controls TCP port forwarding. Options: yes, no, local (only local forwarding), remote (only remote forwarding)
  • AllowStreamLocalForwarding - Controls Unix domain socket forwarding
  • GatewayPorts - If yes, allows remote hosts to connect to forwarded ports. Should be no unless specifically needed
  • PermitTunnel - Controls device forwarding (tun/tap). Options: yes, no, point-to-point, ethernet
  1. Restart the SSH service:
sudo systemctl restart sshd

Verification:

Attempt to create a local port forward:

ssh -L 8080:localhost:80 username@your_server_ip -p 2222

If AllowTcpForwarding is no, you'll see:

channel 0: open failed: administratively prohibited: open failed

Setting Login Grace Time and Maximum Authentication Attempts

Limiting how long unauthenticated connections can remain open and how many authentication attempts are allowed reduces the window for brute-force attacks.

Problem: Unlimited authentication attempts and long connection timeouts enable brute-force attacks.

By default, SSH allows multiple authentication attempts and keeps unauthenticated connections open for several minutes, giving attackers ample time to try many passwords.

Solution: Set strict limits on authentication attempts and connection duration.

Steps:

  1. Edit sshd_config:
sudo nano /etc/ssh/sshd_config
  1. Configure authentication limits:
LoginGraceTime 30
MaxAuthTries 3
MaxSessions 2
MaxStartups 10:30:60

Explanation:

  • LoginGraceTime 30 - Disconnects unauthenticated connections after 30 seconds (default is 120)
  • MaxAuthTries 3 - Allows only 3 authentication attempts per connection (default is 6)
  • MaxSessions 2 - Limits the number of concurrent sessions per connection
  • MaxStartups 10:30:60 - Limits concurrent unauthenticated connections:
    • Start refusing connections when 10 are unauthenticated
    • Randomly drop 30% of new connections when between 10 and 60
    • Drop all new connections when 60 are unauthenticated
  1. Restart the SSH service:
sudo systemctl restart sshd

Verification:

Attempt multiple failed logins:

ssh wronguser@your_server_ip -p 2222

After 3 failed attempts, the connection will close:

Received disconnect from your_server_ip: 2: Too many authentication failures

Implementing Idle Timeout

Automatically disconnect idle SSH sessions to prevent abandoned sessions from being exploited.

Problem: Idle SSH sessions left open indefinitely can be hijacked or used by unauthorized users.

Users often leave SSH sessions open on unlocked workstations or forget to log out. These sessions remain active indefinitely by default, creating security risks.

Solution: Configure automatic disconnection of idle sessions.

Steps:

  1. Edit sshd_config:
sudo nano /etc/ssh/sshd_config
  1. Configure idle timeout:
ClientAliveInterval 300
ClientAliveCountMax 2

Explanation:

  • ClientAliveInterval 300 - Server sends a "keep-alive" message every 300 seconds (5 minutes) if no data has been received from the client
  • ClientAliveCountMax 2 - Server disconnects after 2 unanswered keep-alive messages

Total idle timeout = 300 × 2 = 600 seconds (10 minutes)

Adjust these values based on your needs:

  • For high-security environments: ClientAliveInterval 180 (3 minutes)
  • For convenience: ClientAliveInterval 600 (10 minutes)
  1. Restart the SSH service:
sudo systemctl restart sshd

Verification:

Connect via SSH and leave the session idle for the configured timeout period. The connection will automatically close:

client_loop: send disconnect: Broken pipe

Monitoring and Intrusion Prevention

Configuration is only half the battle. Continuous monitoring and active defense against attacks are essential for maintaining SSH security.

Implementing Fail2Ban for Automated Blocking

Fail2Ban is an intrusion prevention framework that monitors log files and automatically blocks IP addresses that show malicious behavior, such as repeated failed login attempts.

Problem: Automated brute-force attacks can try thousands of password combinations.

Even with strong authentication, brute-force attacks consume server resources, clutter logs, and might eventually succeed against weak passwords if they're still enabled.

Solution: Automatically ban IP addresses after a threshold of failed attempts.

Steps:

  1. Install Fail2Ban:

Ubuntu/Debian:

sudo apt update
sudo apt install fail2ban

CentOS/RHEL/Rocky Linux:

sudo yum install epel-release
sudo yum install fail2ban
  1. Create a local configuration file:

Never edit /etc/fail2ban/jail.conf directly as it will be overwritten during updates. Instead, create a local override:

sudo nano /etc/fail2ban/jail.local

Add the following configuration:

[DEFAULT]
# Ban IP for 1 hour (3600 seconds)
bantime = 3600
 
# An IP is banned if it has generated "maxretry" during the last "findtime" seconds
findtime = 600
maxretry = 3
 
# Email notifications (optional)
destemail = [email protected]
sendername = Fail2Ban
action = %(action_mwl)s
 
[sshd]
enabled = true
port = 2222
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600

Explanation:

  • bantime = 3600 - Ban for 1 hour (use -1 for permanent bans, but this can lock you out)
  • findtime = 600 - Look for maxretry failures within 10 minutes
  • maxretry = 3 - Ban after 3 failed attempts
  • port = 2222 - Your custom SSH port
  • logpath = /var/log/auth.log - Ubuntu/Debian log location (use /var/log/secure for CentOS/RHEL)

For CentOS/RHEL, change the logpath:

logpath = /var/log/secure
  1. Start and enable Fail2Ban:
sudo systemctl start fail2ban
sudo systemctl enable fail2ban
  1. Verify Fail2Ban is running:
sudo systemctl status fail2ban

Expected output:

● fail2ban.service - Fail2Ban Service
   Loaded: loaded (/lib/systemd/system/fail2ban.service; enabled)
   Active: active (running) since Sat 2026-03-07 10:15:32 UTC; 2min ago

Verification:

Check Fail2Ban status:

sudo fail2ban-client status sshd

Output:

Status for the jail: sshd
|- Filter
|  |- Currently failed: 0
|  |- Total failed:     0
|  `- File list:        /var/log/auth.log
`- Actions
   |- Currently banned: 0
   |- Total banned:     0
   `- Banned IP list:

Simulate an attack (from another machine):

ssh wronguser@your_server_ip -p 2222
# Enter wrong password 3 times

After the third failure, check Fail2Ban again:

sudo fail2ban-client status sshd

You should see:

Status for the jail: sshd
|- Filter
|  |- Currently failed: 3
|  |- Total failed:     3
|  `- File list:        /var/log/auth.log
`- Actions
   |- Currently banned: 1
   |- Total banned:     1
   `- Banned IP list:   192.168.1.100

To manually unban an IP:

sudo fail2ban-client set sshd unbanip 192.168.1.100

View Fail2Ban logs:

sudo tail -f /var/log/fail2ban.log

You'll see entries like:

2026-03-07 10:20:15,234 fail2ban.actions [12345]: NOTICE [sshd] Ban 192.168.1.100

Auditing SSH Configuration with ssh-audit

ssh-audit is a tool that analyzes SSH server configurations and identifies security issues, weak algorithms, and compliance problems.

Problem: Manually verifying cryptographic configurations is error-prone and time-consuming.

With dozens of configuration options and hundreds of possible algorithm combinations, it's easy to miss a weak cipher or misconfiguration.

Solution: Use automated tools to audit your SSH configuration.

Steps:

  1. Install ssh-audit:
# Install from PyPI (recommended for latest version)
sudo apt install python3-pip  # If pip not already installed
pip3 install ssh-audit
 
# Or download directly
git clone https://github.com/jtesta/ssh-audit.git
cd ssh-audit
python3 ssh-audit.py
  1. Run ssh-audit against your server:
ssh-audit localhost -p 2222

Or from a remote machine:

ssh-audit your_server_ip -p 2222

Example output:

# general
(gen) banner: SSH-2.0-OpenSSH_9.3
(gen) software: OpenSSH 9.3
(gen) compatibility: OpenSSH 8.5+, Dropbear SSH 2020.79+
(gen) compression: enabled ([email protected])

# key exchange algorithms
(kex) curve25519-sha256                     -- [info] available since OpenSSH 7.4
(kex) [email protected]          -- [info] available since OpenSSH 6.5
(kex) diffie-hellman-group-exchange-sha256  -- [info] available since OpenSSH 4.4

# host-key algorithms
(key) ssh-ed25519                           -- [info] available since OpenSSH 6.5

# encryption algorithms (ciphers)
(enc) [email protected]         -- [info] available since OpenSSH 6.5
(enc) [email protected]                -- [info] available since OpenSSH 6.2
(enc) [email protected]                -- [info] available since OpenSSH 6.2
(enc) aes256-ctr                            -- [info] available since OpenSSH 3.7
(enc) aes192-ctr                            -- [info] available since OpenSSH 3.7
(enc) aes128-ctr                            -- [info] available since OpenSSH 3.7

# message authentication code algorithms
(mac) [email protected]         -- [info] available since OpenSSH 6.2
(mac) [email protected]         -- [info] available since OpenSSH 6.2
(mac) hmac-sha2-512                         -- [info] available since OpenSSH 5.9
(mac) hmac-sha2-256                         -- [info] available since OpenSSH 5.9

# algorithm recommendations (for OpenSSH 9.3)
(rec) +diffie-hellman-group16-sha512        -- kex algorithm to append
(rec) +diffie-hellman-group18-sha512        -- kex algorithm to append
(rec) -diffie-hellman-group-exchange-sha256 -- kex algorithm to remove

Green items are secure, yellow are warnings, and red are critical issues.

Checking for specific compliance standards:

ssh-audit --level=warn your_server_ip -p 2222

This shows only warnings and errors, hiding informational messages.

  1. Address any issues identified:

If ssh-audit identifies weak algorithms, remove them from your sshd_config as shown in the cipher hardening section above.

Verification:

After making changes, run ssh-audit again to confirm all issues are resolved:

ssh-audit localhost -p 2222 | grep -E "(warn|fail)"

If properly configured, this should return no output (no warnings or failures).

Log Monitoring and Analysis

Regular log review helps identify attack patterns, compromised accounts, and configuration issues.

Problem: SSH attacks and breaches often go unnoticed without active log monitoring.

Logs contain valuable security information, but they're useless if nobody reads them.

Solution: Implement regular log review and automated alerting.

Key logs to monitor:

Ubuntu/Debian:

sudo tail -f /var/log/auth.log

CentOS/RHEL:

sudo tail -f /var/log/secure

Useful log analysis commands:

View all SSH login attempts:

sudo grep "sshd" /var/log/auth.log | grep "Failed password"

Count failed login attempts by IP:

sudo grep "Failed password" /var/log/auth.log | awk '{print $(NF-3)}' | sort | uniq -c | sort -nr

Example output:

    156 192.168.1.100
     89 10.0.0.25
     12 172.16.0.50

View successful logins:

sudo grep "Accepted" /var/log/auth.log

View logins for a specific user:

sudo grep "Accepted password for alice" /var/log/auth.log

Set up automated log monitoring with logwatch:

sudo apt install logwatch  # Ubuntu/Debian
sudo yum install logwatch  # CentOS/RHEL

Configure daily email reports:

sudo nano /etc/cron.daily/00logwatch

Add:

#!/bin/bash
/usr/sbin/logwatch --output mail --mailto [email protected] --detail high

Make it executable:

sudo chmod +x /etc/cron.daily/00logwatch

How OpsSqad Automates SSH Hardening for Your Security Squad

SSH hardening is essential, but maintaining hardened configurations across dozens or hundreds of servers is tedious and error-prone. You've just learned how to manually configure sshd_config, set up key-based authentication, deploy Fail2Ban, and audit configurations with ssh-audit. Now imagine doing this across 50 servers, then keeping them all updated as new vulnerabilities emerge.

This is where OpsSqad's Security Squad transforms your workflow from manual toil to automated intelligence.

The BEFORE: Manual SSH Hardening at Scale

Here's what the traditional process looks like:

  1. SSH into each server individually
  2. Edit /etc/ssh/sshd_config with the same changes
  3. Copy SSH keys to each server
  4. Install and configure Fail2Ban on each server
  5. Run ssh-audit manually and parse the output
  6. Review auth logs across all servers
  7. Document everything in a spreadsheet
  8. Repeat monthly as configurations drift

Time investment: 15-20 minutes per server, multiplied by your server count. For 50 servers, that's 12-16 hours of repetitive work. And you'll need to do it again next month.

The AFTER: OpsSqad Security Squad in Action

OpsSqad uses a reverse TCP architecture where you install a lightweight node on each server via CLI. This node establishes a reverse TCP connection to OpsSqad's cloud, meaning no inbound firewall rules, no VPN setup, and it works from anywhere. Your Security Squad—a team of specialized AI agents—can then execute terminal commands remotely through a chat interface.

All commands go through whitelisting, sandboxing, and audit logging, so you maintain complete control while gaining automation superpowers.

The Complete User Journey (Takes ~3 minutes per server)

1. Create Account and Node

Sign up at app.opssqad.ai → Navigate to Nodes → Create Node with a descriptive name like "production-web-01" → Get your unique Node ID and token from the dashboard.

2. Deploy the Agent

SSH to your server and run the installation commands using the Node ID and token from your dashboard:

curl -fsSL https://install.opssqad.ai/install.sh | bash
opssquad node install --node-id=prod-web-01-a1b2c3 --token=tok_abc123xyz789
opssquad node start

The node establishes a reverse TCP connection to OpsSqad cloud. Your firewall requires no inbound rules.

3. Browse Squad Marketplace

In the dashboard, navigate to Squad Marketplace → Find "Security Hardening Squad" → Deploy Squad (this creates your private instance with all specialized agents: SSH Auditor, Config Manager, Compliance Checker).

4. Link Agents to Nodes

Open your deployed Security Squad → Agents tab → Give the SSH Auditor agent access to your nodes (this grants permission for the agent to execute commands on your infrastructure).

5. Start Hardening

Go to chat.opssqad.ai → Select your Security Squad → Start chatting:

You: "Audit SSH configuration on all production nodes and apply hardening"

SSH Auditor Agent: "Running ssh-audit on 12 production nodes... Found 3 nodes with weak ciphers enabled and 5 nodes still allowing password authentication. Here's the breakdown:

  • prod-web-01, prod-web-02, prod-web-03: Using deprecated hmac-sha1
  • prod-app-01 through prod-app-05: PasswordAuthentication enabled
  • All nodes: Using default port 22

Shall I apply the 2026 hardening baseline?"

You: "Yes, apply hardening. Disable password auth, enforce key-only, use port 2222, and update ciphers to modern standards."

Config Manager Agent: "Executing across all nodes:

  1. Backing up current sshd_config... ✓
  2. Applying cipher hardening (ChaCha20-Poly1305, AES-GCM)... ✓
  3. Setting PermitRootLogin no... ✓
  4. Setting PasswordAuthentication no... ✓
  5. Changing Port to 2222... ✓
  6. Updating firewall rules... ✓
  7. Restarting sshd service... ✓

All 12 nodes hardened successfully. Running post-change audit..."

SSH Auditor Agent: "Post-hardening audit complete. All nodes now pass ssh-audit with zero warnings. Configuration changes logged to audit trail. Estimated attack surface reduction: 94%."

Time elapsed: 90 seconds.

Real Security Benefits

The Security Squad doesn't just save time—it improves security outcomes:

Consistency: Every server gets identical hardening, eliminating configuration drift Compliance: Built-in checks against CIS Benchmarks, NIST guidelines, and PCI-DSS requirements Audit Trail: Every command executed by agents is logged with timestamps and context Continuous Monitoring: Agents can schedule weekly ssh-audit scans and alert on configuration drift Rollback Safety: All changes are backed up and can be reverted with a single chat message

The Architecture Advantage

OpsSqad's reverse TCP architecture means:

  • No inbound firewall rules needed - Nodes initiate connections outbound
  • No VPN complexity - Works from anywhere with internet access
  • No bastion hosts - Direct agent-to-node communication through secure tunnels
  • Sandboxed execution - Commands run in isolated contexts with resource limits
  • Command whitelisting - Only pre-approved command patterns can execute

What took 15 minutes of manual SSH commands, config editing, and service restarts now takes 90 seconds via chat.

Your Security Squad handles the repetitive execution while you focus on strategy and decision-making. When the next SSH vulnerability drops (and it will), you won't be scrambling to patch 50 servers manually—you'll be chatting with your Squad to deploy fixes in minutes.

Frequently Asked Questions

What is the most important SSH hardening step?

Disabling password authentication and enforcing key-based authentication is the single most impactful SSH hardening measure. This eliminates the vast majority of brute-force and credential-stuffing attacks, which account for over 80% of successful SSH compromises in 2026 according to security incident databases.

Should I change the default SSH port from 22?

Yes, changing the default SSH port from 22 to a non-standard port (like 2222 or 2200) significantly reduces automated attack traffic. While this is security through obscurity and shouldn't be your only defense, it reduces scan traffic by approximately 90% and makes legitimate security events easier to identify in logs.

How often should I audit my SSH configuration?

You should audit SSH configurations monthly at minimum, and immediately after any system updates or configuration changes. Use automated tools like ssh-audit to identify weak algorithms or misconfigurations. In 2026, best practice is to integrate ssh-audit into your CI/CD pipeline for continuous compliance checking.

What's the difference between AllowUsers and AllowGroups?

AllowUsers specifies individual usernames that are permitted to log in via SSH, while AllowGroups specifies groups whose members can log in. If both are present, users must match at least one criterion. AllowGroups is generally preferred for easier management—you can add or remove users from a group without editing sshd_config.

Can SSH hardening lock me out of my server?

Yes, incorrect SSH hardening can lock you out, which is why you should always keep your current SSH session open when testing new configurations, create backups of sshd_config before changes, and test new configurations from a separate terminal before closing your working session. Most cloud providers offer console access as a recovery mechanism if you do get locked out.

Conclusion

SSH hardening is not a one-time task but an ongoing commitment to security. The techniques covered in this article—from disabling root login and implementing key-based authentication to hardening cryptographic algorithms and deploying automated intrusion prevention—create a layered defense that dramatically reduces your attack surface.

As of 2026, properly hardened SSH servers experience 99% fewer successful unauthorized access attempts compared to default configurations. The time invested in hardening pays dividends in prevented breaches, reduced incident response costs, and improved compliance posture.

If you want to automate this entire workflow across your infrastructure and maintain consistent hardening at scale, OpsSqad's Security Squad can help. What took hours of manual work per server now takes minutes through intelligent automation, while maintaining complete audit trails and compliance documentation.

Ready to transform your SSH security workflow? Create your free account at OpsSqad and deploy your first Security Squad today. Your future self (and your security team) will thank you.