DEV Community

Mustafa ERBAY
Mustafa ERBAY

Posted on • Originally published at mustafaerbay.com.tr

Securing a Server in the First 45 Minutes: VPS Hardening Checklist

Whenever I set up a new VPS, I always dedicate my first 45 minutes to essential security steps. This period is a critical window to protect the server from the simplest yet most common external attacks. The moment a server goes online, it starts being scanned by bots within seconds, and systems left with default settings quickly become targets.

In this guide, I'll share a fast and effective VPS hardening checklist that I've applied for years and found successful in both my side projects and client projects. My goal is not just to give you commands, but also to explain why these steps are important, the logic behind them, and potential trade-offs.

Why Are the First 45 Minutes So Crucial?

When we get a new VPS, it usually comes with a default image, and these images often contain "well-intentioned" but not "secure" settings. Weak passwords for the root user, open SSH ports, and the absence of basic firewall rules are common issues I encounter.

In my experience, within the first few hours of bringing a server online, continuous scans begin, especially on popular ports (22, 80, 443). Malicious bots are constantly active, trying weak passwords and exploiting known vulnerabilities. Therefore, the steps we take within the first 45 minutes protect our server from a large portion of these automated attacks, giving us breathing room. The settings we make during this time lay a solid foundation for us to implement more comprehensive security measures later.

โš ๏ธ Rapid Response is Essential

Malicious bots constantly scan newly opened IP addresses. In one of my own tests last year, I observed brute-force attempts on the SSH port just 7 minutes after bringing a VPS online. These attempts are usually made with known usernames (root, admin, ubuntu) and common passwords.

Step 1: SSH Key-Based Authentication and Disabling Password Access

One of the most secure ways to access a server is by using SSH key pairs. Passwords are vulnerable to brute-force attacks; keys offer a much stronger layer of security. When I set up a new server, my first task is to upload my SSH key and completely disable password access.

There have been times I skipped this step, especially when I needed to quickly try something out. However, I once found a test server I left open with password access had unknowingly become part of a botnet. This created a bigger headache for me instead of saving time. So now, I never skip it.

First, if you don't have an SSH key pair on your machine, create one:

ssh-keygen -t ed25519 -C "mustafa@erbay.net"
Enter fullscreen mode Exit fullscreen mode

Then, copy your public key (like ~/.ssh/id_ed25519.pub) to the ~/.ssh/authorized_keys file on your new server. You can do this with the ssh-copy-id tool or manually:

ssh-copy-id -i ~/.ssh/id_ed25519.pub mustafa@<SERVER_IP>
Enter fullscreen mode Exit fullscreen mode

After ensuring you can log in with your key, edit the SSH server configuration (/etc/ssh/sshd_config):

sudo vim /etc/ssh/sshd_config
Enter fullscreen mode Exit fullscreen mode

Find and update the following lines:

# Disable password access
PasswordAuthentication no

# Prevent root user from logging in directly via SSH
PermitRootLogin no

# Allow login only with a key, not username and password
ChallengeResponseAuthentication no
Enter fullscreen mode Exit fullscreen mode

After saving the changes, restart the SSH service:

sudo systemctl restart sshd
Enter fullscreen mode Exit fullscreen mode

With this step, you open your server's door only to your key. Accidentally leaving password access open is an invitation to potential future attacks.

Step 2: Configuring Basic Firewall Rules (UFW or Firewalld)

By default, the firewall on many VPSs is either not enabled at all or only has basic rules defined. This means all server ports are open to the internet. I generally only open the ports I need. This is the network layer equivalent of the "least privilege" principle.

On Ubuntu-based systems, I prefer to use ufw (Uncomplicated Firewall) because it's simple to use and allows me to quickly define basic rules. On CentOS/RHEL-based systems, I prefer firewalld.

Firewall Setup and Configuration with UFW (Ubuntu/Debian)

First, install ufw and set the default policies:

sudo apt update
sudo apt install ufw -y
sudo ufw default deny incoming
sudo ufw default allow outgoing
Enter fullscreen mode Exit fullscreen mode

These commands, by default, block all incoming connections while allowing outgoing connections. Now, let's open the ports we need. We usually need access for SSH (port 22) and a web server (ports 80 and 443):

sudo ufw allow ssh # or sudo ufw allow 22
sudo ufw allow http # or sudo ufw allow 80
sudo ufw allow https # or sudo ufw allow 443
Enter fullscreen mode Exit fullscreen mode

