Key Principles of Securing a Linux Server: How to Protect Your VDS

Key Principles of Securing a Linux Server: How to Protect Your VDS

Dmitrii Kriukov

Nowadays, owning a personal Linux-based server - especially a virtual dedicated server (VDS) - opens up a wide range of possibilities for hosting projects, managing data, and running various tasks. However, like any device connected to the internet, your VDS is at risk of cyberattacks and unauthorized access. A personal server, even if not owned by a company, still requires thorough protection to ensure data security and stable operation.

In this article, I’ll walk you through step-by-step how to secure a Linux server. This is a minimal but effective set of settings that I personally apply to all my machines - both at home and on remote VDS setups.

Update System Abstract

1. Regular System and Software Updates

Updating is the first and most crucial step. Developers quickly patch vulnerabilities, but without regular updates, your server remains at risk. Updates often include critical security fixes.

• Manual update - updates the list of packages and installs available updates

sudo apt update && sudo apt upgrade -y
Manual Update & Upgrade System


Delete Root User Abstract

2. Restricting Access Rights

One of the first steps after installing the server and connecting via SSH should be to create a separate user with limited privileges and disable direct access for the root user.

• Creating a user - creates a new system user

sudo adduser example
Make User

In Linux, access rights often depend on group membership.

• Adding user to the sudo group - gives the new user administrative privileges

sudo usermod -aG sudo example
Add User to Sudo Group

This grants the new user administrative rights equivalent to root.

The next step is to disable root login. This is crucial for security: if an attacker knows that "root" definitely exists on the server, they only need to guess the password or key. But if root login is disabled, then even with the correct IP and username, they won’t be able to log in directly.

This eliminates an obvious attack target and forces attackers to guess actual usernames, which is far more difficult.

• Disable root login - open SSH configuration file

sudo nano /etc/ssh/sshd_config
Open SSHD Config In Editor

• Find the line indicating root login permission

PermitRootLogin yes
Root Login Yes

• Change it to deny root login

PermitRootLogin no
Root Login No

• Restart SSH service to apply changes

sudo systemctl restart sshd
Restart SSH Daemon

Now, SSH access will only be possible through the new user, and root will be protected from direct login.

⚠️ Important: After restarting the SSH service, don’t exit your current session! ⚠️

First, open a new terminal tab and try connecting to the server using the new user. Make sure it works and that the user has the necessary permissions.


SSH KeyGen Abstract

3. Using SSH Keys

One of the most important steps in securing a Linux server is to disable password-based login and switch to SSH key authentication. This not only boosts security but also eliminates the need to enter a password every time - unless you're using a passphrase.

SSH keys come as a pair of files:

  • Public key (id_ed25519.pub) — can be shared and placed on servers.
  • Private key (id_ed25519) — must be kept secret and stored locally.

The private key protects access to the server. Even if someone knows the IP and username, they can’t log in without the key.

SSH keys are generated on your local machine (the one you use to connect to the server)

ssh-keygen -t ed25519 -C "your_email@example.com"
SSH KeyGen

-t ed25519 - specifies the key type. Ed25519 is modern and secure, recommended for most uses.

-C "your_email@example.com" - adds a comment so you can easily identify what the key is for or who created it (e.g., email or device name).

During the generation process, you’ll be prompted to answer two questions:

Enter file in which to save the key - press Enter to save to ~/.ssh/id_ed25519.

Enter passphrase - create a passphrase to further protect the private key. You can leave this field empty if you want to use the key without a password - but this is insecure: if your device is compromised, an attacker will be able to connect to the server without restrictions.

You can add your SSH key to the server in two ways:

• Automatic method using one command

ssh-copy-id username@your_server_ip
Auto Copy SSH Key

This command will copy the contents of your public key to the server and place it in the ~/.ssh/authorized_keys file.

• If ssh-copy-id isn’t available (e.g., on minimal systems), you can do it manually

cat ~/.ssh/id_ed25519.pub
Print Public Key

Copy the output — it’s a long line starting with ssh-ed25519.

• Then

ssh username@your_server_ip
Connect to server via SSH

• On the server, create the .ssh directory if it doesn’t already exist:

mkdir -p ~/.ssh && chmod 700 ~/.ssh
Make SSH Dir & Permissions

mkdir -p - creates the directory if it doesn’t exist.

chmod 700 - sets the permissions (accessible only to the user).

• Now add the copied public key to the ~/.ssh/authorized_keys file. Paste it as a single line, save, and exit

nano ~/.ssh/authorized_keys
Open SSH Keys Config in Editor

• Set the correct permissions

chmod 600 ~/.ssh/authorized_keys
Make Permissions

The final step is to disable password authentication altogether, so attackers can’t even attempt to guess your password

