Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Harden SSH on Linux Servers

By Kokil Thapa | Last reviewed: September 2026

Every production Linux box I maintain still gets hammered on port 22 within hours of going live. If you harden SSH on Linux servers before that traffic arrives, you cut brute-force noise and real breach risk in one pass. This guide walks through the same baseline I apply on Linux system administration engagements: key-only login, a minimal sshd_config, firewall limits, and fail2ban. It assumes Ubuntu 22.04 or 24.04 LTS, though the steps transfer to Debian and most RHEL-family distros with small path changes.

Why should you harden SSH on Linux servers before anything else?

SSH is the front door to your server. Laravel apps, WordPress sites, and database hosts all share the same risk profile here. An open SSH service with password auth enabled is a magnet for credential stuffing. Bots do not care whether you host a law-firm portal or a WooCommerce shop.

On sister sites I deploy with Deployer 7 and GitLab CI, SSH is how releases land. Lock it down wrong and you break deploys. Lock it down right and you still push code while attackers bounce off. That balance is what this article targets.

Start with a checklist mindset aligned with CIS benchmarks for server hardening:

  • Authentication: keys only, no empty passwords, no root login.
  • Configuration: modern ciphers, sensible timeouts, limited users.
  • Network: firewall allowlists, optional non-default port.
  • Detection: fail2ban or similar, plus log review.
  • Recovery: console access tested before you restart sshd.
SSH Attack Surface on Linux ServersBefore HardeningPort 22 open worldwidePassword auth enabledRoot login allowedNo rate limitingfixAfter HardeningFirewall IP allowlistEd25519 keys onlyDeploy user + sudofail2ban + logging
How to harden SSH on Linux servers: shrink the attack surface from open password login to key-based access with network controls.

If you are building from scratch, read Ubuntu SSH server setup first. Then return here for production-grade tightening. For broader context, see Ubuntu server security best practices and how to secure your website and server in Nepal.

How do you set up SSH key authentication and disable passwords?

Key-based auth is the single highest-impact change when you harden SSH on Linux servers. Passwords leak through reuse, support tickets, and screenshots. Keys do not.

Generate a modern key pair on your workstation

Use Ed25519. It is fast, short, and widely supported on current OpenSSH builds shipped with Ubuntu 22.04 and 24.04.

ssh-keygen -t ed25519 -a 100 -C "deploy@yourcompany" -f ~/.ssh/id_ed25519_prod

Passphrase-protect the private key. Store backups offline. For team deploy keys, use a dedicated user per environment rather than sharing one private key across five developers.

Need a strong passphrase? Use the password generator and store secrets in a manager, not a spreadsheet.

Create a non-root deploy user on the server

sudo adduser deploy
sudo usermod -aG sudo deploy
sudo mkdir -p /home/deploy/.ssh
sudo chmod 700 /home/deploy/.ssh

Copy your public key into /home/deploy/.ssh/authorized_keys. Permissions matter. OpenSSH ignores the file if ownership or mode is wrong.

sudo nano /home/deploy/.ssh/authorized_keys
sudo chown -R deploy:deploy /home/deploy/.ssh
sudo chmod 600 /home/deploy/.ssh/authorized_keys

Details on ownership rules live in Linux file permissions and ACLs explained.

Test key login before you disable passwords

Open a second terminal. Keep your current root session alive. Test from your IP:

ssh -i ~/.ssh/id_ed25519_prod deploy@203.0.113.10

Only after a successful login should you edit sshd_config. I have seen teams lock themselves out by skipping this step on a Friday evening. Do not be that team.

Which sshd_config settings should you change to harden SSH?

The main file is /etc/ssh/sshd_config on Ubuntu and Debian. Always validate syntax before restart. The OpenSSH project documents every directive in the official OpenSSH manual.

Create a drop-in file so package upgrades do not overwrite your edits:

sudo nano /etc/ssh/sshd_config.d/99-hardening.conf

Add these directives:

Port 2222
Protocol 2
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
ChallengeResponseAuthentication no
PubkeyAuthentication yes
AuthenticationAttempts 3
AllowUsers deploy
MaxAuthTries 3
MaxSessions 3
LoginGraceTime 30
ClientAliveInterval 300
ClientAliveCountMax 2
X11Forwarding no
AllowTcpForwarding no
PermitEmptyPasswords no
UsePAM yes
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org

Validate and reload:

sudo sshd -t
sudo systemctl reload ssh

