On this page
A newly provisioned VPS needs more than a strong password. Reliable access control combines tested SSH keys, a narrow network policy, and monitoring that can slow repeated authentication abuse without locking out the administrator.
This walkthrough stages those changes on an Ubuntu server you control. Keep the provider's recovery console available throughout, and leave your original SSH session open until a second connection succeeds after every significant change.
Map your access paths before changing rules
Write down the SSH port, administrator account, public addresses, and services that must remain reachable. Include IPv6, backup connections, monitoring, and any private VPN interface. Recovery access should not depend on the firewall configuration you are about to edit.
Use the Subnet & CIDR Calculator below to inspect an IPv4 address and prefix before writing an allow rule. It shows whether the intended administrator address falls within a network and how many addresses that rule covers. A single-host prefix such as /32 is usually narrower than an office-wide subnet.
Documentation addresses in this guide are placeholders. Replace 198.51.100.25 with your actual trusted public egress address; it is not the laptop's private Wi-Fi address. If that address changes frequently, establish a controlled VPN or another documented access method instead of maintaining a fragile allowlist.
Read the Ubuntu UFW guide before enabling filtering on an unfamiliar setup. Container-published ports and cloud firewalls introduce additional paths, so host rules alone may not describe the server's effective exposure.
Create and test key-based access
Use a non-root administrator account with sudo privileges. If your provider already supplied one, verify it rather than creating duplicate accounts. Generate a passphrase-protected key on your workstation and copy only its public portion to the server:
# On your workstation
ssh-keygen -t ed25519 -a 64 -f ~/.ssh/example_vps
ssh-copy-id -i ~/.ssh/example_vps.pub admin@203.0.113.10
ssh -i ~/.ssh/example_vps -o IdentitiesOnly=yes admin@203.0.113.10
# In the new server session
sudo -vConfirm the server's host-key fingerprint through a trusted provider console before accepting it. Do not reflexively delete a changed host-key warning; first determine whether the server was rebuilt or the connection is being intercepted. Host verification and user authentication solve different problems.
Keep private keys on trusted devices, protected by a passphrase and appropriate filesystem permissions. Do not upload them into the server's authorized_keys file. That file contains public keys, one per line. A hardware-backed key can further reduce exposure where your client and workflow support it.
Before proceeding, open a second terminal and repeat the key login. Verify sudo there too. Successful key login must be demonstrated independently of an old session that may still be using password authentication or an SSH connection-sharing socket.
Stage UFW without cutting off administration
Inspect existing rules before adding anything. The following example assumes SSH is actually listening on TCP port 22 and that the machine serves public HTTP and HTTPS. Adapt the source address and port before execution:
sudo apt update
sudo apt install ufw fail2ban
sudo ufw status verbose
sudo ss -lntp
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow from 198.51.100.25/32 to any port 22 proto tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status numberedDo not add web ports unless the server needs them. For a non-web host, omit those rules. Default deny incoming is a useful starting point, but existing permissive rules still apply; review and remove obsolete entries deliberately. Never reset a remote firewall casually.
Mirror the required access in the provider's firewall if one is enabled. Check /etc/default/ufw for IPv6 handling and audit IPv6 listeners separately. The IPv4 calculator cannot validate an IPv6 allowlist, and disabling IPv6 filtering does not safely close IPv6 services.
Test a new SSH connection immediately while the original session stays open. Verify expected public services from another network and check that database ports are not public. External verification catches differences between the rules you intended and the path packets actually take.
Harden SSH only after the key test passes
On Ubuntu, use a local SSH configuration snippet if the packaged configuration includes that directory. Check existing snippets and provider-generated files first: OpenSSH generally uses the first obtained value for a setting, and Match blocks can change the effective result.
sudo tee /etc/ssh/sshd_config.d/00-local-hardening.conf >/dev/null <<'EOF'
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no
EOF
sudo /usr/sbin/sshd -t
sudo /usr/sbin/sshd -T | grep -E 'pubkeyauthentication|passwordauthentication|kbdinteractiveauthentication|permitrootlogin'
sudo systemctl reload sshOnly reload if syntax validation succeeds. For configurations containing Match, inspect effective settings with sshd -T -C and connection-specific values as described in the OpenSSH manual. Disabling keyboard-interactive authentication can disable PAM-based second-factor workflows; preserve the appropriate mechanism if your deployment intentionally uses one.
Try another fresh key connection after reloading. Confirm password login is unavailable without repeatedly hammering production authentication. Root login restriction assumes the tested non-root account has working sudo access. Keep emergency recovery through the provider console documented and protected with strong account authentication.
Configure Fail2ban and verify the actual jail
Fail2ban reacts to logged failures; it does not patch vulnerable services or replace firewall policy. Use the project's official documentation repository for configuration behavior and distribution-specific details. Start with one SSH jail rather than enabling many untested filters.
Create /etc/fail2ban/jail.d/sshd.local with an [sshd] section containing enabled = true, backend = systemd, port = 22, maxretry = 5, findtime = 10m, and bantime = 1h, each on its own line. Set the actual SSH port if different. This assumes the distribution's SSH journal integration is available.
Run sudo fail2ban-client -t before restarting the service, then use sudo systemctl restart fail2ban and sudo fail2ban-client status sshd. Inspect sudo journalctl -u fail2ban if the jail is missing or reports errors. An active service does not necessarily mean the intended jail is reading authentication events.
Test banning only with a separate source you control and a recovery route ready. Know the unban command: sudo fail2ban-client set sshd unbanip 198.51.100.25. Use the CIDR calculator again before adding trusted ranges to any ignore list; broad exemptions weaken protection.
Maintain updates, backups, and access reviews afterward. The offsite backup guide complements hardening, while the Web Hosting & Servers category covers related operational tasks.
Schedule periodic access reviews to remove unused public keys and confirm that remaining administrators still need their permissions.
Key takeaways
- Preserve console recovery and a working session throughout firewall and SSH changes.
- Test keys and sudo before disabling passwords or root access.
- Validate IPv4, IPv6, cloud firewalls, and container exposure separately.
- Check effective SSH settings and confirm Fail2ban is reading real events.
- Keep updates and recovery testing part of normal server maintenance.
Reach designers & developers at work
Put your product next to the tools people use every day. One sponsor per placement, no tracking pixels.
Become a sponsor