• Open the SSH configuration file

sudo nano /etc/ssh/sshd_config
Open SSH Config In Editor

• Find the line

PasswordAuthentication yes
Password Auth Yes

• And change it to

PasswordAuthentication no
Password Auth No

• Then restart the SSH service

sudo systemctl restart sshd
Restart SSH Daemon

⚠️ Important: Before disabling password authentication, make sure your SSH key works! Otherwise, you might lose access to the server. ⚠️


OTP App Abstract

4. Two-Factor Authentication (OTP)

For additional protection of SSH access to your server, you can set up two-factor authentication using One-Time Passwords (OTP). This means that even if an attacker has your password or SSH key, they still won’t be able to log in without the code generated on your phone.

• Update package lists

sudo apt update
Update Packages

This command should be run before installing any new packages to refresh the list of available packages from the repositories (in our case, Ubuntu).

• Install the libpam-google-authenticator package

sudo apt install libpam-google-authenticator
Install Google OTP Package

• Launch the Google Authenticator setup

google-authenticator
Open Google OTP Installer

• The setup process will include several questions:

Do you want authentication tokens to be time-based (y/n) y
Google OTP Installer Question 1

Tokens will be refreshed every 30 seconds, just like in the Google Authenticator app. This is the most secure and standard option.

Next, the terminal will display a link/QR code and your Secret Key. After scanning the QR code or entering the Secret Key manually in the Google Authenticator app on your phone, you will see a six-digit code that changes every 30 seconds. During this window, you need to enter this code in the prompt:

Warning: pasting the following URL into your browser exposes the OTP secret to Google: https://www.google.com/exampleurl Your new secret key is: XXXXXXXXXXXXXXXXXXXXXXXXX Enter code from app (-1 to skip): XXXXXX
Google OTP Installer Question 2

After entering a valid code, you will see a message

Code confirmed Your emergency scratch codes are: 12345678 87654321 47329483 76532923 45678345
Google OTP Installer Question 3

Emergency codes - these are backup codes. Use them if you lose access to your phone. Save them in a safe place! I recommend storing them in tools like KeePassXC.

The next question will be:

Do you want me to update your "/home/exampleUser/.google_authenticator" file? (y/n) y
Google OTP Installer Question 4

This saves the settings to a file so that the system knows you are now using 2FA for login.

Then you’ll be asked about disallowing reuse of the same code:

Do you want to disallow multiple uses of the same authentication token? This restricts you to one login about every 30s, but it increases your chances to notice or even prevent man-in-the-middle attacks (y/n) y
Google OTP Installer Question 5

This prevents reuse of the same code. It enhances protection from attacks but imposes a minor restriction — one login every 30 seconds.

Next, you’ll see a question about increasing the time delay window

By default, a new token is generated every 30 seconds by the mobile app. In order to compensate for possible time-skew between the client and the server, we allow an extra token before and after the current time. This allows for a time skew of up to 30 seconds between authentication server and client. If you experience problems with poor time synchronization, you can increase the window from its default size of 3 permitted codes (one previous code, the current code, the next code) to 17 permitted codes (the 8 previous codes, the current code, and the 8 next codes). This will permit for a time skew of up to 4 minutes between client and server. Do you want to do so? (y/n) n
Google OTP Installer Question 6

This extends the allowed time skew between server and client (up to ±4 minutes instead of ±30 seconds). Use this if your time is not properly synchronized.

The final question is about enabling brute-force protection

If the computer that you are logging into isn't hardened against brute-force login attempts, you can enable rate-limiting for the authentication module. By default, this limits attackers to no more than 3 login attempts every 30s. Do you want to enable rate-limiting? (y/n) y
Google OTP Installer Question 7

Final Step: Enabling 2FA for SSH

Once you’ve configured Google Authenticator for your user, you need to enable OTP code checking during SSH login

Enable the PAM module for Google Authenticator

• Open the PAM configuration for SSH

sudo nano /etc/pam.d/sshd
Open PAM Config in Editor

• Add the following line at the top of the file (if it doesn’t already exist)

auth required pam_google_authenticator.so
Enable 2FA in Config

This line enables 2FA when logging in via SSH

Enable challenge-based authentication

• Open the main SSH configuration file

sudo nano /etc/ssh/sshd_config
Open SSH Config in Editor

Make sure the following parameters are set

PasswordAuthentication no KbdInteractiveAuthentication yes UsePAM yes AuthenticationMethods publickey,keyboard-interactive
Checking Important Parameters

If any of the parameters are missing — add them manually

Note: On Ubuntu, all configs from the sshd_config.d folder are also included in the main SSH configuration file