If sshd -t prints an error, fix it before reload. A broken config can drop every active session on restart.

SSH Hardening Workflow on Linux1. Backup2. Keys3. Config4. TestKeep existing root session openTest new SSH session in second terminal5. Reload sshd safely6. Firewall + fail2ban + monitor auth.log
Safe order of operations when you harden SSH on Linux servers: never reload sshd until a parallel key login succeeds.

For AllowUsers, list every account that needs shell access. Omit service accounts unless they truly require SSH. On legal-tech portals and eCommerce hosts I maintain, only deploy and one admin user get in.

Match this work with SSH key auth, fail2ban, and port hardening for a focused companion read. Server hardening for Ubuntu web servers covers the full stack beyond SSH.

Should you change the default SSH port on Linux servers?

Changing the port is security through obscurity. It is not a substitute for keys and firewall rules. It does cut log noise. That alone is worth it on busy hosts.

If you move SSH from 22 to 2222, update three places at once:

  1. Port directive in sshd_config.
  2. UFW or nftables allow rule for the new port.
  3. Your CI deploy config and local ~/.ssh/config.

Example client stanza:

Host prod-law-portal
  HostName 203.0.113.10
  User deploy
  Port 2222
  IdentityFile ~/.ssh/id_ed25519_prod
  IdentitiesOnly yes

On GitLab CI deploy jobs, pass -p 2222 or set the port in Deployer’s host() config. I have fixed more broken pipelines from forgotten port changes than from actual attacks.

ControlSecurity valueOps costVerdict
Disable password authVery highLow once keys workAlways do this
Ed25519 keys with passphraseVery highLowAlways do this
AllowUsers restrictionHighLowStrongly recommended
Firewall IP allowlistVery highMedium if IPs changeBest when IPs are stable
Non-default portLow–mediumMediumNice extra, not core
fail2banMediumLowRecommended on public IPs

Port knocking and VPN-only SSH are valid for high-value systems. For a typical Laravel or WordPress VPS in Nepal, key auth plus UFW from office and CI IPs gets you most of the way there. See set up a WireGuard VPN server if you want SSH reachable only over VPN.

How do you combine firewall rules and fail2ban to protect SSH?

SSH hardening without network controls leaves key brute-force and exploit probes hitting sshd directly. Layer a host firewall first. Add fail2ban second.

UFW example for Ubuntu

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow from 198.51.100.20 to any port 2222 proto tcp comment 'Office IP'
sudo ufw allow from 203.0.113.50 to any port 2222 proto tcp comment 'GitLab CI'
sudo ufw enable
sudo ufw status verbose

Prefer nftables on newer stacks? The same logic applies. Compare approaches in iptables vs nftables Linux firewalls and nftables: the modern Linux firewall.

Install and configure fail2ban

sudo apt update
sudo apt install fail2ban
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local

Minimal SSH jail override:

[sshd]
enabled = true
port = 2222
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
findtime = 600
bantime = 3600
ignoreip = 127.0.0.1/8 198.51.100.20

Restart and verify:

sudo systemctl enable fail2ban
sudo systemctl restart fail2ban
sudo fail2ban-client status sshd

The fail2ban project wiki at fail2ban installation docs covers distro-specific paths. On RHEL-family systems, auth logs may live under journald instead of /var/log/auth.log.

Layered SSH Protection StackInternet traffic hits each layer in orderLayer 1: Cloud / hosting firewallLayer 2: UFW or nftables on hostLayer 3: fail2ban on auth failuresLayer 4: Hardened sshd + key auth
Defense in depth when you harden SSH on Linux servers: network rules, automated bans, then strict sshd settings.

Pair this with monitoring. Linux server monitoring with Netdata and alerts catches unusual auth spikes even when fail2ban handles the noise.

How do you audit and maintain SSH hardening over time?

Hardening is not a one-time task. Package updates rewrite defaults. New developers ask for temporary password auth. CI IP ranges change after a provider migration.

Quick audit commands

sudo sshd -T | grep -E 'permittrootlogin|passwordauthentication|pubkeyauthentication|allowusers|port'
sudo grep "Failed password" /var/log/auth.log | tail -20
sudo grep "Accepted publickey" /var/log/auth.log | tail -20

Run these after every openssh-server upgrade. Ubuntu’s security notices at ubuntu.com/security/notices flag OpenSSH CVEs that may require urgent patching.

Automate compliance checks

