Basic VPS Configuration
Table of Contents
- Step 0: Prerequisites
- Step 1: First Connection and System Update
- Step 2: Create a New User and Disable Root SSH Login
- Step 3: Changing the Default SSH Port (Optional)
- Step 4: Setting Up a Basic Firewall (UFW)
- Step 5: Setting Up Fail2Ban for Attack Protection
- Step 6: Timezone, Locale, and Monitoring Setup
- Step 7: (Optional, but Recommended) Setting Up Backups
- Step 8: Running a Security Audit
- Step 9: Enabling Logging for All sudo Commands
- Step 10: Setting Up Automatic Security Updates
- Step 11: Setting Up a Strong Password Policy
A checklist of basic settings you should apply right after spinning up a new server.
Imagine you’ve just spun up a new VPS server. Ready to use it as a reliable gateway for your home servers? The first few minutes of a server’s life are the most critical from a security standpoint. Freshly created cloud servers are a favorite target for automated scanners and bots.
In this guide we’ll walk step by step through the necessary and sufficient minimum of initial Ubuntu Server setup that will turn a “bare” system into a convenient, well-protected outpost for your services. Our goal isn’t just to protect it from attacks, but also to make working with it comfortable.
Step 0: Prerequisites
- A VPS server running Ubuntu Server 22.04 LTS or 24.04 LTS.
- Root access to the server (the login and password your hosting provider gave you, or an SSH key).
- An SSH client on your computer (the standard terminal usually works fine).
Step 1: First Connection and System Update
First, let’s connect to the server. If the provider gave you a root password, the command looks like this (replace your_server_ip with the actual IP address):
ssh root@your_server_ip
You’ll get a prompt to confirm the fingerprint. Answer “yes” to trust it, then enter the password.
Right after logging in:
-
Update the package index and install updates:
apt update && apt upgrade -yThis makes sure all system components and libraries have the latest security patches.
-
Install some basic useful utilities:
apt install -y curl wget nano ufw fail2ban· curl, wget — for downloading files from the internet. · nano — a simple text editor for beginners (you can use vim instead if you’re familiar with it). · ufw — a firewall for easily managing network traffic. · fail2ban — a system for blocking brute-force attacks (password guessing).
-
Reboot the server (if the update touched the kernel):
rebootWait a minute, then reconnect.
Step 2: Create a New User and Disable Root SSH Login
Working as root is bad practice. Let’s create a new user with elevated privileges.
-
Create a user (replace username with your actual username):
adduser usernameSet a strong password and fill in the information (you can skip this part).
-
Add the user to the sudo group: this gives them permission to run commands with root privileges.
usermod -aG sudo username -
Set up SSH keys for secure login (recommended): · On your computer (not on the server!), generate an SSH key pair if you don’t already have one:
ssh-keygen -t ed25519· Copy the public key to the server for your new user:
ssh-copy-id username@your_server_ip(On Windows, without ssh-copy-id, you can copy the contents of ~/.ssh/id_ed25519.pub and manually add it to the ~/.ssh/authorized_keys file on the server.)
-
Configure the SSH daemon for better security: open the config file:
nano /etc/ssh/sshd_configFind and change the following directives (if the lines are commented out with #, uncomment them):
PermitRootLogin no # Disable root login PasswordAuthentication no # Disable password login, key-based only PubkeyAuthentication yes # Allow key-based authenticationWarning! Make sure your SSH key is copied over and working before restarting SSH! Otherwise you’ll lock yourself out, and you’ll have to get back in through your hosting provider’s admin panel.
-
Reload the SSH service to apply the changes:
systemctl reload sshdNow open a new terminal window and try connecting as the new user: ssh username@your_server_ip. If it works, you can close the root session.
Step 3: Changing the Default SSH Port (Optional)
Changing the default SSH port isn’t about real security. In this case, security comes from obscurity (security through obscurity). Changing the port doesn’t remove the need for SSH keys and Fail2Ban. It’s an extra, very easy-to-implement layer that significantly cuts down the noise from automated scanners.
Is it safe to leave port 22 as-is? Yes, if and only if you’ve already implemented:
- A full ban on password login (PasswordAuthentication no).
- SSH-keys-only authentication (PubkeyAuthentication yes).
- A ban on root login (PermitRootLogin no).
- Protection via Fail2Ban.
Under these conditions, leaving port 22 open is a perfectly safe choice. Automated bots scanning port 22 will just get their authentication attempts rejected and walk away empty-handed.
Even though it doesn’t add any cryptographic strength, I still strongly recommend changing the default port. It brings some practical benefits:
- A sharp drop in log “noise.” Your
/var/log/auth.loglogs and theFail2Banban table won’t get cluttered with thousands of attempts from primitive bots that only scan port22. That makes log analysis easier and cuts a small but pointless load on the system. - It makes targeted attacks harder. An attacker deliberately going after your system will still find the
SSH portby scanning the full port range (Nmap). But that’s no longer a low-level script bot — it’s a more advanced attack. You cut off99.9%of automated junk.
Downside:
· Convenience. You always have to specify the non-standard port when connecting: ssh -p 12345 user@server.ip. That said, it’s easily solved by configuring the SSH config on your client machine.
Which port should you pick? Best practices.
The main rule: don’t pick a port that’s already used by another well-known service (for example, 80 — HTTP, 443 — HTTPS, 21 — FTP), to avoid future conflicts.
A good choice:
· Ports in the 1024-49151 range (registered/user ports).
· Avoid “trendy” non-standard SSH ports like 2222, 22222, 22225. Those get scanned en masse too, because their popularity has made them just as obvious.
· Pick a random, unremarkable number.
Examples of good ports: 53982, 28465, 49123, 13574
Examples of bad ports: 22 (default), 2222 (obvious), 22222 (obvious), 9022 (obvious variation), 21, 80, 443 (conflicting).
How to change the port
-
Connect to the server and open the SSH config file:
sudo nano /etc/ssh/sshd_config -
Find the line #Port 22. Uncomment it (remove the # comment mark) and change the port number to the one you picked earlier.
Port 53982 # Replace 53982 with your own port -
Save the file and reload the SSH service.
sudo systemctl reload sshWARNING! Don’t close your current SSH session! Make sure the new connection works first.
-
Open a new terminal window and try connecting to the server using the new port.
ssh -p 53982 username@your_server_ipIf the connection succeeds, you can close the old session.
Step 4: Setting Up a Basic Firewall (UFW)
We’ll only allow the most essential ports: SSH for management, and later on, a port for the network linking our local machines with the remote server.
-
Allow
SSHconnections: it’s important to do this before enabling the firewall.ufw allow OpenSSHIf you’re planning to use a non-standard port for
SSH, specify it explicitly:ufw allow 2222/tcp(example for port 2222).- If you’ve already changed the default SSH port, update the
UFWfirewall rules. First remove the oldOpenSSHrule (which opened port22), then add the new one.
sudo ufw delete allow OpenSSH # Or just 'ufw delete allow 22/tcp' sudo ufw allow 53982/tcp # Allow the new port sudo ufw status # Make sure port 22 is closed and the new one is open - If you’ve already changed the default SSH port, update the
-
Enable the firewall:
ufw enableAnswer y. The firewall is now active and already blocking all disallowed incoming connections.
-
Check the status:
ufw status verboseYou should see that only
OpenSSHor the newSSHport is allowed.
Step 5: Setting Up Fail2Ban for Attack Protection
Fail2Ban will monitor SSH logs and automatically block IP addresses that try to brute-force the password.
-
Create a simple config for
SSH: copy the defaultjailsettings file:cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.localEdit it:
nano /etc/fail2ban/jail.localIn the [sshd] section, make sure the settings look like this:
[sshd] enabled = true port = ssh logpath = %(sshd_log)s backend = %(sshd_backend)s maxretry = 3 bantime = 1h findtime = 1hmaxretry= 3: the number of failed attempts before a ban.bantime= 1h: the ban duration (1 hour). You can set -1 for a permanent ban.findtime= 1h: the time window in which failed attempts are counted.
-
Start and enable
Fail2Ban:systemctl enable fail2ban --now -
Check the status:
fail2ban-client status sshdYou’ll see a list of banned
IPaddresses, if there have already been attacks. -
(Important!) Update the
Fail2Banconfig if you’ve changed the defaultSSHport.Fail2Banwatches port22by default. You need to tell it to watch the new port instead.- Edit the
Fail2Banconfig forSSH:
sudo nano /etc/fail2ban/jail.local- In the [sshd] section, find the
portparameter and change it:
[sshd] enabled = true port = 53982 # Put your new port here! # ... rest of the settings (maxretry, bantime) ...- Restart
Fail2Ban:
sudo systemctl restart fail2ban - Edit the
Step 6: Timezone, Locale, and Monitoring Setup
-
Set the correct timezone:
sudo timedatectl set-timezone Europe/Moscow # Replace with your own timezone -
Set the locale you need (system language):
sudo dpkg-reconfigure localesChoose en_US.UTF-8 UTF-8 (recommended for servers) or ru_RU.UTF-8 UTF-8.
-
Install resource monitoring utilities:
sudo apt install -y htophtop is a great replacement for the standard top, letting you interactively watch CPU load, RAM usage, and processes.
Step 7: (Optional, but Recommended) Setting Up Backups
Your VPS is important infrastructure. Its failure shouldn’t turn into a catastrophe. That’s why I strongly recommend setting up backups for critical directories (/etc, /home, /root). You can use the built-in backup utilities your provider offers, or set up your own storage and ship the data to, say, S3.
Step 8: Running a Security Audit
There are tools out there that can run a basic security audit of your server. For example, there’s a bash script called VPS Audit. Running it gives you a report analyzing the system. The report is saved to a separate file so you can go back and review it later.
The analysis covers not only the points we’ve touched on here, but also less obvious things, like whether critical packages get updated automatically, or whether a password policy rule is set up for users. Here’s the list of checks it runs:
System Restart— checks whether the server needs a reboot after an updateSSH Root Login— checks whether root login via SSH is enabledSSH Password Auth— checks whether password-based SSH authentication is enabledSSH Port— checks the SSH port; if it’s set to 22, changing it is recommendedFirewall Status (UFW)— checks the status of the UFW firewallUnattended Upgrades— checks whether security packages are updated automaticallyIntrusion Prevention— checks for the presence of Fail2ban or CrowdSec, plus their activity statusFailed Logins— checks the number of failed login attemptsSystem Updates— checks whether all system packages are up to dateRunning Services— checks the number of running services; more than 16 isn’t greatPort Security— checks open ports; having too many isn’t great eitherDisk Usage— checks available disk spaceMemory Usage— checks how much RAM is usedCPU Usage— checks CPU load; high load is also worth a second lookSudo Logging— checks whether logging of sudo commands is enabled or disabledPassword Policy— checks the password policy configurationSUID Files— searches for the presence of SUID files
Step 9: Enabling Logging for All sudo Commands
To enable logging, you need to edit the sudo config file. To do that, just run the command
sudo visudo
This opens an editor with the utility’s config. By default it opens in nano. After that, you need to add the following lines, right after the block with all the Defaults settings
Defaults logfile=/var/log/sudo.log
Defaults log_input,log_output
Step 10: Setting Up Automatic Security Updates
To set up automatic security updates on most Linux distros, it’s enough to install and configure unattended-upgrades (for Debian/Ubuntu distros) or dnf-automatic (for Fedora/RHEL), and enable them by configuring the services or timers themselves. You can later customize these utilities to update only security packages.
Installing and configuring for Debian/Ubuntu
-
Install the
unattended-upgradespackagesudo apt update sudo apt install unattended-upgrades -
Now configure the utility
sudo dpkg-reconfigure --priority=low unattended-upgradesA confirmation dialog will pop up where you’ll need to answer
yesto enable automatic updates. -
Check the configuration. You can do this by running an update in
--dry-runmodesudo unattended-upgrades --dry-run --debug
Installing and configuring for Fedora and RHEL
-
Install the
dnf-automaticpackagesudo dnf install dnf-automatic -
Configure the
dnf-automatic.confconfig filesudo nano /etc/dnf/automatic.confthere you need to add the following lines
apply_updates = yes upgrade_type = security -
Enable and start the timer
sudo systemctl enable --now dnf-automatic.timer -
And check its status
systemctl status dnf-automatic.timer
Beyond that, you can also configure:
- Email alerts. On both systems you can enable alerts about what happened. To do that, edit the
/etc/apt/apt.conf.d/50unattended-upgradesfile and set an address to send messages to the sysadmin. - Automatic reboot. You can also set this up, but be careful here — it’s not a good idea to enable this in a production environment.
- You should also keep an eye on the log file after updates. For example, for
unattended-upgradesit’s located at/var/log/unattended-upgrades.
Step 11: Setting Up a Strong Password Policy
Pluggable Authentication Modules (PAM) are installed by default on DEB-based systems (Ubuntu/Debian). However, you’ll need to install an extra module, libpam-cracklib.
sudo apt install libpam-cracklib
On Debian-based systems, password policies are defined in the /etc/pam.d/common-password file. Before making any changes to it, it’s best to back the file up.
sudo cp /etc/pam.d/common-password /etc/pam.d/common-password.bak
Now edit the /etc/pam.d/common-password file:
sudo nano /etc/pam.d/common-password
Find the following line and edit it; if it doesn’t exist, you’ll need to create it.
password required pam_cracklib.so try_first_pass retry=3 minlen=12 lcredit=1 ucredit=1 dcredit=2 ocredit=1 difok=2 reject_username
What each option means
- try_first_pass retry=N – the maximum number of retries when changing a password. N is a number. The default value is 1.
- minlen=N – the minimum allowed length for a new password (plus one, if credits aren’t disabled, which is the default). In addition to the character count of the new password, each character type (other, uppercase, lowercase, and digit) earns a credit (+1 to length). The default value is 9.
- lcredit=N – sets the maximum credit for having lowercase letters in the password. The default value is 1.
- ucredit=N – sets the maximum credit for uppercase letters in the password. The default value is 1.
- dcredit=N – sets the maximum credit for digits in the password. The default value is 1.
- ocredit=N – sets the maximum credit for other characters in the password. The default value is 1.
- ifok=N – sets the number of characters that must differ from the previous password.
- reject_username – forbids users from using their username as their password.
Users will now need to use a password with a complexity score of 12. One “credit” is given for 1 lowercase letter, 1 credit for 1 uppercase letter, 1 credit for at least 2 digits, and 1 credit for 1 other character.
You can also disable credits by assigning them negative values, forcing the user to use a combination of different character types of a minimum length.
Let’s look at an example:
password required pam_cracklib.so minlen=8 lcredit=-1 ucredit=-1 dcredit=-2 ocredit=-1 difok=2 reject_username
As defined above, users must use an 8-character password that includes 1 lowercase letter, 1 uppercase letter, 2 digits, and 1 other character. Note that these restrictions only apply to regular users, not to root. The root user can use any type of password.
Checking Password Complexity
After setting the policy itself, you need to check whether it actually works. Let’s assign a simple password that doesn’t meet the policy and see what happens. To change or set the password for the current user, run:
$ passwd
Example output:
Changing password for myuser.
Current password:
New password:
BAD PASSWORD: it does not contain enough DIFFERENT characters
New password:
BAD PASSWORD: it is based on a dictionary word
New password:
Retype new password:
BAD PASSWORD: is too simple
New password:
BAD PASSWORD: is too simple
New password:
BAD PASSWORD: is too simple
passwd: Have exhausted maximum number of retries for service
passwd: password unchanged
So the user can’t set the password because it doesn’t meet the requirements.