Securing a VPS: First Steps
Three steps to harden your linux servers.
So you've just provisioned a new server. It's clean, it's fast, and it's ready for whatever you want to build. You spend a few minutes poking around, maybe install a package or two, feeling good about the blank canvas in front of you. Then you check the logs.
Hundreds of failed login attempts. Random IP addresses from around the world, all trying to guess the root password. You've been online for less than an hour, and already you're under attack. Bots found your server before you even finished configuring it. Welcome to the internet. In this post, I'll show you the three simple steps you can take to harden a fresh server; disabling root, setting up two-factor SSH authentication, and configuring a firewall.
Stop Using Root
Most VPS providers let you set up you a root password when you provision the VPS. The root user is the most powerful account on Linux operating systems. It can do anything, delete anything, and consequently ruin everything. Hackers know this, which is why nearly all automated attacks target the username root. By disabling root user log in via SSH, you force an attacker to guess your custom username before they can even try to guess your password.
Creating a New User
- First, we create a new user account. Let's call them
newuserfor the purposes of this guide.
adduser and useradd. useradd is low-level and requires specific options to configure new user accounts while adduser is higher-level and more user friendly, creating a home directory and skeleton files by default. I prefer to use adduser for new accounts except for specific instances like writing scripts or creating system accounts.adduser newusercreate new user
- Next, we add this user to the
sudogroup so they can be granted temporary root privileges when needed. Theusermodcommand with the-aGoption does this:
usermod -aG sudo newuseradd user to sudo group
wheel instead of sudo.Its a good idea to verify you can log in as the new user before you proceed. In some cases, you might be required to reset the user's password using the passwd command.
passwd newuserChanging users password
- Now we can log out and log back in as
newuser:
ssh newuser@192.168.0.100log in with SSH (replace username and IP address with your own)
- Verify we can execute commands using
sudo:
sudo whoamiverify sudo command access
If it returns root, were good. From this point on, we'll use this account instead of root. Next, we'll disable root login entirely later when we configure SSH.
Disabling Root Login
Now that we have a working non-root user, we can disable root login entirely.
- Open the SSH daemon config:
sudo nano /etc/ssh/sshd_config- Find
PermitRootLoginsetting and set it tono:
PermitRootLogin nodisable root login via SSH
- Restart SSH to apply the change:
sudo systemctl restart sshrestart SSH daemon after verifying changes
- Let's verify it worked. We try logging in as root from a new terminal:
ssh root@192.168.1.10try logging in as root
You should see "Permission denied." SSH will now reject any login attempt to the root account, whether the password is correct or not.
Two-Factor Authentication
SSH authentication typically requires a valid SSH key or a password to log in. For additional security, we can configure our server to require a valid SSH key AND a valid password for the specified user. This enforces a degree of Multi-Factor Authentication that works without needing to setup additional PAM modules.
Multi-Factor Authentication is a security method that requires multiple different verification steps before granting access to a resource. In this case its something we have (SSH key file on the physical device) and something we know (password for the newuser account). If someone steals the physical device, they still need a valid password for the newuser account. If someone guesses the password, they still need the key file sitting on our physical device. Both have to be compromised for an attacker to gain access.
SSH Keys
SSH keys use public-key cryptography to authenticate SSH clients to SSH servers. With this method, SSH authentication uses an asymmetric public-private key pair:
- The private key is stored securely on your local machine. It acts as the physical key.
- The public key is uploaded to the server you want to access and typically placed in the
~/.ssh/authorized_keysfile. It acts like a lock only your private key can open.
Key Generation and Setup
Generate a dedicated key for each project instead of reusing the same one everywhere. That way, if a key is ever compromised, you only have to deal with one problem instead of ten.
- Run the following command in your terminal to generate an ed25519 ssh key pair in the
~/.ssh/folder on your device:
ssh-keygen -t ed25519 -f ~/.ssh/blog_server -C "newuser@blog"# -f names the file specifically and its location
# -C adds a comment so I know what this key is for later.
The ssh-keygen command will generate two files. ~/.ssh/blog_server which is the private key and ~/.ssh/blog_server.pub which is the public key. When generating the key, you'll be prompted for a passphrase. This is optional but recommended. The passphrase encrypts your private key, so even if someone steals the key file, they can't use it without the passphrase. If you forget the passphrase, the key is useless. There's no recovery. You'll need to generate a new key and update your servers.
- To avoid typing long commands every time, we'll create an alias in my SSH config for each host:
nano ~/.ssh/configopen SSH config file on client
- Paste this in the
~/.ssh/configfile and save:
Host myblog
HostName 192.168.1.10 # replace with your remote host's IP address
User newuser
IdentityFile ~/.ssh/blog_serverSSH host config file contents
Next, we'll configure the remote host, our server, to allow public key authentication to the server.
Allowing Public Key Authentication on the Remote Server
Before your key will work, the server needs to be configured to accept public key authentication. Most distributions enable this by default, but it's worth confirming before you go any further.
- Open the SSH daemon config:
sudo nano /etc/ssh/sshd_config- Look for this line:
PubkeyAuthentication yesIf it's commented out (starting with a #) or set to no, change it. This tells the server to check incoming connections against the keys stored in ~/.ssh/authorized_keys.
- If we changed
PubkeyAuthenticationfromnotoyes, we restart SSH to apply the change:
sudo systemctl restart sshrestart SSH daemon
Sending the SSH Public Key to the remote server
- Send the SSH public key key to the server. The
ssh-copy-idcommand handles this for you:
ssh-copy-id ~/.ssh/blog_server.pub myblogCopy ssh key to server
It connects to the server (using your password, since the key isn't there yet), then appends your public key to ~/.ssh/authorized_keys.
If SSH refuses your key, ensure your.sshfolder is set to chmod 700 andauthorized_keysischmod 600.
- Once that's done, test the connection:
ssh myblogConnecting to the remote server
If you get in without typing your password, the key is in place. If it still asks for a password, something went wrong. Check that PubkeyAuthentication is enabled and that your key file path is correct in ~/.ssh/config. Also check the contents of the ~/.ssh/authorized_keys file to verify your public key was copied there.
Locking It Down: Key + Password
To tie it all together, you need to configure the server to require BOTH the key and the password.
- Open the SSH daemon config file:
sudo nano /etc/ssh/sshd_config
- Find the settings below and make sure they look like this:
PermitRootLogin noverify again root login is disabled entirely
PubkeyAuthentication yesMake sure the server accepts public keys
PasswordAuthentication yesMake sure password authentication is also enabled
AuthenticationMethods publickey,passwordRequire BOTH key and password
That comma is where the magic happens. The comma means both required. A space would mean either works, which is not what we want.
- Restart SSH to apply the changes.
sudo systemctl restart ssh(On some distributions like Fedora or CentOS, the service is called sshd instead. If ssh doesn't work, try sshd.)
- Open a new terminal and connect with
ssh myblog.
ssh myblogrun this in a NEW terminal while keeping the original session open in the previous tab
If everything is configured correctly, you'll authenticate with your key first, then be asked for your password. If it only asks for one or the other, something's off. Double-check your sshd_config settings.
The Firewall (UFW)
At this point, we've locked down who can log in. But the server is still listening on every port, waiting for connections we don't need. My preferred default firewall for Linux servers is UFW (Uncomplicated Firewall). UFW comes preinstalled on most Debian-based distributions. If yours doesn't have it, install with the following command:
sudo apt install ufw
command to install ufw
The default firewall configuration tool for Ubuntu is ufw. Developed to ease iptables firewall configuration, ufw provides a user-friendly way to create an IPv4 or IPv6 host-based firewall.
ufw is not intended to provide complete firewall functionality via its command interface, but instead provides an easy way to add or remove simple rules. It is currently mainly used for host-based firewalls.
Ensure the firewall is disabled before we start making any changes to the configuration. If UFW is already enabled from a previous setup, disable it while we configure the rules:
sudo ufw disabledisable ufw firewall
Setting the Defaults
For an initial setup, the idea is straightforward. We want to block everything coming in, allow everything going out by default and make exceptions only for what we actually use. To configure this as the default behaviour, run the following commands.
- Disable all incoming traffic unless explicitly allowed:
sudo ufw default deny incomingblock all incoming traffic
- Your server still needs to reach the outside world for updates, API calls, and general internet access so allow outgoing traffic:
sudo ufw default allow outgoingallow outgoing traffic
Allow SSH Traffic
Before enabling the firewall, you need to allow SSH traffic. Skip this and you'll lock yourself out the moment you enable UFW.
sudo ufw allow sshallow SSH traffic through the UFW firewall
Verify the rules created by running this command:
sudo ufw statusview UFW firewall status and rules
You should see SSH listed as allowed (or your custom port), with default deny for everything else. After verifying the rules, activate the firewall with your configured rules:
sudo ufw enableenable UFW firewall
The Result
We started with a server that was wide open to the internet. Let's take a step back and look at what we've achieved so far.
- Root login is disabled, so the common and high-value attack vector is gone.
- SSH requires both a key and a password, which means an attacker would need to compromise two separate things at the same time to get in.
- The firewall blocks every port except the one we explicitly allowed (SSH).
None of this makes the server impenetrable, but it raises the bar high enough that most automated attacks will fail and attackers will move on to easier targets. The security foundation is solid. There's more we could add down the line like fail2ban, intrusion detection, regular audits... but those are for another post. Now let's go build something on it.
See you in the next log.