Tools like Lynis or OpenSCAP against CIS profiles help on audit-heavy clients. For small teams, a monthly calendar reminder plus the commands above is enough. Document your SSH policy in the same place you keep backup runbooks. Tie backups to Ubuntu server backup strategies so you can recover if lockout happens.

On production Laravel hosts such as Adventure Third Pole Trek and sister legal-tech sites on shared EC2, SSH stays limited to deploy users. Application logs and queue workers never share the same UNIX account as the human admin. That separation limits blast radius if a key ever leaks.

SSH Hardening Decision TreePublic IP server?yesKeys + fail2bannoKeys + internal ACLStable admin IPs?UFW allowlistVPN + SSHDisable password authNever skip key test
Choose SSH hardening controls based on exposure: public Laravel VPS vs internal staging on a private network.

If you tunnel for database admin, read SSH tunneling and port forwarding explained. Disable forwarding in sshd_config unless you actively need it.

For ongoing help after launch, support and maintenance and domain registration and hosting cover the ops side many Nepal teams outsource. Ubuntu server setup guide remains the right starting point for fresh VMs.

Key Takeaways

  • Enforce Ed25519 key authentication and set PasswordAuthentication no before exposing any production host.
  • Keep a live root or sudo session open while testing a new sshd_config in a second terminal.
  • Restrict SSH with AllowUsers, modern ciphers, and PermitRootLogin no in a drop-in under sshd_config.d.
  • Combine UFW or nftables IP allowlists with fail2ban rather than relying on a non-default port alone.
  • Re-audit SSH settings after every OpenSSH package upgrade and CI infrastructure change.
  • Document deploy user keys, ports, and console recovery steps alongside your backup runbooks.

People Also Ask

Is it safe to disable password authentication for SSH?

Yes, once every legitimate admin and CI job uses key auth and you have console or IPMI access as a fallback. Disabling passwords removes the main target for brute-force bots. The risk is lockout if keys are missing or permissions on authorized_keys are wrong. Test before you reload sshd.

What is the best SSH key type for Linux servers in 2026?

Ed25519 is the default choice for new keys. It is shorter and faster than RSA while meeting current OpenSSH security guidance. Use RSA 4096 only when you must support legacy clients that lack Ed25519. Always passphrase-protect private keys on workstations.

Does changing the SSH port stop hackers?

It reduces automated scan volume but does not stop targeted attacks. Anyone can port-scan your host. Treat port changes as a log-noise filter. Pair them with key-only auth, firewall allowlists, and fail2ban for real protection.

How often should you review SSH hardening settings?

Review after every OpenSSH security update, when staff or contractors change, and when CI provider IP ranges move. A monthly quick audit of sshd -T output and recent auth.log entries catches drift before it becomes an incident.

Next steps: lock the door, then monitor the house

You now have a practical baseline to harden SSH on Linux servers without blocking your own deploys. Keys first. Config second. Firewall and fail2ban third. Audit monthly. If you want this done on production hosts you already run, or baked into a new web development launch, contact us and we can review your SSH posture alongside the rest of your stack. For a wider hardening pass, see diagnose high CPU and memory usage on a Linux server and keep your Notary Kathmandu-class deployments on the same secure footing.

Frequently Asked Questions

Shrinking SSH from open password login to Ed25519 key auth, a minimal sshd_config, UFW or nftables IP limits, and fail2ban on port 22 or your custom port.

SSH is the front door to your server. Laravel apps, WordPress sites, and database hosts all share the same risk profile. An open SSH service with password auth enabled is a magnet for credential stuffing, and bots hit port 22 within hours of a new box going live. On sister sites I deploy with Deployer 7 and GitLab CI, SSH is also how releases land. Lock it down wrong and you break deploys. Lock it down right and attackers bounce off while you still push code. That balance is what production hardening targets.

Generate an Ed25519 key on your workstation with ssh-keygen -t ed25519 -a 100, passphrase-protected. Create a non-root deploy user with sudo, copy the public key into /home/deploy/.ssh/authorized_keys, set .ssh to mode 700 and authorized_keys to mode 600, and chown both to deploy:deploy. Open a second terminal, keep your current session alive, and test key login before touching sshd_config. Only after a successful login set PasswordAuthentication no, run sshd -t, then systemctl reload ssh.

