
Your VPS will get scanned within minutes of going public. Most probes aren’t “targeted.” They’re automated, cheap, and relentless. This VPS hardening tutorial gives you a practical baseline for an Ubuntu web-hosting server. It cuts attack surface without making daily admin work miserable.
Aim for boring, predictable operations. Lock down SSH, open only the ports you use, apply security patches on a schedule, and keep logs you can trust.
Do this once. Future maintenance gets easier.
What you’ll harden (and what you’ll need)
- OS: Ubuntu Server 24.04 LTS or 22.04 LTS (commands are the same in practice)
- Access: root or a sudo user via SSH
- Services assumed: SSH now; later you may add Nginx/Apache, PHP, mail, or a control panel
- Time: 45–90 minutes, plus a reboot window
If you want a clean starting point with predictable networking and console access, a HostMyCode VPS is a straightforward way to run Ubuntu with dedicated resources and full root control.
Step 0: Snapshot or console access before you touch SSH
Before you change SSH or firewall rules, make sure you can recover if something goes sideways.
- Take a snapshot/backup if your platform supports it.
- Confirm you have console access (VNC/serial/IPMI-style console) or rescue mode.
- Keep your current SSH session open until the very end.
Step 1: Update the system and reboot once
Bring the base image current first. “Fresh” VPS images are often behind on kernel and OpenSSH fixes.
sudo apt update
sudo apt -y full-upgrade
sudo reboot
After the reboot, confirm you’re on the expected kernel and SSH version:
uname -r
ssh -V
Step 2: Create a dedicated admin user (and stop daily root logins)
A named admin account gives you cleaner audit trails. It also makes credential rotation easier than working directly in root.
sudo adduser admin
sudo usermod -aG sudo admin
Verify sudo works:
su - admin
sudo -v
Pitfall: Don’t disable root SSH until you can log in as admin with your key (next step).
Step 3: Switch SSH to key-based auth and tighten sshd
Key-based SSH doesn’t “solve security.” It does remove password guessing as an entry point.
Generate the key on your local machine (not on the server).
ssh-keygen -t ed25519 -a 64 -C "admin@yourdomain"
Copy it to the server:
ssh-copy-id admin@YOUR_SERVER_IP
Now tighten the SSH daemon on the server. Edit:
sudo nano /etc/ssh/sshd_config
Set (or confirm) these lines. Keep them explicit so behavior stays predictable after package updates:
Port 22
Protocol 2
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
UsePAM yes
PubkeyAuthentication yes
AuthenticationMethods publickey
AllowUsers admin
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no
PermitTunnel no
ClientAliveInterval 300
ClientAliveCountMax 2
MaxAuthTries 4
LoginGraceTime 30
Should you change the SSH port? It can cut down on log noise, but it’s not a security control.
If you change it, update your firewall rules and monitoring first. Otherwise, you can lock yourself out. For most hosting setups, port 22 plus keys and a firewall is simpler and less fragile.
Validate and reload SSH:
sudo sshd -t
sudo systemctl reload ssh
Open a new terminal and confirm you can log in:
ssh admin@YOUR_SERVER_IP
If it fails, keep your existing session open. Fix the config before you close anything.
If you want a dedicated walkthrough on firewall rules that stay friendly to SSH and security tooling, see our UFW firewall setup tutorial for Ubuntu VPS.
Step 4: Configure UFW with a hosting-safe default policy
Start with default-deny inbound. Then open only what you intend to serve.
Install and enable UFW:
sudo apt -y install ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
Allow SSH first (adjust if you changed the port):
sudo ufw allow 22/tcp comment 'SSH'
If this VPS will host websites, open HTTP/HTTPS too:
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'
Enable the firewall:
sudo ufw enable
sudo ufw status verbose
Quick diagnostic: from your laptop, confirm SSH and your web ports respond the way you expect:
ssh admin@YOUR_SERVER_IP
curl -I http://YOUR_SERVER_IP
Step 5: Add Fail2Ban for brute-force and noisy scanners
With passwords disabled, SSH guessing is mostly a non-issue. Fail2Ban still helps by slowing abusive IPs and cutting down background noise.
It matters even more once you add web logins.
sudo apt -y install fail2ban
sudo systemctl enable --now fail2ban
Create a minimal jail override:
sudo nano /etc/fail2ban/jail.d/sshd.local
[sshd]
enabled = true
maxretry = 4
findtime = 10m
bantime = 1h
banaction = ufw
Restart and check status:
sudo systemctl restart fail2ban
sudo fail2ban-client status
sudo fail2ban-client status sshd
Pitfall: If you administer from a fixed office IP, add an allowlist. Use UFW or Fail2Ban’s ignoreip. That prevents accidental bans during long maintenance sessions.
Step 6: Turn on automatic security updates (with sensible behavior)
Small servers often get compromised through old, well-known vulnerabilities. Automated security updates shrink that window.
sudo apt -y install unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades
Review settings here:
sudo nano /etc/apt/apt.conf.d/50unattended-upgrades
Confirm at least security origins are enabled for your Ubuntu release.
Then enable periodic runs:
sudo nano /etc/apt/apt.conf.d/20auto-upgrades
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
Operational note: In production hosting, pair unattended upgrades with a monthly reboot window. Kernel and libc fixes won’t fully apply if the system never restarts.
Step 7: Reduce exposed services and confirm what’s listening
Hardening is mostly subtraction. If you didn’t install it on purpose, don’t leave it reachable.
List listening sockets:
sudo ss -tulpn
You typically want to see SSH, and maybe 80/443.
If you see unexpected ports (like 3306 for MySQL), make sure they bind to localhost. If they must listen on a public IP, block them with the firewall.
Check enabled services:
systemctl list-unit-files --type=service --state=enabled
Disable what you don’t need (example):
sudo systemctl disable --now rpcbind
Step 8: Add basic kernel and network safety knobs (sysctl)
These won’t save a poorly managed server. They do block common abuse patterns like spoofing and redirects.
They also reduce a few loud scanning tricks.
Create a dedicated sysctl file:
sudo nano /etc/sysctl.d/99-hosting-hardening.conf
net.ipv4.tcp_syncookies = 1
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
Apply:
sudo sysctl --system
Step 9: Tighten file permissions for SSH and sudo
If your home directory or .ssh permissions are too open, OpenSSH may ignore your keys.
When that happens, people often troubleshoot the wrong thing.
sudo chmod 700 /home/admin
sudo chmod 700 /home/admin/.ssh
sudo chmod 600 /home/admin/.ssh/authorized_keys
sudo chown -R admin:admin /home/admin/.ssh
For sudoers edits, don’t gamble. Use visudo so a syntax mistake doesn’t strand you:
sudo visudo
Step 10: Logging you’ll actually use (journald + basic rotation awareness)
If you can’t reconstruct events after the fact, you’re operating blind. Ubuntu’s journald defaults are fine.
On small disks, set caps so logs don’t grow forever.
Edit:
sudo nano /etc/systemd/journald.conf
Set a cap that matches your disk size (example values):
SystemMaxUse=512M
RuntimeMaxUse=256M
Restart journald:
sudo systemctl restart systemd-journald
Quick checks:
journalctl -u ssh --since "1 hour ago"
journalctl -u fail2ban --since "24 hours ago"
Step 11: Add a lightweight integrity and malware baseline (optional but practical)
On hosting servers, two tools are usually worth the small overhead. Use auditd for audit trails, and rkhunter for periodic checks.
They don’t replace patching and backups. They can surface “that’s weird” changes early.
sudo apt -y install auditd audispd-plugins rkhunter
Initialize rkhunter database and run a first pass:
sudo rkhunter --update
sudo rkhunter --propupd
sudo rkhunter --check --sk
Pitfall: Don’t treat the first rkhunter run like an incident. Expect some informational warnings. It’s most useful for spotting changes over time.
Step 12: If you’re running a control panel, adjust hardening to fit
cPanel/WHM, Plesk, and DirectAdmin manage services for you. That convenience has a tradeoff.
Avoid “extra hardening” that blocks panel-managed ports or breaks updates.
- Firewall: open only the control-panel ports you use, and restrict them by IP if possible.
- Updates: let the control panel manage its own update cycle, but keep OS security updates on.
- SSH: keys + no root login still works well for panel servers.
If your hosting stack includes cPanel, test your backup and restore process. Don’t just configure it and hope.
Hardening reduces risk; recovery covers the rest. Use our cPanel backup tutorial for WHM to set up remote storage and restore testing.
Verification checklist: prove your baseline is working
Don’t trust config files. Verify the behavior you meant to enforce.
- SSH: password login fails; key login works
- Root SSH: denied
- Firewall: only required ports are open
- Fail2Ban: running and has an sshd jail active
- Updates: unattended upgrades enabled and scheduled
- Listening ports:
ss -tulpnmatches what you intend
# SSH should reject passwords
ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no admin@YOUR_SERVER_IP
# See firewall rules
sudo ufw status numbered
# Confirm fail2ban jail
sudo fail2ban-client status sshd
# Confirm unattended upgrades timers
systemctl list-timers | grep -E 'apt|unattended'
Common troubleshooting (fast fixes)
You locked yourself out after enabling UFW
- Use your provider’s console access.
- Temporarily disable UFW:
sudo ufw disable - Re-allow SSH:
sudo ufw allow 22/tcpthen re-enable:sudo ufw enable
SSH says “Permission denied (publickey)”
- Check file permissions:
~/.sshshould be 700 andauthorized_keys600. - Confirm you edited the right config:
sudo sshd -T | grep -E 'passwordauthentication|permitrootlogin|pubkeyauthentication' - Look at logs:
journalctl -u ssh -n 80 --no-pager
Fail2Ban is running but not banning
- Confirm the jail is enabled:
sudo fail2ban-client status - Check the log path: Ubuntu uses journald; Fail2Ban typically reads via backend defaults, but custom configs can break it.
- Validate bans:
sudo fail2ban-client get sshd banip
Next steps: plan for migration and recovery
Hardening reduces risk. It won’t save you from failed disks, bad updates, or accidental deletes.
Pair this baseline with tested backups, a migration plan, and regular restore drills.
If you’re moving to a new server, use this near-zero downtime server migration tutorial. It helps you keep uptime while you move and re-secure.
Need TLS next? Follow our SSL certificate setup guide for Ubuntu VPS to enable Let’s Encrypt and renewals safely.
Summary: VPS hardening tutorial baseline you can operate
This baseline blocks common entry points: password brute forcing, accidental open ports, and long-unpatched packages.
It also keeps operations sane. You get key-based SSH, a firewall you can read quickly, and logs that show what changed.
If you’d rather keep the same baseline while offloading some ongoing work, managed VPS hosting from HostMyCode can help. It covers OS patching assistance, security reviews, and operational support while you run your sites.
If you’re building a production hosting server in 2026, start with a VPS that gives you predictable CPU/RAM and reliable console access. A HostMyCode VPS is a solid base for this hardening checklist, and managed VPS hosting is there when you want hands-on help with ongoing security and maintenance.
FAQ
Does this tutorial apply to Debian or AlmaLinux/Rocky Linux?
The concepts are the same: key-only SSH, a default-deny firewall, timely patching, and fewer exposed services. The commands change (UFW vs firewalld, apt vs dnf). If you’re on Ubuntu for hosting, this guide maps 1:1.
Should I disable IPv6 on my VPS?
Not by default. If your provider assigns IPv6, keep it and firewall it properly. Disabling IPv6 often creates subtle connectivity problems for mail and some CDNs.
Do I still need Fail2Ban if I’ve disabled SSH passwords?
It’s optional for SSH-only, but still useful for noise control and as a backstop. It matters more once you host web apps with login endpoints.
What ports should I open for a basic web server?
For a typical site: 22/tcp (SSH), 80/tcp (HTTP), and 443/tcp (HTTPS). Add other ports only when you can clearly justify them.
How often should I review hardening settings?
At least quarterly, and after major stack changes (new control panel, adding mail, switching web server). Also review after any incident or suspicious login attempt.