New VDS checklist
Once you receive a new server, run through these steps in the first 30 minutes — they protect you from 95% of typical attacks.
Step 1: creating a user and SSH keys
# Create a user
adduser myuser
usermod -aG sudo myuser
# Copy the SSH key for the new user
rsync --archive --chown=myuser:myuser ~/.ssh /home/myuser/
Step 2: configuring SSH
# /etc/ssh/sshd_config
Port 2222 # Change the default port
PermitRootLogin no # Deny root login
PasswordAuthentication no # Keys only
PubkeyAuthentication yes
MaxAuthTries 3
systemctl reload sshd
Step 3: the UFW firewall
ufw allow 2222/tcp # Your SSH port
ufw allow http
ufw allow https
ufw enable
Step 4: automatic security updates
apt install unattended-upgrades -y
dpkg-reconfigure --priority=low unattended-upgrades
Step 5: fail2ban
apt install fail2ban -y
systemctl enable --now fail2ban
| Action | Time | Priority |
|---|---|---|
| Update the system | 2 min | Critical |
| Create a sudo user | 2 min | Critical |
| Configure SSH | 5 min | Critical |
| Configure UFW | 3 min | High |
| Install fail2ban | 2 min | High |
Diagnosing first-connection errors
The error Permission denied (publickey) when logging in as the new user means the key was not copied, or the file permissions are wrong. SSH flatly refuses to read a key if the directory or files are writable by anyone besides the owner.
debug1: Authentications that can continue: publickey
debug1: Trying private key: /home/myuser/.ssh/id_ed25519
Permission denied (publickey).
Check the permissions: ~/.ssh must be 700, and ~/.ssh/authorized_keys must be 600, both owned by the user, not root. The rsync --chown command from step 1 usually handles this, but after a manual copy the permissions must be set separately.
The error Connection refused after changing the SSH port means either the firewall has not opened the new port yet, or sshd has not reloaded its config. Order matters: open the new port in UFW first, then apply the sshd config, and only then close the old port — otherwise you risk locking yourself out entirely. Keep one SSH connection open until the new port is fully verified, in case something goes wrong.
Table: what is lost at each step if skipped
| Step | If skipped | Consequence |
|---|---|---|
| SSH keys | Password login stays enabled | Brute force cracks the password within hours |
| Changing the SSH port | Port 22 visible to everyone | Constant bot noise in auth.log, extra load |
| UFW | All ports open | Services that should not be public become reachable |
| fail2ban | No lockout on repeated attempts | The same IP hammers SSH without limits |
When this checklist is not enough
- The server handles payments or personal data — it needs a full audit against a standard like PCI DSS, not a general VDS checklist.
- The server hosts several independent clients or projects — it needs isolation through containers or separate system users with limited rights; basic hardening is not enough.
- The server is reachable from an untrusted network without a VPN — consider restricting SSH by IP through UFW, or closing the port entirely and connecting only through a VPN or bastion host.
- You need an audit trail of who did what on the server, not just protection against unwanted logins — the basic checklist does not cover command and file-change logging, which needs a separate tool such as auditd.
Which SSH key type to choose
Running ssh-keygen with no options creates an RSA-3072 key — a working choice, but not the best one. For a new server, generate a modern key explicitly: ssh-keygen -t ed25519 -C "admin@example.com".
| Key type | Length | When to use |
|---|---|---|
| ed25519 | 256 bits | Default for new servers, faster and more compact than RSA |
| RSA | 4096 bits | Only for compatibility with old software lacking ed25519 support |
Verifying the result
- In a separate second terminal, without closing the current session, connect again:
ssh -p 2222 myuser@server_ip— the login must succeed via the key without a password prompt. - Confirm root can no longer log in over SSH:
ssh root@server_ipmust fail with an access error. - Check the firewall status:
ufw status verbosemust show only the needed ports in theALLOWstate. - Confirm fail2ban is active and watching SSH:
fail2ban-client status sshd.
Further steps for limiting login attempts are in the article on configuring fail2ban, and a comparison of firewall tools is in the article on UFW on a VPS. If after basic hardening you also need to log user actions at the kernel level, see the article on setting up auditd for Linux monitoring — that one covers ongoing server control, not first steps.