Network & delivery
8 min readUpdatedSSH port 22: what it is, why it is scanned, and how to harden it
Quick answer
SSH listens on TCP port 22 by default, a number IANA assigned in July 1995. Because every SSH server starts there, automated scanners try it constantly with common usernames and passwords. The defence is not a different port but key-only authentication, no direct root login, an allowlist or VPN where possible, and rate limits such as fail2ban. Moving SSH to another port cuts log noise from untargeted bots but adds no real security.
01What is port 22?
Port 22 is one of the well-known ports: the TCP port registered with IANA for the Secure Shell protocol (SSH), which provides encrypted remote login, command execution, file transfer with SFTP and scp, and tunnelling. RFC 4253, the SSH transport standard of January 2006, says a server "normally listens for connections on port 22". IANA also lists UDP and SCTP 22 for SSH, but in practice SSH runs over TCP only.
The number has a story. Tatu Ylönen wrote the first SSH in spring 1995 to replace telnet on port 23 and FTP on port 21, and asked IANA for the free number between them just before announcing ssh-1.0 in July 1995. The server most people run today, OpenSSH, first shipped with OpenBSD 2.6 on 1 December 1999.
| Property | Value |
|---|---|
| Default port | TCP 22 |
| Protocol | SSH-2, standardised in RFC 4251 to RFC 4254 (January 2006) |
| Usual server | OpenSSH sshd |
| Configuration | /etc/ssh/sshd_config, plus /etc/ssh/sshd_config.d/*.conf on Debian and Ubuntu |
| First bytes sent | A plain-text banner such as SSH-2.0-OpenSSH_9.2p1 |
| Face the internet? | Only if needed, ideally from known addresses or through a VPN |
02Why is port 22 scanned so much?
Because it is predictable and valuable. Every internet-facing address is probed continuously, much of it by compromised machines in botnets, and an SSH server that accepts a guessed password hands over a shell. Bots work through lists of common usernames such as root, admin, test or ubuntu with leaked passwords, and some only read the banner to map software versions for the next vulnerability. When Qualys disclosed regreSSHion in July 2024, it counted more than 14 million potentially vulnerable OpenSSH servers exposed to the internet, which shows how large the pool is.
The attempts are easy to recognise in the log. The lines below were produced on a test server running OpenSSH 9.2p1 by failed logins and by a client that sent HTTP to port 22, the same patterns scanners leave around the clock. With password authentication switched on, failed guesses also appear as "Failed password for" lines.
Invalid user admin from 198.51.100.23 port 52808
Connection closed by invalid user admin 198.51.100.23 port 52808 [preauth]
User root from 198.51.100.23 not allowed because none of user's groups are listed in AllowGroups
kex_exchange_identification: client sent invalid protocol identifier "GET / HTTP/1.1"
banner exchange: Connection from 198.51.100.23 port 52824: invalid format
kex_exchange_identification: Connection closed by remote host03How to check what your SSH server allows
Read the effective configuration, not the file. sshd -T prints every setting after defaults and included files are applied, which catches overrides you did not know about. On OpenSSH 9.2 with no configuration at all it reports permitrootlogin without-password (keys only for root), passwordauthentication yes, maxauthtries 6, logingracetime 120 and maxstartups 10:30:100; distributions, installers and cloud images change some of these.
# effective settings that matter for exposure
sudo sshd -T | grep -Ei '^(port|permitrootlogin|passwordauthentication|kbdinteractiveauthentication|maxauthtries|maxstartups) '
# which addresses sshd listens on
sudo ss -Htlnp 'sport = :22'
# logins attempted for unknown users in the last 24 hours
journalctl -u ssh --since '-24h' -o cat | grep -c 'Invalid user'
# the addresses trying hardest
journalctl -u ssh --since '-24h' -o cat | grep -oP 'Invalid user \S+ from \K[0-9a-f.:]+' | sort | uniq -c | sort -rn | head
# installed version, for vulnerability checks
ssh -V04A tested sshd_config hardening baseline
Put changes in a drop-in file instead of editing the main file. sshd uses the first value it reads for each keyword, and Debian and Ubuntu include /etc/ssh/sshd_config.d/*.conf at the top of sshd_config, so a file named 10-hardening.conf wins over later lines and over drop-ins that sort after it. This block was checked with sshd -t and sshd -T on OpenSSH 9.2p1: with PermitRootLogin yes and PasswordAuthentication yes further down the main file, both still came out as no. The table explains each setting; the file itself follows it.
| Setting | OpenSSH default | Baseline | Why |
|---|---|---|---|
| PermitRootLogin | prohibit-password (sshd -T prints without-password) | no | Log in as a named user and use sudo, so every action has an owner |
| PasswordAuthentication, KbdInteractiveAuthentication | yes | no | Removes password guessing altogether |
| MaxAuthTries | 6 | 3 | Fewer attempts per connection; failures are logged from half the limit |
| LoginGraceTime | 120 seconds | 30 seconds | Unauthenticated connections are cut sooner, freeing slots |
| MaxStartups | 10:30:100 | 10:30:60 | Above 10 unauthenticated connections, refuse 30% of new ones, rising to all at 60 |
| PerSourceMaxStartups | none | 3 | One address cannot hold more than 3 unauthenticated connections |
| AllowGroups | any user | sshusers | Only members of one group may log in at all |
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
MaxAuthTries 3
LoginGraceTime 30
MaxStartups 10:30:60
PerSourceMaxStartups 3
AllowGroups sshusers
X11Forwarding no
AllowAgentForwarding no
ClientAliveInterval 300
ClientAliveCountMax 205Banning brute-force sources with fail2ban
Key-only authentication makes guessing futile, but the attempts still cost CPU, fill logs and occupy MaxStartups slots. fail2ban reads the log and bans an address after repeated failures. On Debian 12, which logs to the systemd journal and has no /var/log/auth.log unless rsyslog is installed, the jail needs backend = systemd and the python3-systemd package, or fail2ban stops with "Have not found any log file for sshd jail".
The mode matters too. Run against the full 10-line test log behind the excerpt above, the stock sshd filter in its default normal mode counted 3 of the 10 lines as failures; in aggressive mode it counted 9, including the banner-scan lines, and ignored only the clean disconnect.
[sshd]
enabled = true
backend = systemd
mode = aggressive
port = ssh
maxretry = 5
findtime = 10m
bantime = 1h
bantime.increment = true
banaction = nftables-multiport06Rate-limiting new SSH connections in the firewall
A firewall rate limit works before sshd sees anything and needs no log parsing. The ruleset below drops new connections to port 22 from any single address above 10 per minute, after a burst of 5, and leaves established sessions alone. In an isolated test, 30 rapid connection attempts from one address produced 6 accepted and 24 dropped. Retransmitted SYNs count against the limit, so keep it generous for real users behind shared NAT.
OpenSSH 9.8 (1 July 2024) added a built-in alternative, PerSourcePenalties, on by default: sshd records a penalty against addresses that fail authentication repeatedly or crash a session and refuses them until it expires. It is not available on the OpenSSH 9.2 tested here.
table inet ssh_guard {
set ssh4 { type ipv4_addr; flags dynamic; timeout 5m; size 65535; }
set ssh6 { type ipv6_addr; flags dynamic; timeout 5m; size 65535; }
chain input {
type filter hook input priority filter; policy accept;
tcp dport 22 ct state new add @ssh4 { ip saddr limit rate over 10/minute burst 5 packets } counter drop
tcp dport 22 ct state new add @ssh6 { ip6 saddr limit rate over 10/minute burst 5 packets } counter drop
}
}07Should you change the SSH port?
Changing the port is obscurity, not security. It removes most untargeted bots, which only try 22, so logs become quieter and real attacks easier to spot. It does nothing against anyone who scans all 65,536 ports, and it protects no weak password. Use it, if at all, on top of key-only login, never instead of it. A port below 1024 that nothing else uses has one advantage: an unprivileged local user cannot bind it while sshd is down. The table at the end of this section compares a port change with the measures that do add security.
If you do change it, open the new port in the firewall before you restart, and keep the old session open until a login on the new port works:
- Debian, RHEL and most distributions: set Port 2222 (or your choice) in a drop-in file, run sshd -t, then restart the ssh or sshd service
- Port lines add up instead of overriding each other: with Port 2222 in a drop-in and Port 22 left uncommented in sshd_config, sshd -T shows both and sshd listens on both
- Ubuntu 22.10 and later use socket activation; from 24.04 the port is read from sshd_config by a generator, so run systemctl daemon-reload and restart ssh.socket
- RHEL with SELinux: allow the new port first with semanage port -a -t ssh_port_t -p tcp 2222, as Red Hat documents
- Clients: add Port 2222 under the host in ~/.ssh/config, or pass ssh -p 2222
| Measure | Stops | Does not stop |
|---|---|---|
| Key-only authentication | Password guessing and credential stuffing | Stolen private keys, sshd vulnerabilities |
| Allowlist or VPN in front of SSH | Almost all exposure: strangers never reach sshd | Attacks from inside the allowed network |
| fail2ban or a firewall rate limit | Noisy brute force from single addresses | Slow attacks spread across many addresses |
| Changing the port | Bots that only try port 22 | Anyone who scans more than one port |
| Keeping OpenSSH patched | Known vulnerabilities such as regreSSHion | Weak configuration |
08What a firewall, VPN or proxy can do for SSH
The strongest protection is to make sshd unreachable from strangers: allow port 22 only from your office or management network, or reach it through a VPN or a bastion host. Then the guessing never starts. For a fleet, that one rule beats every log-parsing tool combined, and it fits the default-deny approach in firewall configuration.
A web reverse proxy or WAF does not help here. It carries HTTP and HTTPS, not SSH, so it neither filters brute force on port 22 nor hides it, and if the server's address is public, attackers can flood port 22 directly. Rate limits such as those in what is rate limiting slow guessing on one host; a volumetric flood against the port needs network-level filtering upstream of the server's link.
09SSH and port 22: a dated timeline
Vulnerabilities matter as much as configuration, because a pre-authentication flaw bypasses every login rule. Check the installed version with ssh -V and your distribution's package version, which carries back-ported fixes.
| Date | Event |
|---|---|
| July 1995 | Tatu Ylönen announces ssh-1.0; IANA assigns port 22 |
| 1 December 1999 | OpenSSH 1.2.2 ships with OpenBSD 2.6 |
| January 2006 | SSH-2 published as RFC 4251 to RFC 4254 |
| 1 July 2024 | CVE-2024-6387, regreSSHion, unauthenticated remote code execution in OpenSSH 8.5p1 to 9.7p1 on glibc Linux, fixed in OpenSSH 9.8 |
| 1 July 2024 | OpenSSH 9.8 adds PerSourcePenalties, on by default |
Questions
Frequently asked questions
Sources
Sources and verification
sshd_config drop-in validated with sshd -t and sshd -T on OpenSSH 9.2p1 using a scratch configuration; log lines produced by a scratch sshd in an isolated network namespace; fail2ban 1.0.2 (Debian package, unpacked into a scratch directory) jail checked with fail2ban-client -t and the sshd filter run with fail2ban-regex against those lines; nftables rate limit syntax-checked and load-tested with nftables 1.0.6 in an isolated namespace; Debian 12, October 2026. No 24-hour exposure measurement is included.
- 01RFC 4253: The Secure Shell (SSH) Transport Layer Protocol · IETF
- 02How SSH got port number 22 (Tatu Ylönen) · SSH Communications Security
- 03OpenSSH project history · OpenSSH
- 04OpenSSH 9.8 release notes (PerSourcePenalties) · OpenSSH
- 05sshd_config(5), OpenSSH 9.2 in Debian bookworm · Debian
- 06regreSSHion: remote unauthenticated code execution vulnerability in OpenSSH server · Qualys
- 07fail2ban issue 3567: Have not found any log file for sshd jail (Debian 12) · fail2ban (GitHub)
- 08SSHd now uses socket-based activation (Ubuntu 22.10 and later) · Ubuntu Discourse
- 09Securing networks: using secure communications between two systems with OpenSSH (RHEL 9) · Red Hat
Last updated