>_ CentralHost
← All posts

Stop exposing port 22 to the internet

Hardening sshd is not the same as removing it from the attack surface. Three levels, from key-only auth to closing the port entirely.

By CentralHost Team 4 min read

security ssh linux

Every Linux server that touches the internet gets the same welcome: within minutes, automated scanners find it and start hammering SSH. Most fleets learn to live with that as background noise. This post argues the opposite — three escalating levels that take sshd off the public internet, each worth shipping on its own, until port 22 is closed for good.

First, size the problem. Run this on any box with 22 open to the world:

journalctl -u ssh --since today | grep -c "Failed password"

That number is the brute force you absorb every single day. It’s just noise — until it isn’t: every OpenSSH CVE (remember regreSSHion, CVE-2024-6387) becomes remotely exploitable against a daemon anyone can reach. Hardening sshd lowers the risk; removing it from the public attack surface eliminates that class of problem entirely.

Changing the port doesn’t count — Shodan indexes 2222 too. The question that actually matters is a different one: who can reach the daemon at all?

Level 1: keys only

The starting point. If PasswordAuthentication yes survives anywhere in your fleet, fix it today:

# /etc/ssh/sshd_config
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin prohibit-password

Reload with systemctl reload sshd and confirm from a second terminal that you can still get in, before closing your session. While you’re at it, audit authorized_keys: you’ll find keys belonging to people who left two years ago.

Level 2: allow-list the source

A daemon nobody can reach can’t be brute-forced. If you connect through a VPN, a bastion or any box with a stable address, scope 22 to that source.

This is where people lock themselves out of their own servers, so don’t copy rules from a blog — this one included. Every firewall has its own syntax; what matters is the shape of the rule:

allow  tcp/22  from  <your bastion's IP / your VPN range / a box you keep for this>
deny   tcp/22  from  everything else

Rules of survival:

  • Allow a stable source, not whatever IP your home connection has today (echo $SSH_CLIENT on the server shows where you’re really coming from).
  • Make the change in the firewall that actually governs the host — ufw, firewalld, CSF, nftables. A rule added behind the real manager’s back gets ignored, or vanishes on its next reload.
  • If your provider offers security groups, prefer them: they live outside the host and stay editable from the panel even after you’ve locked yourself out.
  • Keep your session open, confirm access from a new terminal, and leave a scheduled revert (at) armed — cancel it only once you’ve confirmed you can still get in.

The price: maintaining that list becomes one more chore — rotating IPs, on-call from a hotel. Hence level 3.

Level 3: close the port

The trick is to invert the direction of the connection. With SSH, you are the one connecting in, which forces the server to keep a port listening to the entire internet. Invert it: an agent on the host connects outward, to a control plane, and keeps a single encrypted, authenticated connection alive — the same way any HTTPS client crosses your firewall. When you need a shell, you talk to the control plane and your session travels down the channel the agent already holds open.

before:   you ──▶ tcp/22 ──▶ sshd          port open to the whole internet

after:    you ──▶ control plane ◀── agent  the server connects OUTWARD;
                                           your session rides that channel

Nothing on the host is listening to the internet anymore, and with that the whole class of attack disappears:

  • no port for scanners to find,
  • no login prompt to brute-force,
  • no reachable daemon for the next sshd 0-day.

Port 22 is no longer your entrance, so you close it. Note the nuance: the firewall rule is mere cleanup — the security lies in having removed the listener:

deny  tcp/22  from  anywhere
# keep sshd on localhost/VPN as break-glass

CentralHost is built on this model: the agent keeps a single outbound WebSocket to the control plane, and every session is authenticated, tied to a named operator and recorded. Keep a break-glass path — your provider’s console or a VPN-scoped sshd — in case what fails one day is the control plane itself.

Rollout

  1. Today — keys only; audit authorized_keys.
  2. This week — scope 22 to your VPN or bastion wherever you can.
  3. This quarter — outbound channel for day-to-day access, inbound 22 closed, one audited break-glass path.

After the last step, the brute-force counter reads zero: there’s nothing left to hit.