Use a drop-in at /etc/ssh/sshd_config.d/99-hardening.conf so package upgrades do not overwrite your edits. Baseline includes Port 2222, PermitRootLogin no, PasswordAuthentication no, PubkeyAuthentication yes, AllowUsers listing only accounts that need shell access, MaxAuthTries 3, LoginGraceTime 30, X11Forwarding no, AllowTcpForwarding no, and modern Ciphers, MACs, and KexAlgorithms such as chacha20-poly1305@openssh.com and curve25519-sha256. Validate with sshd -t before reload. On legal-tech and eCommerce hosts I maintain, only deploy and one admin user get in.

Yes, once every legitimate admin and CI job uses key auth and you have console or IPMI access as a fallback. Test key login before reload to avoid lockout from missing keys or wrong authorized_keys permissions.

Ed25519. It is shorter and faster than RSA while meeting current OpenSSH guidance on Ubuntu 22.04 and 24.04. Use RSA 4096 only for legacy clients. Always passphrase-protect private keys on workstations.

Changing port 22 to something like 2222 is security through obscurity. It is not a substitute for keys and firewall rules, but it does cut brute-force log noise on busy hosts. If you move the port, update three places at once: the Port directive in sshd_config, your UFW or nftables allow rule, and your CI deploy config plus local ~/.ssh/config. On GitLab CI jobs pass -p 2222 or set the port in Deployer host() config. I have fixed more broken pipelines from forgotten port changes than from actual attacks.

No. It reduces automated scan volume but not targeted attacks. Anyone can port-scan your host. Treat port changes as a log-noise filter paired with key-only auth, firewall allowlists, and fail2ban.

Set UFW to default deny incoming, allow outgoing, then allow SSH only from trusted office and GitLab CI IPs on your chosen port. Install fail2ban, copy jail.conf to jail.local, enable the sshd jail with port matching sshd_config, maxretry 3, findtime 600, bantime 3600, and add trusted IPs to ignoreip. Enable fail2ban, restart it, and verify with fail2ban-client status sshd. Defense in depth means network rules first, automated bans second, strict sshd settings third. On RHEL-family systems auth logs may live under journald instead of /var/log/auth.log.

Never reload sshd until a parallel key login succeeds. Keep your current root or sudo session open, open a second terminal, and test ssh -i your_key deploy@your_server before disabling passwords or restricting AllowUsers. Confirm console or IPMI recovery works before restart. Run sshd -t before every reload because a broken config can drop every active session. I have seen teams lock themselves out by skipping the test step on a Friday evening. Do not be that team.

OpenSSH ignores authorized_keys if ownership or mode is wrong. The .ssh directory must be mode 700 and owned by the user. authorized_keys must be mode 600 with ownership set to deploy:deploy or whichever account logs in. After adding keys run chown -R on the .ssh directory. Wrong permissions are one of the most common causes of lockout right after PasswordAuthentication no takes effect.

Keys first, config second, firewall and fail2ban third, audit monthly. Generate Ed25519 keys, create a non-root deploy user, install the public key, and verify login from a second terminal. Add sshd_config drop-in directives and validate with sshd -t before reload. Configure UFW or nftables IP allowlists for your port, install fail2ban with matching port settings, update Deployer and GitLab CI configs, and document console recovery steps alongside backup runbooks. Safe order means never reloading sshd until parallel key login works.

Hardening is not a one-time task. Package updates rewrite defaults, developers ask for temporary password auth, and CI IP ranges change after provider migrations. After every openssh-server upgrade run sshd -T filtered for permitrootlogin, passwordauthentication, pubkeyauthentication, allowusers, and port. Review recent Failed password and Accepted publickey lines in auth.log. Ubuntu security notices flag OpenSSH CVEs requiring urgent patching. For audit-heavy clients, Lynis or OpenSCAP against CIS profiles help. Small teams can use a monthly calendar reminder plus these commands and document SSH policy next to backup runbooks.

Review after every OpenSSH security update, when staff or contractors change, and when CI provider IP ranges move. A monthly quick audit of sshd -T output and recent auth.log entries catches drift before it becomes an incident. Run the same checks after any openssh-server package upgrade on Ubuntu 22.04 or 24.04 hosts.

Not if you update deploy configs when you change ports, users, or firewall rules. Lock it down wrong and releases stop landing. Lock it down right and pipelines still push code while attackers bounce off. Pass -p 2222 in SSH commands or set the port in Deployer host() config. Add GitLab CI runner IPs to UFW allow rules and fail2ban ignoreip. Use a dedicated deploy user per environment rather than sharing one private key across five developers. Test the full deploy path from a second terminal before closing your fallback session.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: