
A new VPS comes online quickly—and it attracts attention just as fast. Automated scans will probe SSH, guess passwords, and poke at exposed services within minutes.
This server hardening tutorial gives you a clean, repeatable baseline for an Ubuntu hosting VPS you can run every day. You’ll set up safe SSH access, a sensible firewall, automatic security updates, and a backup-ready foundation.
The steps below assume Ubuntu Server 24.04 LTS (common in 2026) and root access. You’ll stick to standard tools (OpenSSH, UFW, systemd) plus a few conservative defaults. These reduce risk without breaking normal hosting work.
What you’ll build (and what you should have first)
This guide fits the common “one VPS hosts a few sites” model. Think WordPress or custom apps on Nginx/Apache. Mail is best treated as a later project.
The goal stays simple: predictable access, fewer exposed surfaces, and fewer surprises.
- Secure admin access: SSH keys, no root login, minimal users.
- Network baseline: UFW default-deny with explicit web + SSH rules.
- Patch discipline: unattended security updates with clean reboot handling.
- Auditability: logs, basic intrusion signals, and recovery steps.
- Backup readiness: a plan that won’t fail when you need it.
Before you start:
- Your VPS IP address and console access (provider console or KVM) in case you lock yourself out.
- A domain you control (optional now; required if you add TLS soon). If you need one, register via HostMyCode domains.
- SSH client (macOS/Linux Terminal or Windows PowerShell/Windows Terminal).
Step 1: Create a real admin user (and stop using root for daily work)
Most providers hand you root on day one. Use it briefly. Then switch to a dedicated admin account with sudo.
adduser admin
usermod -aG sudo admin
Confirm sudo works:
su - admin
sudo -v
Why this matters: once root SSH is disabled, a stolen login won’t instantly become total control. You also get clearer per-user audit trails in logs.
Step 2: Set up SSH keys (and prove you can log in without a password)
On your local machine, generate a key if you don’t already have one:
ssh-keygen -t ed25519 -a 64 -f ~/.ssh/hostmycode-admin
Copy the public key to the server (replace IP):
ssh-copy-id -i ~/.ssh/hostmycode-admin.pub admin@203.0.113.10
Test key login explicitly:
ssh -i ~/.ssh/hostmycode-admin admin@203.0.113.10
If you want a deeper checklist for safe SSH defaults (keys, sudo, permissions), use this companion guide: SSH key setup tutorial.
Step 3: Harden sshd_config (no root login, no passwords, tighter auth)
Edit SSH server settings:
sudo nano /etc/ssh/sshd_config
Set (or confirm) the lines below. Keep the config boring and easy to reason about.
Port 22
Protocol 2
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
PermitEmptyPasswords no
X11Forwarding no
AllowTcpForwarding yes
ClientAliveInterval 300
ClientAliveCountMax 2
Notes:
AllowTcpForwarding yeskeeps SSH tunnels available for admin-only services. If you never use tunnels, set it tono.- Keep port 22 unless you have a real policy to change it. A different port reduces log noise, not actual risk. Keys and firewall rules are what move the needle.
Validate config, then restart SSH:
sudo sshd -t
sudo systemctl restart ssh
Safety move: keep your current SSH session open. Test a second session before you log out. If you misconfigure SSH, that first session is your lifeline.
If your logs show brute-force attempts or you’re getting locked out, this is the practical troubleshooting path: SSH brute-force troubleshooting tutorial.
Step 4: Add a firewall baseline with UFW (default deny)
A hosting VPS shouldn’t expose “whatever happens to be installed.” UFW gives you a readable ruleset. It also matches Ubuntu’s defaults and stays manageable over time.
Install and enable UFW:
sudo apt update
sudo apt install -y ufw
Set the default policy and allow only what you need:
sudo ufw default deny incoming
sudo ufw default allow outgoing
# SSH (lock this to your office IP if possible)
sudo ufw allow 22/tcp
# Web
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
Enable the firewall:
sudo ufw enable
sudo ufw status verbose
Common hosting pitfall: enabling UFW before allowing SSH will lock you out. If you suspect you did that, follow: firewall troubleshooting tutorial.
If you run mail on this VPS later, open mail ports deliberately (and only when you’re ready): 25, 587, 465, 143/993, 110/995. Don’t open them “just in case.”
Step 5: Patch the OS and turn on unattended security updates
Most real-world break-ins are still old vulnerabilities meeting old packages. Aim for boring operations.
Apply security updates automatically. Then handle reboots on purpose.
Upgrade packages:
sudo apt update
sudo apt -y full-upgrade
sudo apt -y autoremove --purge
Install unattended upgrades:
sudo apt install -y unattended-upgrades apt-listchanges
sudo dpkg-reconfigure --priority=low unattended-upgrades
Check the config files (these are the ones that matter):
/etc/apt/apt.conf.d/50unattended-upgrades/etc/apt/apt.conf.d/20auto-upgrades
Then confirm the service runs:
systemctl status unattended-upgrades --no-pager
grep -R "Unattended-Upgrade" /var/log/unattended-upgrades/ | tail -n 20
Reboots: kernel security updates often require a reboot on Ubuntu. If you want a disciplined routine (updates, reboots, cleanup, health checks), pair this with: Linux server maintenance tutorial.
Step 6: Reduce the attack surface (remove what you don’t use)
Every listening service is another place to make a bad assumption. Start by confirming what’s actually exposed.
List listening ports:
sudo ss -lntup
List enabled services:
systemctl list-unit-files --type=service --state=enabled
If you find services you don’t need (examples: an unused FTP daemon, legacy RPC services), remove or disable them. For example:
sudo systemctl disable --now avahi-daemon 2>/dev/null || true
Don’t guess. If you’re unsure what a service is, identify the owning package first:
dpkg -S /usr/sbin/<binary-name>
Step 7: Add basic kernel and network hardening via sysctl
You don’t need aggressive tuning here. A small, conservative sysctl set cuts down common abuse patterns. It also avoids noisy defaults.
Create a file:
sudo nano /etc/sysctl.d/99-hostmycode-hardening.conf
Add:
# IP spoofing protection
net.ipv4.conf.all.rp_filter=1
net.ipv4.conf.default.rp_filter=1
# Don’t accept ICMP redirects
net.ipv4.conf.all.accept_redirects=0
net.ipv4.conf.default.accept_redirects=0
net.ipv6.conf.all.accept_redirects=0
net.ipv6.conf.default.accept_redirects=0
# Don’t send ICMP redirects
net.ipv4.conf.all.send_redirects=0
net.ipv4.conf.default.send_redirects=0
# Reduce info leaks
net.ipv4.tcp_timestamps=0
kernel.kptr_restrict=2
kernel.dmesg_restrict=1
Apply it:
sudo sysctl --system
Quick check:
sysctl net.ipv4.conf.all.accept_redirects kernel.kptr_restrict kernel.dmesg_restrict
Step 8: Tighten file permissions and default umask for hosting workflows
On hosting servers, permissions are where “minor” mistakes turn into incidents. Start with the basics.
Private keys and SSH files should be readable only by the owning user (and root).
Audit SSH permissions:
chmod 700 /home/admin/.ssh
chmod 600 /home/admin/.ssh/authorized_keys
sudo chown -R admin:admin /home/admin/.ssh
If you later host WordPress or PHP apps, avoid 777 permissions. Fix ownership, use groups properly, and keep write access narrow.
For WordPress-specific hardening, link your future self: WordPress security hardening tutorial.
Step 9: Add fail-safe logging and quick security signals (without building a SIEM)
You don’t need a full logging platform to get value. You do need enough visibility to answer two questions: “Are we being attacked?” and “What changed?”
Journald and SSH logs cover a lot.
Check authentication failures:
sudo journalctl -u ssh --since "24 hours ago" | tail -n 200
Watch live SSH attempts during an incident:
sudo journalctl -u ssh -f
If you manage more than one VPS, centralizing logs becomes worth it fast. This setup is practical and secure: log shipping with rsyslog + TLS.
Step 10: Make backups part of the hardening baseline (not an afterthought)
Hardening isn’t just about preventing break-ins. It’s also about recovering cleanly after a bad deploy, a broken update, or ransomware inside a web app.
Pick one backup approach. Then test a restore.
- File-level + database-aware backups: great for WordPress and multi-site VPS setups.
- Snapshot/image-level backups: fast full-system recovery, especially for emergency rebuilds.
If you want encrypted offsite backups with retention and real restore tests, follow: Incremental Backup Tutorial (Restic + S3). For full-server image-level coverage, this is the cleanest workflow: VPS snapshot backup tutorial.
Minimum standard: schedule backups daily, keep at least 7–14 days retention, and run a monthly restore drill to a separate VPS.
Step 11: Optional—prepare for TLS and DNS without breaking cutovers
You don’t need a full web stack installed to plan TLS and DNS. You do need to avoid two common outages: sloppy DNS changes and certificate renewals that quietly fail.
- Lower DNS TTL before migrations or major changes (300 seconds is a sane temporary value).
- Keep HTTP (port 80) open if you use Let’s Encrypt HTTP-01 validation.
If you’re moving sites between servers, don’t improvise. Use a cutover checklist: DNS cutover tutorial. If renewals fail later, this saves hours: SSL renewal troubleshooting tutorial.
Step 12: Server hardening tutorial verification checklist (run this every time)
Run these checks after you finish. They catch the mistakes that lead to lockouts, exposed services, and “how did that happen?” incidents.
- SSH:
sudo sshd -T | egrep "permitrootlogin|passwordauthentication|kbdinteractiveauthentication" - Firewall:
sudo ufw status numbered - Open ports:
sudo ss -lntup(expect 22, 80, 443; anything else should be intentional) - Updates:
systemctl status unattended-upgrades - Reboot needed:
[ -f /var/run/reboot-required ] && cat /var/run/reboot-required || echo "No reboot required" - Backups: confirm last backup timestamp and perform a restore test to a staging VPS
Where HostMyCode fits (and when to choose managed vs unmanaged)
If you want full control, start with a HostMyCode VPS and apply the baseline above. It’s a good fit if you prefer standard Ubuntu tooling and want to avoid platform lock-in.
If you’d rather not spend your time chasing patches, firewall tweaks, and restore drills, managed VPS hosting is the practical option. You still control your sites. The routine security and maintenance work is handled for you.
If you’re building a secure hosting stack in 2026, start with infrastructure you can live with long-term. A HostMyCode VPS gives you root access and stable performance for serious projects, while managed VPS hosting covers hardening, patching, and routine operational hygiene so it doesn’t turn into a weekly emergency.
FAQ
Should I change the SSH port from 22 on a hosting VPS?
You can, but it’s not a substitute for SSH keys and firewall rules. If you do change it, update UFW first, keep an active session open, and test a second login before restarting SSH.
Can I enable UFW if I’m using a control panel later (cPanel, Plesk, DirectAdmin)?
Yes, but plan your rules around the panel you choose. Control panels open additional ports (e.g., 2087/2083 for cPanel, 8443 for Plesk). Only open what you use, and restrict admin access by IP when you can.
What’s the minimum backup standard for a small VPS with WordPress?
Daily encrypted offsite backups, 7–14 days retention, and a monthly restore test to a separate VPS. If you never test restores, you don’t have backups—you have files.
What’s the fastest way to tell if my VPS is exposed right now?
Run ss -lntup to list listening ports, then compare that list to what you intended to run. If you see unexpected listeners, identify the package and disable or remove it.
Will unattended upgrades break my websites?
Security updates are usually safe, but you still need a plan for reboots and post-update checks. For high-traffic sites, pair this with staging restores and a rollback plan.