Hardening a New VPS: The First Hour That Prevents Most Incidents

A fresh server gets SSH login attempts within the first hour online. The checklist we run on every new VPS: user accounts and keys, a firewall, automatic security updates, and a log baseline, before any application code goes on it.

The first hour on a new VPS

A brand-new server with SSH on port 22 collects its first login attempts before you have finished installing packages. If you check lastb on any rented VPS a day old, you'll find thousands of password guesses against root, admin, and test usernames. The noise is constant. The danger is your server still accepting passwords while you're busy with other setup.

So the first hour is about closing the easy paths before anything valuable lands on the machine.

Accounts and keys before anything else

One person, one account. Not a shared root login and not a shared "deploy" user, because a compromised key on a shared account can't be individually revoked, and audit logs become useless if four people are the same user.

The order matters more than the details:

  1. Create your own user with sudo, add your SSH key, and confirm you can log in with it.
  2. Keep the original login session open in a second terminal. Every SSH config change gets tested from a new session while the old one stays alive as your way back in.
  3. Only then disable what you're not using: PasswordAuthentication no in sshd_config, and PermitRootLogin prohibit-password so root keys can work in a pinch but password guessing cannot.

This two-terminal habit has saved more lockouts than any recovery tool we have tried. It costs one minute.

On changing the SSH port: we don't bother. Moving to 54321 reduces log noise, and that's it, since a port scanner finds an open port regardless of the number. The brute-force problem is gone either way once password logins are off.

Firewall: deny by default, open the minimum

The default posture is deny everything inbound, then explicitly open exactly what serves traffic, typically 22, 80, and 443:

ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable

The rule that actually prevents incidents is the one this list hides: the database never gets an internet-facing port. The two recurring ways around it in the wild are a Postgres listening on 0.0.0.0 after someone "fixed" a connectivity problem, and Redis bound to all interfaces because the docs suggested it. Both daemons belong on localhost or a private network. If two servers need to talk over a database port, use a VPN or a private subnet, not a public listener.

Automatic security updates

A VPS that patches itself beats a VPS whose owner patches it by hand eventually. unattended-upgrades from the security repo, left on, covers the OpenSSL-class problems where the window between "CVE published" and "scanner finds you" is measured in hours.

Two notes from running this in practice. First, unattended-upgrades applies patches but mostly doesn't restart services for you, so a running kernel or an old OpenSSL in memory is not the same as a patched one; a periodic reboot or a needrestart check closes that gap. Second, schedule your reboots yourself. An unattended upgrade kicking off a reboot at 14:00 on a client's site is a self-inflicted outage that looks identical to a malicious one in the logs.

Read the logs once, learn what normal looks like

Baselines work because you set them when everything is fine. On a fresh box that means noticing that auth.log gets constant noise and deciding not to alert on it, then looking for the signals that do matter:

  • Successful logins after password authentication is off. On a keys-only server, an "Accepted password" line is not noise, it's an incident.
  • sudo commands from accounts nobody maintains.
  • Failed sudo attempts from your own accounts early on, which usually means a teammate's setup, not an attack.

That's the whole intrusion detection setup we recommend for a single-application VPS. A full intrusion-detection stack on a box running one Laravel app is weight nobody reads; a weekly glance at the auth log catches more.

The box is the easier half

All of this takes under an hour and blocks the boring attack paths. It does nothing for the application itself: an app with an unauthenticated endpoint that writes files somewhere executes code no matter how the server is configured. Application hardening is its own checklist, and the two combine only when monitoring is watching the result.