The practical difference between automated background noise and an active breach, how to audit your logs, and the exact steps to lock down port 22 for good.
You run sudo journalctl -u ssh -n 50 on a server you deployed yesterday, and the terminal window fills with red text.
Lines scroll past faster than you can read them:
Failed password for root from 185.220.101.5 port 42118 ssh2
Invalid user admin from 45.141.87.12 port 51204 ssh2
Failed password for invalid user ubnt from 194.26.29.112 port 38920 ssh2
Connection closed by authenticating user root 185.220.101.5 port 42118 [preauth]
Your heart rate jumps.
Did someone target your company? Did an attacker find your new project? Are you under an active cyber attack right now?
Take a breath.
No, you are not being singled out. A team of elite hackers is not sitting in a dark room typing furiously against your IP address.
What you are looking at is the ambient noise of the internet. It is automated, relentless, and completely normal.
Every single public IPv4 address on Earth gets probed within fifteen minutes of coming online. Scripted worms, compromised IoT devices, and scanners like Censys or Shodan sweep the entire 32-bit address space around the clock. They hit port 22, throw twenty common usernames at it, and move on.
That does not mean you can ignore it.
If your server runs default settings with weak passwords, these mindless scripts will break in within an hour. Once inside, they install crypto miners, join your box to a DDoS botnet, or pivot into your private network.
So, what should you actually do when you see these connection attempts? You triage, you audit, and then you apply real engineering controls.
1. Internet Background Radiation: Why Port 22 Gets Hammered
To handle this cleanly, you need to understand what is happening on the wire.
There are roughly 3.7 billion routable IPv4 addresses in the world. Modern scanning tools like Masscan or ZMap can scan that entire address space across a single port in under forty-five minutes using a 10-gigabit connection.
Scanners do not care who you are. They do not care what domain points to your IP. They generate random IP addresses, send a TCP SYN packet to port 22, and listen for a SYN-ACK response.
When your server replies with a SYN-ACK, their script notes that port 22 is open. A secondary worker thread immediately opens a full TCP handshake and initiates the SSH protocol exchange.
OpenSSH sends its identification banner right away:
SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13
Now the bot knows your exact operating system and SSH version. It fires off credentials pulled from leaked dumps like rockyou.txt or common cloud default pairings:
-
rootwith123456,password, oradmin -
ubuntuwithubuntu -
adminwithadmin123 -
test,oracle,git,guest,support, andpostgres
These bots run distributed across thousands of infected residential routers and cheap VPS nodes. Seeing five thousand failed attempts in your log file across twenty-four hours is standard operating procedure for a public Linux box.
Treat it like street noise outside an office window. It is annoying, but it only becomes a crisis if someone turns the doorknob and finds the deadbolt unlocked.
2. Triage First: Did Anyone Actually Get In?
Never start reconfiguring firewalls while in a state of panic. Your first task is finding out whether an attacker successfully authenticated.
Do not waste time reading through forty thousand lines of failed attempts. Look exclusively for successes.
Run this command on Debian or Ubuntu systems:
sudo grep "Accepted " /var/log/auth.log
On systems running systemd without standard text log files (such as modern Fedora, Arch, or RHEL 9), query the journal directly:
sudo journalctl -u sshd -g "Accepted "
If your server uses the older service naming convention on Debian or Ubuntu, use -u ssh:
sudo journalctl -u ssh -g "Accepted "
Clean output looks like this:
Mar 08 14:22:01 web-node-01 sshd[18210]: Accepted publickey for asep from 198.51.100.24 port 54210 ssh2: ED25519 SHA256:7uK...
Every accepted connection should list a username you recognize, an authentication method you expected (like publickey), and a source IP that belongs to you or your team.
What happens if you see something like this?
Mar 08 03:14:22 web-node-01 sshd[91204]: Accepted password for root from 185.220.101.5 port 39112 ssh2
That is an active breach. Someone guessed or brute-forced your root password at 3:14 AM from a remote IP address.
Next, check who is on the machine right now:
who
w
The w command shows the active username, the TTY terminal, the source IP address, and what command their shell is running right now.
Then inspect the historical login records stored in /var/log/wtmp:
last -n 20
Also check /var/log/lastlog to see if inactive system accounts like daemon, sync, or www-data somehow registered an interactive login:
lastlog -b 0
If you see an unauthorized IP address in your accepted logins, stop reading this guide. Disconnect the server network interface from your cloud management console immediately. Take an out-of-band disk snapshot for forensic analysis, back up your raw database dumps, and prepare to rebuild the operating system from scratch.
Never try to clean a compromised Linux box by simply deleting a file. Attackers drop kernel rootkits, backdoored shared libraries in /etc/ld.so.preload, and hidden systemd timers within thirty seconds of gaining root. Rebuilding is the only safe option.
Assuming all accepted logins belong to you, let us move to locking the front door properly.
3. The Port 2222 Myth: Useful Noise Reduction, Bad Security
Go to any Linux forum and ask how to stop SSH attacks. Half the replies will tell you:
"Just change Port 22 to Port 2222 in /etc/ssh/sshd_config!"
This advice is both helpful and dangerous. It depends on whether you understand what changing the port actually achieves.
Does moving your SSH port stop dumb, automated scanners? Yes.
Most mass-scanning botnets do not have the bandwidth or patience to scan all 65,535 TCP ports on every single IPv4 address. They scan port 22 because that gives them the highest return on investment.
If you move SSH to port 2222, 28492, or any other high number, your /var/log/auth.log failure count will drop by roughly 98 percent overnight.
Your log files stop filling up gigabytes of disk space. Your system stops spawning hundreds of child sshd processes every hour just to exchange keys with bots. Your CPU usage flattens.
Here is why it is not a real security boundary: security through obscurity fails against anyone targeting you.
Run this simple Nmap scan from an outside machine:
nmap -sV -p 1-65535 your-server-ip
Even if you run SSH on port 49812, Nmap connects to the port, reads the initial banner (SSH-2.0-OpenSSH), and immediately flags it:
PORT STATE SERVICE VERSION
49812/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13
A targeted port scan finds OpenSSH on a custom port in under two minutes.
Changing the port is a great operational hygiene trick to keep your logs clean. It reduces system wear and tear. But do not treat it as a firewall or a substitute for strong authentication.
4. The Three Non-Negotiables in sshd_config
If you want to eliminate brute-force risk completely, configure the SSH daemon correctly.
Open your SSH configuration. On modern systems, avoid editing /etc/ssh/sshd_config directly if your distribution supports drop-in directories. Instead, drop a configuration file into /etc/ssh/sshd_config.d/:
sudo nano /etc/ssh/sshd_config.d/99-hardening.conf
If your system does not support the .d directory, edit /etc/ssh/sshd_config directly.
Apply these three settings:
# 1. Disable remote root login
PermitRootLogin no
# 2. Kill password authentication completely
PasswordAuthentication no
KbdInteractiveAuthentication no
# 3. Require public key authentication
PubkeyAuthentication yes
MaxAuthTries 3
Look at what these lines do to an incoming attack.
First, PermitRootLogin no takes away the primary username every script targets. Root exists on every Unix machine. Attackers know this, so ninety percent of credential stuffing targets root. Forcing attackers to guess both an unknown username and a secret credential multiplies their difficulty exponentially.
Second, PasswordAuthentication no kills the entire concept of password guessing. When a bot opens an SSH connection to your box, the daemon offers only one authentication method: public key cryptography.
If the client cannot present a valid private key signed against your public key list, the connection terminates during the initial SSH handshake. The bot cannot guess a single password. No dictionary works. The attack surface drops to zero.
Third, MaxAuthTries 3 stops an attacker who does establish a connection from testing dozens of keys or passwords inside a single multiplexed TCP session.
Before you apply these changes, you must remember the golden rule of SSH administration:
Never restart the SSH daemon without testing the syntax first, and never close your current shell session.
Test your configuration syntax:
sudo sshd -t
If the command produces zero output, the syntax is valid. If it returns an error, fix it before touching the service.
Now reload the daemon:
sudo systemctl reload ssh
On CentOS, RHEL, or Fedora, the service name is sshd:
sudo systemctl reload sshd
Leave your current terminal window open. Open a completely new terminal window on your local laptop, and try to SSH into the box:
ssh -i ~/.ssh/id_ed25519 your-user@your-server-ip
If you get in cleanly, your configuration works. If you get locked out, your original terminal session is still active with root or sudo access so you can revert the mistake.
5. Stop Using 10-Year-Old RSA Keys
Disabling passwords only protects you if your SSH keys are strong.
Many engineers still run ssh-keygen and hit Enter until they get a 2048-bit RSA key. Some guides written in 2012 still recommend RSA keys.
RSA is old. It works, but it is bulky, slow to compute, and vulnerable to implementation errors. If you use an RSA key smaller than 2048 bits, it is mathematically weak. Even 4096-bit RSA keys produce huge signatures and consume unnecessary CPU time during handshakes.
Use Ed25519 instead.
Ed25519 is an elliptic curve signature scheme (using Curve25519). It provides roughly 128 bits of security, matching a 3072-bit RSA key, but with a public key of just 68 characters. It generates signatures in constant time, protecting you against cache-timing and side-channel attacks.
Generate an Ed25519 key on your local workstation:
ssh-keygen -t ed25519 -a 100 -C "work-laptop-2026"
The -a 100 flag tells the key generator to run 100 Key Derivation Function (KDF) rounds using bcrypt. This makes brute-forcing your private key passphrase offline excruciatingly slow for an attacker who manages to steal your raw id_ed25519 file.
Always set a strong passphrase on your private key.
An unencrypted private key sitting on your laptop disk is no better than a plaintext password written in a text file. If someone steals your laptop or an npm package runs malicious code inside your workspace, an unencrypted key lets them access every server you manage.
Use ssh-agent so you only type your passphrase once per session:
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
Next, check ~/.ssh/authorized_keys on your server. You can lock keys down further using key options.
If your laptop always connects from an office network or home static IP block, you can restrict the key directly in the file:
from="198.51.100.0/24,203.0.113.15" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... work-laptop-2026
Even if someone steals your private key and knows your passphrase, the SSH daemon rejects the connection if the packets arrive from any IP address outside those CIDR ranges.
6. Dynamic Defense: Fail2ban, CrowdSec, and Kernel Rate Limits
Once password authentication is dead and your keys are locked down, the bots cannot break in. But they will still knock on the door, write failure entries to your logs, and waste resources.
You have three solid options for dynamic defense.
Option A: Fail2ban (The Classic Log Watcher)
Fail2ban is a Python daemon that tails your log files. When it sees an IP produce multiple authentication failures within a set time window, it calls iptables or nftables to insert a temporary firewall rule blocking that IP.
Install it on Ubuntu or Debian:
sudo apt update && sudo apt install -y fail2ban
Create a local jail configuration:
sudo nano /etc/fail2ban/jail.local
Paste these settings:
[sshd]
enabled = true
port = ssh
filter = sshd
backend = systemd
maxretry = 3
findtime = 10m
bantime = 1d
ignoreip = 127.0.0.1/8 ::1 198.51.100.24
This configuration tells Fail2ban to watch the systemd journal for SSH failures. If an IP fails three times within ten minutes, Fail2ban drops all traffic from that IP for twenty-four hours.
Make sure you put your own static IP address in ignoreip. Locking yourself out because of a bad script or a fat-fingered key is an embarrassing mistake.
Start and enable Fail2ban:
sudo systemctl enable --now fail2ban
Check the status of your SSH jail:
sudo fail2ban-client status sshd
Output shows active bans:
Status for the jail: sshd
|- Filter
| |- Currently failed: 2
| |- Total failed: 48
| `- File list: ...
`- Actions
|- Currently banned: 6
|- Total banned: 14
`- Banned IP list: 45.141.87.12 185.220.101.5 ...
Fail2ban works well, but it has flaws. It is entirely reactive. An attacker must connect and fail multiple times before Fail2ban takes action. Furthermore, modern botnets use rotating residential proxies. If a botnet has fifty thousand distinct IPs and tests one password per IP, Fail2ban never triggers.
Option B: CrowdSec (Collaborative Threat Intelligence)
CrowdSec is a modern, open-source replacement for Fail2ban written in Go.
Instead of operating in isolation on your machine, CrowdSec shares anonymized attack data with a global network. When thousands of other CrowdSec instances across the internet report an IP address brute-forcing SSH, your server downloads that blocklist automatically.
The attacker gets dropped on their very first packet, before they even touch your SSH socket.
It uses a decoupled architecture: an agent analyzes logs, and a remediation bouncer inserts rules into nftables. If you run a fleet of servers, CrowdSec is significantly more effective than Fail2ban.
Option C: UFW Rate Limiting (Kernel-Level Drop)
If you do not want to run background daemons like Fail2ban, the Linux kernel can rate-limit connections directly using Netfilter's recent module.
If you use UFW, run:
sudo ufw limit ssh
This simple command creates an iptables rule that blocks an IP address if it attempts six or more connections within thirty seconds.
It does not parse logs. It does not run Python scripts. The kernel tracks connection state in memory and drops packets at the network layer.
7. The Ultimate Perimeter: Make Port 22 Completely Invisible
Why should port 22 be open to four billion people on the public internet when only two people need access?
The single best security control for SSH is removing it from the public internet entirely.
Look at your infrastructure options:
Cloud Security Groups / Cloud Firewalls
If your server sits in AWS, Google Cloud, Hetzner, or DigitalOcean, do not rely solely on host-level firewalls like UFW. Use the cloud provider's security group.
Add an inbound rule for TCP port 22 that allows traffic only from your specific office IP address or home static IP:
Type: SSH
Protocol: TCP
Port Range: 22
Source: 198.51.100.24/32
When an unauthorized IP tries to connect, the cloud hypervisor drops the packet before it reaches your virtual machine's virtual network interface.
Your Linux kernel does not even have to spend CPU cycles allocating a socket buffer or handling TCP handshakes. Your server becomes a black hole to scanners.
WireGuard and Mesh Overlays (Tailscale)
What if you work remotely and do not have a static IP address?
Use an overlay network like Tailscale, Netbird, or standard WireGuard.
Install Tailscale on your server and your laptop. Your server gets an internal IP address (like 100.82.14.90) accessible only to devices inside your private network.
Now, tell your SSH daemon to listen only on that private interface.
Edit /etc/ssh/sshd_config.d/99-hardening.conf:
# Bind only to your private VPN interface
ListenAddress 100.82.14.90
Restart SSH.
Port 22 on your public IP is now completely dead. A public port scan shows port 22 as closed or filtered. To SSH into the box, you must authenticate to your VPN first.
When building network services (a challenge I ran into often when building local file and media engines like AiroShare), interface binding is always your cleanest security barrier. If a service does not bind to 0.0.0.0, the public internet cannot touch it.
8. Advanced Hardening: FIDO2 Hardware Keys and SSH Certificates
For production environments where basic key authentication is not enough, look into hardware tokens and certificates.
FIDO2 / YubiKey Hardware Keys
Modern versions of OpenSSH (version 8.2 and newer) support hardware security keys natively using FIDO2 / U2F.
Instead of storing an Ed25519 key on your laptop SSD, you generate an ed25519-sk key tied to a physical YubiKey:
ssh-keygen -t ed25519-sk -O resident -C "yubikey-primary"
When you SSH into your server, your terminal pauses until you physically tap the metal contact on your USB hardware key.
Even if a malicious threat actor installs a keylogger on your laptop and steals your private key files, they cannot log into your server without your physical thumb pressing the hardware key on your desk.
SSH Certificates Instead of authorized_keys
Managing ~/.ssh/authorized_keys works fine when you have three servers. When you manage forty servers and five engineers join or leave the team, it becomes an unmanageable mess.
With SSH Certificates, you set up an internal Certificate Authority (CA). You sign an engineer's public key with an expiration timestamp:
ssh-keygen -s ca_user_key -I alice@company.com -V +8h -n alice id_ed25519.pub
Every server in your fleet trusts the CA public key. When Alice logs in, the server checks the signature on her certificate. If the certificate was signed by the company CA and has not expired, she gets access.
When Alice leaves the company, nobody has to log into forty servers to delete her public key. Her certificate expires automatically after eight hours.
9. The Incident Response Checklist: What to Do If They Succeeded
What if you ran the triage commands in Section 2 and discovered an unauthorized IP address in your accepted logins?
Here is your exact operational response checklist:
Step 1: Isolate the Host
Do not reboot. Rebooting clears memory buffers, running process trees, and temporary directories like /dev/shm or /tmp where attackers store their tools.
Go to your cloud provider dashboard and detach the public network interface, or assign a quarantine security group that blocks all traffic except your administrative IP.
Step 2: Capture Forensic Artifacts
If you need to know how the attack happened for compliance or client reporting, take a disk snapshot and dump active memory:
# Check running processes before doing anything else
ps auxf > /tmp/compromised_process_tree.txt
# Dump active network connections
ss -tunap > /tmp/compromised_sockets.txt
# Check recently modified files in system paths
find /etc /usr/bin /usr/sbin -mmin -120 -type f > /tmp/modified_binaries.txt
Step 3: Check Scheduled Persistence
Attackers maintain access using cron jobs, systemd services, or SSH key injection:
# Check all crontabs
for user in $(cut -f1 -d: /etc/passwd); do sudo crontab -u $user -l; done
# Check systemd timers
systemctl list-timers --all
Check every user's ~/.ssh/authorized_keys file to see if the attacker injected their own public key.
Step 4: Rebuild from Scratch
Do not try to fix the system manually. You cannot trust standard binaries on a compromised machine.
An attacker can easily replace /bin/ps or /usr/bin/ss with custom binaries that hide their miner process and network sockets from your terminal.
Back up your application data, wipe the disk, redeploy the operating system from clean code, and rotate every single secret, API key, and database credential that was stored on that server.
10. An Interesting Fact About Port 22
Have you ever wondered why SSH runs on port 22 instead of port 800 or 9999?
In the spring of 1995, Tatu Ylönen, a researcher at the Helsinki University of Technology, designed the original SSH-1 protocol after a password-sniffing attack compromised thousands of accounts on his university network. He wanted a secure alternative to Telnet (port 23) and FTP (port 21).
Port numbers below 1024 required official assignment by the Internet Assigned Numbers Authority (IANA), managed by Internet pioneer Jon Postel.
Ylönen noticed that port 22 was unassigned, sitting right between FTP on port 21 and Telnet on port 23.
In July 1995, Ylönen sent an email to Postel requesting port 22 for his new protocol, explaining that SSH was designed to replace both insecure tools. Postel approved the request in less than twenty-four hours.
Today, that single port assignment handles billions of cryptographic handshakes every day across virtually every data center on the planet.
11. My Production Rules of Thumb
When setting up a new Linux node, I stick to a clean, practical baseline:
-
On the public internet: Never leave port 22 exposed to
0.0.0.0/0unless it is a sacrificial public bastion host. Use a cloud firewall or VPN. -
In sshd configuration: Turn off passwords (
PasswordAuthentication no) and disable direct root access (PermitRootLogin no). This eliminates ninety-nine percent of automated threats immediately. -
For key generation: Always run
ssh-keygen -t ed25519 -a 100and protect the key with a passphrase. Avoid legacy RSA keys. -
When auditing logs: Ignore the failed connection attempts unless they cause resource exhaustion. Focus your attention entirely on
Acceptedlog lines. - For noise reduction: Install Fail2ban or CrowdSec to keep your auth logs clean and prevent socket churn.
-
Before reloading sshd: Always run
sshd -tand test the connection in a separate terminal before closing your active shell.
Wrapping Up
Seeing thousands of failed SSH logins in your terminal can be unsettling. But once you realize that the internet is constantly buzzing with automated scanners, the fear disappears.
The goal is not to stop people from trying to connect. The internet is a public network; anyone can send packets to any IP they want.
Your goal is making sure that when an unauthorized packet hits port 22, it finds nothing to talk to, nothing to guess, and nowhere to go. Turn off passwords, enforce strong keys, hide behind a private network when possible, and let the bots shout into the void.
What does your current SSH setup look like? Are you still running standard port 22 with password logins enabled, or have you moved to VPN-only access?
About the Author
Asep Sayyad is a Linux and DevOps engineer passionate about Linux administration, automation, cloud technologies, containers, and open-source software. He enjoys solving real-world infrastructure challenges and sharing practical knowledge through in-depth technical articles, tutorials, and hands-on guides.
His goal is to help aspiring and experienced engineers build stronger Linux and DevOps skills with content focused on real production scenarios rather than theory alone.
Connect with Me
- Portfolio: asepsayyad007.in
- Blog: asepsayyad007.in/blogs
- GitHub: github.com/asepsayyad007
- LinkedIn: linkedin.com/in/asepsayyad
- Medium: asepsayyad007.medium.com
Enjoyed this article?
If you found this guide helpful, consider:
- Starring my open-source projects on GitHub.
- Sharing this article with fellow Linux and DevOps engineers.
You can also follow me for more practical content on Linux, DevOps, Cloud, Containers, Automation, and Open Source. Thanks for reading, and enjoy your learning!
© 2026 Asep Sayyad
Top comments (0)