Include /etc/ssh/sshd_config.d/*.conf
Second SSH Config

⚠️ This means any line in these files can override your main SSH config. ⚠️

• Go through the files in /etc/ssh/sshd_config.d/ and make sure they match your main SSH config, especially these lines:

PasswordAuthentication no KbdInteractiveAuthentication yes UsePAM yes
Checking Important Parameters

• Validate the config before restarting

sudo sshd -t
Checking syntax in Config

• If nothing is printed - the syntax is valid and you can restart the service:

sudo systemctl restart sshd
Restart SSH Daemon


Fail2Ban Abstract

5. Fail2Ban – Protection from SSH Brute Force Attacks

Fail2Ban is a service that monitors logs (for example, /var/log/auth.log) and automatically blocks IP addresses that attempt to guess the SSH password too frequently.

It’s ideal for protecting your server against random scanners and bots - they often begin probing port 22 just a few minutes after your machine goes online.

• Run package update and install Fail2Ban

sudo apt update && sudo apt install fail2ban -y
Update & Install Fail2Ban Package

• Then enable and start the service

sudo systemctl enable fail2ban sudo systemctl start fail2ban
Enable & Start Fail2Ban Service

Fail2Ban will now automatically start on boot and, by default, already monitors /var/log/auth.log.

• Check the service status

sudo systemctl status fail2ban
Fail2Ban Service Status

To configure Fail2Ban flexibly (define which services to protect, how many attempts are considered suspicious, how long to ban an IP, etc.) — we create a configuration file jail.local.

Important: Do not edit jail.conf directly — it may be overwritten when the package is updated. All custom settings should be placed in jail.local, which has priority.

• Create the file

sudo nano /etc/fail2ban/jail.local
Make Config for Fail2Ban

• Example of a minimal SSH configuration

[DEFAULT] # IP ban duration bantime = 1h # Time window to count failed attempts findtime = 10m # Number of allowed failed attempts. maxretry = 3
backend = auto ignoreip = 127.0.0.1/8 ::1 destemail = root@localhost sender = root@yourserver.local mta = sendmail banaction = iptables-multiport action = %(action_)s
[sshd] enabled = true port = ssh logpath = %(sshd_log)s
Minimal Configuration for Fail2Ban

• After saving the configuration — restart the services

sudo systemctl restart sshd sudo systemctl restart fail2ban
Restart Daemons

• To check how many IPs are currently banned

sudo fail2ban-client status sshd
Fail2Ban Status SSH

• Example output of this command

Status Output


UFW Abstract

6. UFW – Basic Firewall Setup

UFW (Uncomplicated Firewall) is a user-friendly wrapper over iptables that allows you to manage incoming connections to your server. With it, you can easily allow or block access by port, limiting potential attack surfaces.

On most servers, at least SSH (port 22) is open, and sometimes other services like HTTP, HTTPS, FTP, etc. Leaving everything open is a bad idea. Even if you don’t host a website, bots and scanners will still try to connect to your machine.

In most modern Ubuntu and Debian distributions, UFW comes pre-installed by default, so you don’t need to install it manually.

• Check if UFW is active

sudo ufw status
UFW Status

• If the command output shows

Status: inactive
UFW Status Inactive

This means UFW is installed, but not enabled.

UFW works on the principle of “deny everything, allow what you need”, and that’s exactly how it should be configured.

# Deny all incoming connections sudo ufw default deny incoming
Deny Rule
# Allow all outgoing connections (e.g., for updates and pings) sudo ufw default allow outgoing
Allow Rule
# Allow SSH access. sudo ufw allow 22/tcp
Allow 22 SSH Rule
# Allow HTTPS access (if you host a website). sudo ufw allow 443/tcp
Allow 443 HTTPS Rule
# Enable the firewall sudo ufw enable
UFW Enable

⚠️ After enabling UFW, do not close your current SSH session. Instead, open a new one and verify that you can still connect to the server. This helps avoid accidentally locking yourself out. ⚠️

• Check current rules

sudo ufw status verbose
UFW Status Verbose

• Example output of this command

UFW Rule List

• To deny a specific port

sudo ufw deny 21/tcp    # FTP (if not used)
Deny 21 FTP Rule

• To deny a specific IP address

sudo ufw deny from 1.2.3.4
Deny IP Rule

• To allow access to a port only from your IP

sudo ufw allow from 1.2.3.4 to any port 22 proto tcp
Allow IP to 22 SSH Rule

• If you added something by mistake, show a numbered list of rules

sudo ufw status numbered
UFW Status Numbered

• Select the rule to delete and remove it with

sudo ufw delete 1
UFW Delete Numbered Rule

UFW can also be configured via files in /etc/ufw, but for basic protection, the CLI is sufficient. Under the hood, UFW uses iptables, and just makes managing it easier.

Report Page