Back to Blog

The Complete Server Security Checklist for a New VPS

Whether you just spun up a server on Hetzner, DigitalOcean, Linode, Vultr, AWS Lightsail, or any other VPS provider, this guide is for you. A new VPS can be serving traffic in minutes. Install the control panel, add your domains, upload your sites, grab an SSL cert. Done. Everything works.

But “works” and “secure” are not the same thing. Every major provider, from Hetzner Cloud to OVH to Google Cloud to Azure, hands you a running server with permissive defaults. Ports are open, firewalls are not configured, admin panels are public, and nobody is watching. It is on you to lock it down.

This post is every check we run when we set up a new production server, organized into a guide you can follow yourself.

The Firewall: Check This First

This is the single most important thing to verify, and the one most people skip because they assume it is already handled. A surprising number of fresh VPS installations have no active firewall rules at all. Every iptables or nftables chain set to ACCEPT, with empty rulesets. That means every listening port on the machine is reachable from the public internet.

Your control panel might have a firewall extension that is installed but not enabled. Or it might not be installed at all. Do not assume. Run iptables -L -n or nft list ruleset and actually read the output. If the default policy is ACCEPT with no rules, you have no firewall.

Install and configure your panel’s firewall extension, or set up nftables manually. Allow only what you need: HTTP (80), HTTPS (443), your SSH port, and mail ports if applicable. Block everything else by default. That single change covers more ground than everything else in this guide combined.

Lock Down the Ports

Once the firewall is in place, audit what is actually listening. Run ss -tlnp and go through the list.

FTP (port 21) should not be open in 2026. FTP transmits credentials in plaintext. Even if the FTP service is “inactive,” check whether a systemd socket is listening. Socket-activated services wake up on connection, so an inactive service can still accept traffic. Disable the socket and block the port.

Default SSH (port 22) is the first thing bots scan. If you moved SSH to a custom port, make sure port 22 is actually closed. Many configurations leave both ports listening, which doubles the attack surface for no benefit.

Admin panels like Plesk (8443/8880), phpMyAdmin, or Webmin should never be open to the world. Restrict them to your IP in the firewall.

Unencrypted mail ports (110 for POP3, 143 for IMAP) should be blocked if you offer encrypted alternatives (993/995). There is no reason to accept plaintext mail connections.

After making changes, verify with an external port scan. Do not trust what the server reports about itself.

Harden the Web Layer

Every site on the server needs proper HTTP security headers. Run curl -sI https://yourdomain.com on each domain and check for these:

  • Strict-Transport-Security (HSTS): Forces HTTPS
  • X-Frame-Options: Prevents clickjacking
  • X-Content-Type-Options: Prevents MIME-type sniffing
  • Referrer-Policy: Controls what gets sent in the Referer header
  • Permissions-Policy: Restricts browser features (camera, microphone, geolocation)

It is common for some sites to have full headers while others on the same server have none. Check every domain and every subdomain. Add them in your web server config (nginx, Apache) or via .htaccess.

While you are checking headers, look for information leaks. If your responses include X-Powered-By: PHP/8.x.x or Server: Apache/2.4.x, you are telling attackers exactly what version you are running and which CVEs to try. Set expose_php = Off in php.ini, server_tokens off in nginx, or ServerTokens Prod in Apache.

WordPress-Specific Checks

If you host WordPress sites, there are a few things that tend to slip through.

Check wp-config.php permissions on every installation. This file contains your database credentials and should be readable only by the file owner (mode 600). Run ls -la wp-config.php in each WordPress root. If it shows -rw-r--r-- (644), fix it with chmod 600.

Check whether wp-login.php is publicly accessible. Exposed login pages are brute-force targets. Options include IP restriction, a login URL plugin, or HTTP auth in front of the login page.

Register every WordPress installation in your panel’s toolkit (Plesk WP Toolkit, cPanel WP Manager, or similar). Unregistered installations do not get managed updates, vulnerability notifications, or centralized monitoring. Enable auto-updates for core (major and minor) and review plugin/theme auto-update settings per site.

Scan for Secrets and Certificates

Search your codebase and git history for committed secrets: .env files, API keys, tokens, and passwords in code or config. Check git history with git log --diff-filter=A --name-only -- '*.env' and verify .gitignore covers all sensitive files. Look for patterns like API_KEY=, SECRET=, TOKEN=, sk-, and ghp_.

