Back to tutorials
Tutorial

Server Hardening Tutorial (2026): Secure a New Ubuntu VPS for Hosting with SSH Keys, UFW, Updates, and Log Auditing

Server hardening tutorial for Ubuntu VPS hosting in 2026: lock down SSH, firewall, updates, users, logs, and safe rollback steps.

By Anurag Singh
Updated on Aug 10, 2026
Category: Tutorial
Share article
Server Hardening Tutorial (2026): Secure a New Ubuntu VPS for Hosting with SSH Keys, UFW, Updates, and Log Auditing

A new VPS comes online in minutes. It can also get probed (and sometimes popped) just as quickly if you keep the defaults. This server hardening tutorial gives you a practical baseline for Ubuntu Server (22.04 LTS and 24.04 LTS) used for hosting: safer SSH, a predictable firewall, automatic security updates, basic intrusion noise reduction, and logs you can use during an incident.

You can use the same baseline on a single-site WordPress VPS, a reseller box, or a dedicated server.

If you’d rather not own patching, monitoring, and security housekeeping, consider managed VPS hosting. If you want full control, a HostMyCode VPS gives you root access without the hardware overhead.

What you’ll harden (and what you won’t)

This guide focuses on the usual hosting surface area: SSH, HTTP/HTTPS, and optionally mail/DNS if you run them.

It does not cover Kubernetes, service meshes, or complex IAM designs.

The goal is a clean, repeatable baseline you can apply in under an hour.

  • Access control: create a non-root admin, SSH keys, and safer sudo.
  • Network: a firewall policy that doesn’t break renewals, web traffic, or admin access.
  • Patch hygiene: unattended security updates + a reboot plan.
  • Host protections: minimal fail2ban and kernel-level guardrails.
  • Logging: persistent journals, auth visibility, and quick triage commands.

Prerequisites and safety rules (read this first)

Protect your access path before you change anything.

Most outages during hardening are self-inflicted: you tighten the door and lock yourself outside.

  1. Have console access ready (provider web console / rescue console).
  2. Keep two SSH sessions open while you work.
  3. Know your VPS IP and hostname. Confirm DNS A/AAAA records later, not during access changes.
  4. If your VPS sits behind Cloudflare or a load balancer, note it now. IP-based rules change.

Assumptions:

  • Ubuntu Server 22.04 LTS or 24.04 LTS
  • You can SSH as root initially (or a sudo user)
  • Your public key is available locally (usually ~/.ssh/id_ed25519.pub)

Step 1 — Update the system and install baseline tools

Patch first.

Apply hardening changes on a current kernel and OpenSSH build.

sudo apt update
sudo apt -y full-upgrade
sudo apt -y install ufw fail2ban unattended-upgrades ca-certificates curl vim

Check whether a reboot is required:

if [ -f /var/run/reboot-required ]; then cat /var/run/reboot-required; fi

If you’re already serving production traffic, schedule it.

If this is a brand-new VPS, reboot now:

sudo reboot

Step 2 — Create a non-root admin user and lock down sudo

Using root for daily work makes small mistakes expensive.

Create a dedicated admin account. Then use sudo deliberately.

adduser admin
usermod -aG sudo admin

Verify sudo works (in a new SSH session):

ssh admin@YOUR_SERVER_IP
sudo -v

Optional (but helpful on multi-admin servers): log sudo I/O. This lets you review what ran, not just who ran it.

Ubuntu already logs sudo events to syslog/journal.

This adds more detail:

sudo visudo

Add (or confirm) these lines:

Defaults log_output
Defaults iolog_dir="/var/log/sudo-io"
Defaults iolog_file="%{seq}"

Create the directory and secure it:

sudo mkdir -p /var/log/sudo-io
sudo chmod 700 /var/log/sudo-io

If you want a more complete least-privilege workflow (groups, session logging, and safer admin patterns), use this companion guide: SSH access control tutorial for least-privilege admin workflows.

Step 3 — Set up SSH keys (ed25519) and disable password auth

Passwords get guessed. Keys get controlled.

On your local machine, generate a key if you don’t already have one:

ssh-keygen -t ed25519 -a 64 -C "admin@yourdomain"

Copy your key to the server:

ssh-copy-id -i ~/.ssh/id_ed25519.pub admin@YOUR_SERVER_IP

Now harden the SSH daemon config.

Edit:

sudo nano /etc/ssh/sshd_config

Apply these settings (adjust if you have a specific need):

PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
UsePAM yes
X11Forwarding no
AllowUsers admin

If you need SFTP for customers or a team, don’t re-enable passwords just to make it “easy.”