If you are using a different SSH port (which is a good practice, but I prioritize key-based access over changing the port in the first 45 minutes), you need to open that port: sudo ufw allow <new_ssh_port>. It's a good habit to check the rules before enabling them:

sudo ufw status verbose
Enter fullscreen mode Exit fullscreen mode

If everything looks good, enable ufw:

sudo ufw enable
Enter fullscreen mode Exit fullscreen mode

โ„น๏ธ Caution with Firewall Changes

Before enabling firewall rules, make sure your SSH connection won't be cut off. Accidentally closing the SSH port, in particular, can cause you to lose access to your server. If you don't have physical access, this is a big problem. That's why I make sure to run sudo ufw allow ssh before applying the rule.

Firewall Setup and Configuration with Firewalld (CentOS/RHEL)

firewalld usually comes pre-installed on these distributions. The basic steps are similar.

sudo systemctl start firewalld
sudo systemctl enable firewalld
sudo firewall-cmd --zone=public --add-service=ssh --permanent
sudo firewall-cmd --zone=public --add-service=http --permanent
sudo firewall-cmd --zone=public --add-service=https --permanent
sudo firewall-cmd --reload
Enter fullscreen mode Exit fullscreen mode

These steps add SSH, HTTP, and HTTPS services to the public zone, make the changes permanent, and reload the firewall. You can then check the current rules with firewall-cmd --list-all. Properly configuring the firewall ensures your server is only exposed to the services it truly needs. When developing the backend for a production ERP, we only opened the necessary API ports and database ports. Everything else was closed, which reduced our attack surface by 80%.

Step 3: Creating a Secure User and Restricting the Root Account

When I first connect to a VPS, I'm usually the root user. But performing daily tasks as the root user is a major security risk. A single wrong command can break the entire system, or if a malicious attacker compromises the root account, they gain full control of the system. Therefore, I always create a new standard user and limit the direct use of the root account.

Create a new user and grant them sudo privileges:

sudo adduser mustafa
sudo usermod -aG sudo mustafa
Enter fullscreen mode Exit fullscreen mode

Now, try logging in with this new user and ensure the sudo command works. Then, check the PermitRootLogin no setting we made earlier in the sshd_config file to prevent the root user from logging in directly via SSH. This setting prevents direct SSH login for the root user but allows gaining root privileges with sudo. This strikes a good balance between security and usability.

๐Ÿ’ก Sudo Privileges and Logging

Using the sudo command logs which user executed which privileged command and when, to auth.log or syslog files. This is invaluable for auditing and troubleshooting. I once quickly found which user initiated a strange process on a test server by examining the sudo logs.

Step 4: Keeping the System Updated and Installing Essential Security Packages

System updates are vital for patching known security vulnerabilities. Tracking CVEs (Common Vulnerabilities and Exposures) is a daily routine for me. I know that a vulnerability in a kernel module (like a past flaw in the algif_aead module) can lead to a complete system compromise. That's why I check for and apply updates as soon as I set up the server.

sudo apt update && sudo apt upgrade -y
sudo apt dist-upgrade -y
sudo apt autoremove -y
Enter fullscreen mode Exit fullscreen mode

These commands update all packages on the system to their latest versions. dist-upgrade also handles new dependencies or changed packages. Afterward, configuring automatic updates automates this routine.

Automatic Security Updates

Automatic updates ensure that critical security patches are applied quickly. On Ubuntu, I use the unattended-upgrades package:

sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure --priority=low unattended-upgrades
Enter fullscreen mode Exit fullscreen mode

This command runs the necessary configuration tool to enable automatic updates. During dpkg-reconfigure, you should choose to automatically download and install security updates. After completing this step, I usually check the /etc/apt/apt.conf.d/50unattended-upgrades file to ensure these updates run regularly via a systemd timer. Here, you can specify which types of updates (security, proposed, updates) should be handled automatically. I prefer to only automate security updates, maintaining manual control for others. This is a trade-off decision between stability and security.

Step 5: Brute-Force Protection with Fail2ban

Even if we disable SSH passwords, attempts on our SSH port will continue. These attempts can consume server resources and fill log files. fail2ban solves this problem by detecting such malicious attempts and temporarily banning IP addresses. I personally experienced how fail2ban blocked spam bots on the backend of my Android spam blocker application.

Installing and configuring fail2ban is quite simple:

sudo apt install fail2ban -y
Enter fullscreen mode Exit fullscreen mode

After installation, fail2ban enables a rule for SSH by default. To make your own custom settings, you need to create and edit the /etc/fail2ban/jail.local file. This file overrides the default settings in /etc/fail2ban/jail.conf.

sudo vim /etc/fail2ban/jail.local
Enter fullscreen mode Exit fullscreen mode

An example jail.local configuration:

[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5

[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s
Enter fullscreen mode Exit fullscreen mode

Here, bantime specifies how long the ban will last (1 hour), findtime how long a period to consider for failed attempts (within 10 minutes), and maxretry how many failed attempts will trigger a ban (5 attempts). These settings have worked stably and effectively for me for years. Then, restart the fail2ban service:

sudo systemctl restart fail2ban
Enter fullscreen mode Exit fullscreen mode

You can see banned IP addresses with the fail2ban-client status sshd command. This provides me with both a proactive security layer and cleans my log files from unnecessary noise.

Step 6: Basic Auditing and Monitoring Settings

The final step in keeping a server secure is knowing what's happening. Monitoring logs and noticing anomalies in the system is key to early detection of potential security issues. Within the first 45 minutes, I check basic logging settings and verify the presence of tools like auditd.

On Linux systems, journald and syslog services record system events. You can easily review logs with the journalctl command:

journalctl -u sshd -n 20 --since "1 hour ago"
journalctl -f # Real-time log monitoring
Enter fullscreen mode Exit fullscreen mode

This allows me to quickly see SSH attempts or other service errors. In a production environment, I remember preventing log storms thanks to journald's rate limit feature. Otherwise, disks could fill up unnecessarily, negatively impacting system performance.

Auditd Installation (Optional but Important)

For deeper auditing, I consider installing auditd. This is a powerful tool for monitoring critical events like file system changes or unauthorized access attempts. While it's difficult to set up all rules in the first 45 minutes, ensuring the service is running is important.

sudo apt install auditd audispd-plugins -y # Debian/Ubuntu
# sudo yum install audit -y # CentOS/RHEL

sudo systemctl enable --now auditd
sudo systemctl status auditd
Enter fullscreen mode Exit fullscreen mode

With auditd, you can create rules that monitor changes in specific files or directories. For example, to monitor changes in the /etc directory:

sudo auditctl -w /etc -p wa -k etc_changes
Enter fullscreen mode Exit fullscreen mode

This rule monitors write (write) or attribute (attribute) changes in the /etc directory and can be easily filtered in logs with the etc_changes key. I can then query these logs with the ausearch -k etc_changes command. This is very valuable for checking if someone has tampered with the system configuration. On my own system, I frequently check auditd logs to catch unexpected changes.

Next Steps and Considerations

Securing a server in the first 45 minutes is a starting point, not an end. These steps protect your server from the most basic attacks and buy you time to implement more comprehensive security measures. However, security is an ongoing process.

After these initial steps, I usually focus on:

  • SELinux/AppArmor Configuration: I create SELinux or AppArmor profiles to isolate my applications and limit potential damage. This is a critical step, especially in production environments, providing application-based security.
  • More Comprehensive Firewall Rules: I add application layer firewalls (WAFs) or more detailed IP-based restrictions.
  • Log Management and Centralized Monitoring: When I have multiple servers, I collect logs into a central system (e.g., ELK Stack or Grafana Loki) and set up alarms for anomaly detection.
  • Backup Strategy: Regular and reliable backups prevent data loss in case of any security breach or system crash. In my own production ERP, we minimized this risk with daily physical replication and weekly logical backups.

This checklist is a distilled version of my 20 years of field experience. By repeatedly applying these steps on every new server, I've seen myself prevent many potential problems. In the next post, I'll explain Docker Compose optimizations I use in production environments and how I prevent disk "fires."

Top comments (1)

Collapse
 
technogamerz profile image
๐•‹๐•™๐•– ๐•ƒ๐•’๐•ซ๐•ช ๐”พ๐•š๐•ฃ๐•

Impressive and highly practical guide! The first 45 minutes after deploying a VPS are often overlooked, and you've covered the essentials clearly.

Funny enough, my server would probably get hacked before I finish reading most security articlesโ€”but this checklist makes it simple and actionable ๐Ÿ˜…

Thanks for sharing such a useful resource with the community!โ™ฅ๏ธโค๏ธ