
How to secure a VPS for hosting websites?
To secure a VPS for hosting websites, update the operating system, create a non-root sudo user, switch SSH to key-based authentication and disable root and password logins, enable a firewall that only allows SSH, HTTP, and HTTPS, turn on automatic security updates, install Fail2Ban, harden your web server and PHP, keep the database bound to localhost, isolate each site under its own user, and set up off-site backups and monitoring. Doing these steps in the first hour after provisioning a server protects you from the automated attacks that start almost immediately.
A fresh VPS is exposed to the internet the moment it boots, and bots begin scanning it for weak SSH passwords and open services within minutes. This guide walks you through a practical hardening checklist for an Ubuntu or Debian VPS used for web hosting, with notes for RHEL-based systems like Rocky Linux and AlmaLinux where the commands differ. Each step links to a focused topic, so you can go deeper where you need to.
Before You Start
- Keep your provider's web console handy: Most VPS providers offer a browser-based console that works even if SSH breaks. Know where it is before you change SSH or firewall settings.
- Keep your current SSH session open while making changes, and test new settings in a second session.
- Take a snapshot: Many providers let you snapshot a VPS. Take one before major changes so you can roll back.
- Use a supported OS release: Choose the current LTS release of Ubuntu or the current Debian stable release, so you receive security updates for years.
Step 1: Update the System
Start by installing all available updates:
sudo apt update && sudo apt full-upgrade -y
sudo reboot
On Rocky Linux or AlmaLinux:
sudo dnf upgrade --refresh -y
sudo reboot
Rebooting ensures the latest kernel is running.
Step 2: Create a Non-Root User
Working as root all the time is risky. One typo can damage the system, and root is the first account attackers try. Create a regular user with sudo privileges:
adduser deploy
usermod -aG sudo deploy
On RHEL-based systems, the admin group is wheel:
adduser deploy
passwd deploy
usermod -aG wheel deploy
Log in as this user in a new session and confirm sudo works before moving on:
sudo whoami
The output should be root.
Step 3: Set Up SSH Key Authentication
SSH keys are far stronger than passwords and make brute-force attacks practically useless. On your local computer, create a key if you don't have one:
ssh-keygen -t ed25519 -C "you@example.com"
Copy it to the server:
ssh-copy-id deploy@your-server-ip
Then confirm you can log in with the key before disabling passwords:
ssh deploy@your-server-ip
Step 4: Harden the SSH Server
Once key login works, lock down SSH. On recent Ubuntu and Debian versions, the cleanest way is a drop-in file. Create /etc/ssh/sshd_config.d/01-hardening.conf:
sudo nano /etc/ssh/sshd_config.d/01-hardening.conf
Add:
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
LoginGraceTime 30
X11Forwarding no
AllowUsers deploy
Check the syntax and reload:
sudo sshd -t
sudo systemctl reload ssh
On RHEL-based systems, the service is called sshd, so use sudo systemctl reload sshd.
Important: Keep your existing session open and test logging in from a new terminal. If something is wrong, fix it from the open session or the provider's console.
Note that some cloud images ship with a file like /etc/ssh/sshd_config.d/50-cloud-init.conf that sets PasswordAuthentication yes. OpenSSH uses the first value it finds for most settings, and drop-in files are read in alphabetical order, so check for conflicting files:
sudo grep -ri passwordauthentication /etc/ssh/sshd_config /etc/ssh/sshd_config.d/
Because your file starts with 01-, it is read before 50-cloud-init.conf and its values win. If another file sorts even earlier and conflicts, edit or remove that setting. You can confirm the effective configuration with:
sudo sshd -T | grep -E "permitrootlogin|passwordauthentication"
Step 5: Enable a Firewall
A firewall ensures only the services you intend to expose are reachable. On Ubuntu and Debian, UFW is the simplest option.
Allow SSH before enabling the firewall, or you will lock yourself out:
sudo apt install ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose
On RHEL-based systems, use firewalld:
sudo systemctl enable --now firewalld
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
Many providers also offer a cloud firewall in their dashboard. Using both gives you defence in depth.
Step 6: Turn On Automatic Security Updates
Security patches only help if they're applied. On Ubuntu and Debian:
sudo apt install unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades
By default, this installs security updates automatically. You can check the configuration in /etc/apt/apt.conf.d/50unattended-upgrades, including whether to reboot automatically when a kernel update requires it.
On RHEL-based systems, use dnf-automatic:
sudo dnf install dnf-automatic
sudo sed -i 's/^apply_updates = no/apply_updates = yes/' /etc/dnf/automatic.conf
sudo systemctl enable --now dnf-automatic.timer
Step 7: Install Fail2Ban
Fail2Ban watches log files and temporarily bans IPs that show malicious behaviour, like repeated failed SSH logins.
sudo apt install fail2ban
Create /etc/fail2ban/jail.local:
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
backend = systemd
[sshd]
enabled = true
Restart and check status:
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd
Add your own IP to ignoreip in the [DEFAULT] section if you have a static address, so you never ban yourself.
Step 8: Remove Unneeded Services
Every running service is a possible entry point. List what's listening on the network:
sudo ss -tulpn
On a typical web server, you should see only SSH (22), HTTP (80), HTTPS (443), and local-only services like the database on 127.0.0.1. Disable anything you don't need:
sudo systemctl disable --now service-name
Step 9: Secure the Web Server
Whether you use Nginx or Apache, apply a few baseline settings:
- Hide the server version from response headers.
- Serve sites only over HTTPS, redirecting HTTP to HTTPS.
- Use modern TLS protocols (TLS 1.2 and 1.3).
- Add security headers.
- Disable directory listing.
- Block access to hidden files like
.gitand.env.
For Nginx, you can add these to the http block in /etc/nginx/nginx.conf:
server_tokens off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
And in each server block:
location ~ /\.(?!well-known) {
deny all;
}
Get free certificates with Certbot:
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
For Apache, use python3-certbot-apache and certbot --apache instead.
Step 10: Harden PHP
If you host PHP applications like WordPress, use a supported PHP 8.x version and adjust a few settings in your PHP-FPM php.ini (the path depends on the version, for example /etc/php/8.3/fpm/php.ini):
expose_php = Off
display_errors = Off
log_errors = On
allow_url_include = Off
session.cookie_httponly = 1
session.cookie_secure = 1
session.use_strict_mode = 1
Restart PHP-FPM after changes:
sudo systemctl restart php8.3-fpm
Replace 8.3 with your installed version.
Step 11: Isolate Each Website
If you host more than one site, give each its own system user and PHP-FPM pool, so a compromise of one site can't easily touch another's files.
Create a user for the site:
sudo adduser --system --group --home /var/www/example.com example
Create a pool file such as /etc/php/8.3/fpm/pool.d/example.conf:
[example]
user = example
group = example
listen = /run/php/php8.3-fpm-example.sock
listen.owner = www-data
listen.group = www-data
pm = ondemand
pm.max_children = 10
pm.process_idle_timeout = 10s
php_admin_value[open_basedir] = /var/www/example.com:/tmp
Point that site's Nginx configuration at the new socket, and set file ownership so the site user owns its files. Make sure only directories that genuinely need to be writable (like uploads) are writable by the PHP process.
Step 12: Secure the Database
- Keep MySQL or MariaDB listening on
127.0.0.1only. Checkbind-addressin the server configuration. - Run
sudo mysql_secure_installation(ormariadb-secure-installation) to remove anonymous users and the test database. - Create a separate database user per site with privileges only on that site's database.
CREATE DATABASE example_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'example_user'@'localhost' IDENTIFIED BY 'use-a-long-random-password';
GRANT ALL PRIVILEGES ON example_db.* TO 'example_user'@'localhost';
FLUSH PRIVILEGES;
Never open port 3306 in your firewall. If you need remote access for administration, use an SSH tunnel instead.
Step 13: Set Up Backups
A VPS provider's snapshots are useful but usually live in the same account. Also back up to an independent location:
- Back up website files and databases daily at minimum.
- Store backups off the server, for example in object storage with a different provider or account.
- Encrypt backups that contain personal data.
- Test restores regularly.
Tools like restic and borg make encrypted, deduplicated backups straightforward.
Step 14: Monitor the Server
- Logs: Review
/var/log/auth.log(Ubuntu and Debian) orjournalctl -u sshfor login activity, and your web server logs for unusual requests. - Uptime monitoring: Use an external service so you're alerted if a site goes down.
- Resource monitoring: Sudden CPU spikes can indicate malware such as crypto miners.
- File integrity: Tools like AIDE can alert you when system files change.
- Rootkit checks:
rkhuntercan periodically scan for known rootkits.
Step 15: Keep It Maintained
Security isn't a one-time setup. Put a recurring reminder in your calendar to:
- Check that automatic updates are running and reboot when needed.
- Review users with SSH access and sudo rights.
- Update your web applications and their plugins.
- Review firewall rules.
- Test a backup restore.
- Plan OS upgrades before your release reaches end of life.
FAQ: Securing a VPS
Update the system, create a non-root sudo user, and set up SSH key authentication. Then disable root and password logins and enable a firewall that allows SSH before anything else.
Changing the port reduces log noise from automated scans, but it isn't real protection on its own. Key-only authentication, disabled root login, and Fail2Ban matter far more. If you do change the port, update your firewall first.
UFW is a solid host firewall for most web servers, but it's only one layer. Combine it with SSH hardening, automatic updates, Fail2Ban, a secure web stack, and backups.
Keep your current session open, test changes in a second session, allow SSH in the firewall before enabling it, and know how to access your provider's web console as a fallback.
No. Many administrators run secure servers from the command line. A control panel can simplify management, but it also adds software you need to keep updated and secured.
Security updates should be applied automatically, daily. Review the server at least monthly for pending reboots, application updates, and anything automatic updates don't cover.
Yes, if you isolate them. Give each site its own system user, PHP-FPM pool, and database user, so a problem on one site doesn't spread to the others.
Conclusion
Securing a VPS for website hosting is mostly about doing the basics carefully and early: update the system, work as a non-root user, use SSH keys and disable password logins, run a firewall, enable automatic updates, and add Fail2Ban. From there, harden your web server, PHP, and database, and isolate each site so one problem can't spread.
The final piece is maintenance. Backups, monitoring, and a regular review routine keep your server secure long after the initial setup. Follow this checklist on every new server you provision, and you'll avoid the vast majority of attacks that target freshly deployed machines.