Use a dedicated SFTP configuration instead: SFTP setup tutorial with chroot and per-user access.

Test your config before restarting:

sudo sshd -t

Restart SSH:

sudo systemctl restart ssh

Critical test: open a new terminal and verify you can log in as admin.

Keep your existing session open until you confirm.

Step 4 — Firewall setup that won’t break hosting

UFW works well for most single-node hosting VPS setups.

The rules stay readable, which makes audits and changes safer.

The pattern is simple: deny inbound by default, allow outbound, then open only what you use.

Allow SSH first (don’t skip this):

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp comment 'SSH'

Allow web ports:

sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'

If you run DNS on this box (many don’t), you’ll need:

sudo ufw allow 53/tcp comment 'DNS TCP'
sudo ufw allow 53/udp comment 'DNS UDP'

If you run mail (Postfix/Exim), open only the ports you actually use.

A typical minimal set is:

  • 25/tcp (server-to-server SMTP)
  • 587/tcp (submission)
  • 465/tcp (smtps, optional)
  • 143/993 (IMAP/IMAPS) and/or 110/995 (POP3/POP3S) if you host mailboxes

Enable UFW:

sudo ufw enable
sudo ufw status verbose

If you mess this up and block SSH, use the troubleshooting guide built for that exact failure mode: VPS firewall troubleshooting tutorial.

Step 5 — Unattended security updates + a reboot plan

Many real-world compromises start with a known bug and a missed patch window.

Unattended upgrades won’t fix a bad configuration. They do close the “we forgot to update” gap.

Enable Ubuntu’s unattended upgrades:

sudo dpkg-reconfigure --priority=low unattended-upgrades

Then review these config files:

  • /etc/apt/apt.conf.d/50unattended-upgrades
  • /etc/apt/apt.conf.d/20auto-upgrades

For hosting servers, these defaults are sensible in 2026:

  • Install security updates automatically
  • Keep a short package cache (saves disk)
  • Do not automatically reboot during business hours

A practical approach: set a weekly maintenance window.

Then reboot only when the system actually needs it.

sudo apt -y install needrestart
sudo needrestart -r a

needrestart tells you which daemons should restart after updates.

This matters for OpenSSL and libc fixes.

Step 6 — Add fail2ban (minimal, predictable rules)

Fail2ban won’t stop a determined attacker.

It does cut credential-stuffing noise and keeps auth logs readable.

Keep the scope tight. Start with SSH only.

Create a local jail config:

sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local

In [sshd], set:

[sshd]
enabled = true
port = 22
maxretry = 5
findtime = 10m
bantime = 1h
backend = systemd

Restart and check status:

sudo systemctl enable --now fail2ban
sudo fail2ban-client status
sudo fail2ban-client status sshd

If you also host a control panel (cPanel/WHM, Plesk, DirectAdmin), don’t blindly enable HTTP auth jails.

Panels have their own login flows and rate limits.

Protect SSH here, and handle panel security inside the panel.

Step 7 — Reduce exposed services and confirm what’s listening

Hardening goes faster when you know what’s reachable.

Start by listing open sockets:

sudo ss -tulpn

You should see:

  • sshd on 22
  • nginx and/or apache2 on 80/443 (if installed)
  • Anything else should be there because you chose it

If you find services you don’t need (for example, RPC daemons on minimal hosting nodes), disable and remove them:

sudo systemctl disable --now SERVICE_NAME
sudo apt -y purge PACKAGE_NAME

Also check enabled units:

systemctl list-unit-files --state=enabled

Step 8 — Harden the kernel/network baseline with sysctl

These sysctl settings reduce common abuse patterns.

They cover redirects, source routing, and some spoofing risk.

Put them in a dedicated file so you can audit changes later:

sudo nano /etc/sysctl.d/99-hosting-hardening.conf

Paste:

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

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
net.ipv6.conf.all.accept_source_route = 0
net.ipv6.conf.default.accept_source_route = 0

net.ipv4.tcp_syncookies = 1
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

Apply it:

sudo sysctl --system

Tip: skip the giant “hardening mega-lists” you find online.

Some tweaks break legitimate traffic or CDN behavior.

Start conservative, then tighten based on real requirements.

Step 9 — Logging you can actually use (journal persistence + quick triage)

On many VPS images, journald logs disappear after reboot.

That’s painful during an incident.

Enable persistent journaling:

sudo mkdir -p /var/log/journal
sudo systemctl restart systemd-journald

Confirm it’s working:

journalctl --disk-usage
journalctl -u ssh --since "24 hours ago" --no-pager | tail -n 50

For auth failures, these are the usual first stops:

sudo journalctl -t sshd --since "2 hours ago" --no-pager
sudo grep -R "Failed password" /var/log/auth.log | tail -n 50

If you need structured alerts (CPU spikes, disk saturation, suspicious logs), set up monitoring early.

It’s much easier than bolting it on later: Server monitoring tutorial for uptime and resource alerts.

Step 10 — Hosting-specific hardening: TLS, headers, and safe defaults

If this VPS serves websites, “server security” includes HTTPS and sane web defaults.

Two quick wins:

  1. Automate TLS certificates (Let’s Encrypt) for all sites.
  2. Set security headers so browsers enforce stricter behavior.

For Let’s Encrypt on Ubuntu with Nginx/Apache, follow: Let’s Encrypt setup guide tutorial.

If renewals fail later due to firewall/DNS misconfig, use: TLS renewal troubleshooting tutorial.

For Nginx headers (HSTS, CSP starting points, and safer defaults), reference: Nginx security headers configuration tutorial.

If you host WordPress, keep the admin surface small and boring:

  • Disable XML-RPC if you don’t need it (or restrict it)
  • Limit login attempts at the web layer
  • Use a staging workflow for updates so you don’t “secure” the site by breaking production

A clean staging workflow is here: set up a staging server on a VPS for WordPress.

Step 11 — Backups: hardening includes recovery

If you can’t recover cleanly, your security story ends early.

For hosting servers, aim for versioned, offsite backups plus regular restore tests.

Snapshots alone aren’t enough if your provider account gets compromised.

Use a 3-2-1 approach (three copies, two media types, one offsite). Start here: VPS backup strategy tutorial.

Quick checklist you can apply today:

  • Daily filesystem backup (Restic or similar) to offsite object storage
  • Provider snapshots before risky changes (kernel upgrades, major config edits)
  • Monthly restore test to a throwaway VM or a staging VPS
  • Backup encryption keys stored outside the server

Validation checklist: prove the hardening didn’t break hosting

Run these checks after you finish:

  • SSH: Can you log in as admin with keys? Does root login fail?
  • Firewall: sudo ufw status verbose shows only required ports.
  • Updates: unattended-upgrades is enabled and security origins are allowed.
  • Fail2ban: sudo fail2ban-client status sshd shows a running jail.
  • Logs: journalctl --since "1 hour ago" shows recent activity and persists after reboot.
  • Web: Your site responds on HTTPS; renewal timer exists for certbot if used.

Common pitfalls (and quick fixes)

  • Locked out after disabling passwords: use console access, re-enable temporarily, fix ~/.ssh/authorized_keys, then disable again.
  • UFW blocks Let’s Encrypt: ensure 80/tcp is open for HTTP-01 validation, or switch to DNS-01.
  • Fail2ban bans you: whitelist your admin IP in jail.local using ignoreip, then restart fail2ban.
  • Logs are empty after reboot: journald persistence wasn’t enabled; verify /var/log/journal exists.

Summary: your “minimum viable secure” Ubuntu hosting server

After this hardening pass, your VPS is harder to brute-force and exposes fewer services.

You also get a clearer audit trail.

Just as important, you now have a patching rhythm and log retention.

That makes troubleshooting faster when something looks off.

If you’re building a new hosting node, start with a HostMyCode VPS for full control. Or choose managed VPS hosting if you’d rather delegate updates, security maintenance, and operational checks while you focus on your sites.

If you’re hardening a server for client sites or business-critical WordPress, predictable performance and hosting-aware support matter. Start with a HostMyCode VPS for root-level control, or use managed VPS hosting to offload patching, monitoring, and baseline security maintenance.

FAQ

Should you change the SSH port for security in 2026?

It cuts noise, not risk.

SSH keys plus disabled password auth do the real work.

If you change the port, update UFW and document it.

Don’t treat it as protection.

Do you need fail2ban if SSH passwords are disabled?

It’s optional, but still useful for reducing connection spam and keeping logs readable.

Keep the config minimal so you don’t block legitimate admins.

What if you use cPanel/WHM—does this tutorial still apply?

Yes for SSH, updates, and logging.

Be careful with extra web auth jails.

For panel-specific security, follow a cPanel-focused hardening process.

Avoid overlapping protections that fight each other.

How often should you reboot a hosting VPS?

Reboot when the kernel or core libraries require it, typically after security updates.

Set a weekly window and use needrestart so you’re not guessing.

What’s the fastest way to verify you didn’t lock yourself out?

Open a second SSH session before restarting services.

Then test a new login after each major change (SSH config, firewall enablement).

Keep console access available until you’re done.