Then check every domain for a valid SSL certificate. Run echo | openssl s_client -connect yourdomain.com:443 2>/dev/null | openssl x509 -noout -dates and flag anything expiring in fewer than 14 days. Confirm auto-renewal is working and that HTTP redirects to HTTPS on every domain.

Automate the Maintenance

Fixing security issues once is not enough. You need systems that keep the server secure without you thinking about it. This is what separates a “set up” server from a properly managed one.

OS security patches should install themselves automatically. On RHEL/AlmaLinux, install dnf-automatic with upgrade_type = security and apply_updates = yes. On Debian/Ubuntu, use unattended-upgrades. Then verify the timer is actually running, not just installed.

Kernel updates are a special case. Linux installs kernel updates but does not activate them until a reboot. Servers that never reboot can be weeks or months behind on kernel security patches. Rather than rebooting on a fixed schedule, set up a daily check that compares the running kernel to the latest installed one and only reboots when there is a difference:

#!/bin/bash
RUNNING=$(uname -r)
LATEST=$(rpm -q kernel --queryformat '%{VERSION}-%{RELEASE}.%{ARCH}\n' | sort -V | tail -1)
if [ "$RUNNING" != "$LATEST" ]; then
    echo "$(date): Rebooting. Running: $RUNNING, Installed: $LATEST" >> /var/log/kernel-reboot-check.log
    /sbin/reboot
else
    echo "$(date): No reboot needed. Running: $RUNNING" >> /var/log/kernel-reboot-check.log
fi

Schedule it with cron at a low-traffic hour. Most days it logs “no reboot needed” and exits.

Off-site backups are non-negotiable. Local backups die with the server. BorgBackup handles deduplication, compression, and encryption. Pair it with a Hetzner Storage Box or any remote storage target. Back up all site files, server config, mail spools, and a fresh database dump. A retention policy of 7 daily, 4 weekly, and 6 monthly is a good baseline. Schedule backups before the kernel reboot check so your data is always safe before any potential restart.

Uptime monitoring without alerting is just logging. Set up Uptime Kuma, Hetrix Tools, or a similar monitor on every production domain and critical service. Configure email or SMS notifications. If a site goes down at 2am, you should know within 5 minutes.

Resource monitoring catches the problems that uptime monitors miss. Disk filling up, CPU pegged, memory exhausted: these cause outages that do not show up until it is too late. If you run Plesk, install the Watchdog extension. Otherwise, Netdata or even a simple cron script that checks df -h and emails you at 85% disk usage will do. The point is knowing before it breaks.

Malware scanning should run both in realtime and on a schedule. ImunifyAV (free) covers WordPress well and catches injected PHP, web shells, and backdoors. Verify that realtime scanning is active (not just installed) and that a scheduled full scan runs at least monthly against your actual vhosts, not just system files.

The Checklist

Print this. Run through it on every new server.

Security:

  • Firewall is active with explicit allow/deny rules
  • FTP (port 21) is blocked
  • Default SSH port (22) is closed if using a custom port
  • Admin panels restricted by IP
  • Unencrypted mail ports blocked
  • Security headers set on every domain and subdomain
  • Server version info hidden (PHP, nginx, Apache)
  • wp-config.php permissions are 600
  • wp-login.php is protected or restricted
  • No secrets committed in git repos
  • SSL certificates valid and auto-renewing

Automation:

  • OS security patches auto-install
  • Daily kernel reboot check
  • Daily off-site backups with retention policy
  • Uptime monitoring with alerts on every production domain
  • Resource monitoring with threshold alerts
  • Malware scanning (realtime + scheduled)
  • WordPress updates managed via toolkit

Hardening:

  • Root SSH is key-only (PermitRootLogin prohibit-password)
  • Password authentication explicitly disabled
  • fail2ban active on SSH, web, mail, and CMS with recidive jail
  • MySQL/MariaDB bound to localhost only
  • Minimal user accounts with login shells
  • Kernel up to date and rebooted

Do Not Trust the Defaults

A new server is not a secure server. It is a running server. Those are different things. The defaults are designed to make setup easy, not to make your server safe. Every item on this checklist is something that can sit misconfigured for months without any visible symptom, right up until it becomes a serious problem.

Run the audit. Check the ports. Check the headers. Check the permissions. Automate what you can. Monitor what you cannot.

And if you would rather hand all of this to someone else, that is what we do.

Want to talk about this?

If this article raised questions about your own business, reach out. We are happy to chat.

Email Us pete@brooksnewmedia.com