How to Secure a New Ubuntu VPS: A Setup Checklist for Business Websites

A step-by-step hardening checklist for a fresh Ubuntu 26.04 or 24.04 LTS VPS that will host a business website, with copy-paste commands for SSH keys, ufw, unattended-upgrades, fail2ban, time sync, swap, monitoring and backups.

How to Secure a New Ubuntu VPS: A Setup Checklist for Business Websites article cover image

To secure a fresh Ubuntu LTS VPS before it hosts a business website: create a non-root user with sudo, log in with SSH keys only, disable password and root login over SSH, turn on the ufw firewall with only SSH, HTTP and HTTPS open, confirm automatic security updates are running, and add brute-force protection such as fail2ban. Then check time sync, add swap and basic monitoring, and take a tested backup before go-live.

The commands below are written for Ubuntu 26.04 LTS and also work on Ubuntu 24.04 LTS, with differences noted where they matter. They assume a new server you have just received from a provider, with SSH access as root or as a default user such as ubuntu. Work through the sections in order; the order matters, because a mistake in SSH or the firewall can lock you out.

Before you change any SSH or firewall setting, keep a second SSH session open to the server, and do not close it until you have confirmed you can open a brand-new session with the new settings. If something goes wrong, the open session is how you fix it. Most providers also offer a web-based or "rescue" console; find it now, before you need it.

1. Update the system and create a sudo user

Log in as root (or the provider's default user) and bring the system up to date:

`bash

sudo apt update && sudo apt full-upgrade -y

sudo reboot # if a new kernel was installed

`

If /var/run/reboot-required exists after the upgrade, a reboot is needed for the new kernel or libraries to take effect.

Create a personal administrative user. Use a real name rather than admin, which is among the first usernames automated scanners try:

`bash

sudo adduser alex

sudo usermod -aG sudo alex

`

adduser asks for a password. Set a strong one: you will no longer use it to log in over SSH, but sudo will ask for it. On Ubuntu 26.04 the default sudo command is provided by sudo-rs, a Rust reimplementation, according to the Ubuntu 26.04 LTS release notes. Membership of the sudo group works the same way.

If the provider is running the site for several people, create one account per person. Shared accounts make it impossible to tell who did what, and impossible to remove one person's access cleanly.

2. Set up SSH key authentication

SSH keys replace passwords with a key pair: the private key stays on your computer and the public key goes on the server. Ubuntu's OpenSSH server documentation recommends Ed25519 keys.

On your own computer (not the server), create a key if you do not already have one, and protect it with a passphrase:

`bash

ssh-keygen -t ed25519 -C "alex@laptop"

`

Copy the public key to the new user on the server:

`bash

ssh-copy-id [email protected]

`

Replace the IP address with your server's. If your provider already put your key in root's authorized_keys and password login is disabled, ssh-copy-id cannot log in as the new user yet. In that case, copy it across on the server as root:

`bash

sudo mkdir -p /home/alex/.ssh

sudo cp /root/.ssh/authorized_keys /home/alex/.ssh/

sudo chown -R alex:alex /home/alex/.ssh

sudo chmod 700 /home/alex/.ssh

sudo chmod 600 /home/alex/.ssh/authorized_keys

`

Now, from a new terminal, confirm you can log in as the new user with the key, and that sudo works:

`bash

ssh [email protected]

sudo whoami # should print: root

`

Do not continue until this works.

3. Disable password and root login over SSH

Ubuntu's /etc/ssh/sshd_config begins with Include /etc/ssh/sshd_config.d/*.conf, and for each setting sshd uses the first value it reads. That has a practical consequence: many cloud images ship a file such as /etc/ssh/sshd_config.d/50-cloud-init.conf that sets PasswordAuthentication yes, and it will override anything you write lower down in the main file. The reliable fix is a drop-in file whose name sorts first:

`bash

sudo tee /etc/ssh/sshd_config.d/00-hardening.conf > /dev/null <<'EOF'

PermitRootLogin no

PasswordAuthentication no

KbdInteractiveAuthentication no

PubkeyAuthentication yes

AllowUsers alex

EOF

`

Adjust AllowUsers to list every account that needs SSH access, separated by spaces, or remove that line if you would rather not restrict by name.

Check the configuration is valid, then confirm the effective values before restarting:

`bash

sudo sshd -t

sudo sshd -T | grep -Ei '^(permitrootlogin|passwordauthentication|kbdinteractiveauthentication|pubkeyauthentication|allowusers)'

`

The second command should show permitrootlogin no, passwordauthentication no and so on. If it does not, another file is setting the value first; look in /etc/ssh/sshd_config.d/.

Apply the change:

`bash

sudo systemctl restart ssh

`

With your existing session still open, open a new terminal and log in again as your user. Also confirm the server now refuses password logins:

`bash

ssh -o PubkeyAuthentication=no -o PreferredAuthentications=password [email protected]

# expected: Permission denied (publickey).

`

A note on changing the SSH port. Moving SSH off port 22 reduces log noise from automated scans but does not add real security, and it adds a way to lock yourself out. Since Ubuntu 22.10, OpenSSH uses systemd socket activation; on 24.04 and later the socket's port is generated from the Port setting in sshd_config. If you do change it, allow the new port in ufw first, run sudo systemctl daemon-reload and sudo systemctl restart ssh.socket, and test from a new session before removing the old rule. For most business sites, keys-only authentication on port 22 is the better trade-off.

4. Turn on the ufw firewall

ufw is Ubuntu's front end to the kernel firewall. Its manual page warns that enabling it flushes the firewall chains and can drop existing connections such as SSH, so allow SSH before you enable it:

`bash

sudo apt install ufw # usually already installed

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

`

OpenSSH is an application profile provided by the openssh-server package; sudo ufw app list shows the profiles available. Once Nginx is installed you can use sudo ufw allow 'Nginx Full' instead of the two port rules.

Open nothing else to the internet unless you have a specific reason. Databases (MySQL, PostgreSQL, Redis) and application servers (for example a Node.js app on port 3000) should listen on 127.0.0.1 and be reached through Nginx or an SSH tunnel, not opened in the firewall. Check what is listening:

`bash

sudo ss -tulpn

`

Anything bound to 0.0.0.0 or [::] other than SSH, HTTP and HTTPS deserves a second look, even with the firewall in place.

One known gotcha: Docker publishes container ports by writing its own firewall rules, which can bypass ufw. If you run Docker, bind published ports to 127.0.0.1 (for example -p 127.0.0.1:3000:3000).

Many VPS providers also offer a network firewall in their control panel. Using both is reasonable: the provider firewall blocks traffic before it reaches the server, and ufw protects you if the provider rules are ever changed.

5. Automatic security updates

According to Ubuntu's automatic updates documentation, the unattended-upgrades package is installed by default on Ubuntu Server and applies security updates automatically. Do not assume; check:

`bash

cat /etc/apt/apt.conf.d/20auto-upgrades

`

You should see:

`text

APT::Periodic::Update-Package-Lists "1";

APT::Periodic::Unattended-Upgrade "1";

`

If the file is missing or the values are 0, enable it:

`bash

sudo apt install unattended-upgrades

sudo dpkg-reconfigure -plow unattended-upgrades

`

Test what it would do:

`bash

sudo unattended-upgrade -v --dry-run

`

Some updates, kernel updates in particular, only take effect after a reboot. You can let the server reboot itself at a quiet time by setting these in /etc/apt/apt.conf.d/50unattended-upgrades:

`text

Unattended-Upgrade::Automatic-Reboot "true";

Unattended-Upgrade::Automatic-Reboot-Time "04:00";

`

Whether to allow automatic reboots is a business decision. For a single server running a shop, a few minutes of downtime at 4am is usually better than running an unpatched kernel for weeks, but make sure your application starts again on boot (enable its systemd service) before you turn this on. Logs are in /var/log/unattended-upgrades/.

6. Brute-force protection: fail2ban or alternatives

With password login disabled, SSH brute-force attempts cannot succeed, but they still fill logs and use resources. fail2ban watches logs and temporarily bans IP addresses that fail repeatedly.

`bash

sudo apt install fail2ban

`

On Ubuntu, the package ships /etc/fail2ban/jail.d/defaults-debian.conf, which enables the sshd jail and reads the systemd journal. Put your own overrides in /etc/fail2ban/jail.local rather than editing the shipped files:

`bash

sudo tee /etc/fail2ban/jail.local > /dev/null <<'EOF'

[DEFAULT]

bantime = 1h

findtime = 10m

maxretry = 5

# Never ban your own office or VPN address (edit or remove):

# ignoreip = 127.0.0.1/8 ::1 198.51.100.25

[sshd]

enabled = true

EOF

sudo systemctl enable --now fail2ban

sudo systemctl restart fail2ban

sudo fail2ban-client status sshd

`

If fail2ban-client status sshd reports the jail with a filter and actions, it is working. If the service fails to start, check sudo journalctl -u fail2ban before relying on it.

Alternatives. If you would rather not run another daemon, ufw has built-in rate limiting: sudo ufw limit OpenSSH replaces the plain allow rule and, per the ufw manual, denies an IP address that attempts 6 or more connections within 30 seconds. A provider network firewall that only allows SSH from your office or VPN addresses is stronger than either. fail2ban can also protect web applications, such as WordPress login pages, with extra filters, but that is beyond a baseline setup.

7. Time sync, swap and basic monitoring

Time sync. Accurate time matters for TLS certificates, logs, two-factor codes and scheduled jobs. Check it:

`bash

timedatectl

`

Look for System clock synchronized: yes. On Ubuntu 26.04, chrony replaces systemd-timesyncd as the default time daemon and uses authenticated Network Time Security (NTS) with Ubuntu's time servers, per the release notes; chronyc tracking shows its status. On 24.04, systemd-timesyncd is the default. Setting the server timezone to UTC (sudo timedatectl set-timezone UTC) keeps logs easy to compare across systems.

Swap. Small VPS plans often have no swap, so a memory spike (a WordPress import, an npm build) kills processes outright. A modest swap file acts as a safety margin, not as extra RAM:

`bash

sudo fallocate -l 2G /swapfile

sudo chmod 600 /swapfile

sudo mkswap /swapfile

sudo swapon /swapfile

echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

free -h

`

Size it to your plan; 1 to 2 GB is typical for a small server. If a site regularly uses swap, the server needs more memory.

Monitoring basics. At a minimum:

  • An external uptime check on the site's home page and checkout or contact page, alerting by email or chat. External checks catch problems the server cannot report, such as the server being down.
  • Disk space alerts. Full disks are a common cause of outages. df -h shows usage; most providers can alert on it.
  • A habit of reading logs after changes: sudo journalctl -p err -b lists errors since boot, and systemctl --failed lists failed services.
  • Package and reboot status. ls /var/run/reboot-required tells you if a reboot is pending.

8. Backups and the go-live checklist

Take backups before the site goes live, not after the first incident. A sound arrangement has two layers:

  1. 1Provider snapshots of the whole server, for fast rollback after a bad change.
  2. 2Off-server backups of the website files and database, stored with a different provider or account, so a compromised or deleted server does not take its backups with it.

The UK NCSC's small business guide on backing up data recommends keeping a backup separate from your main systems and making backups a routine. For a database, dump it rather than copying live files, for example mysqldump --single-transaction for MySQL or pg_dump for PostgreSQL, then copy the dump off the server. Most importantly, restore a backup to a test server at least once, so you know the process works and how long it takes. A maintenance routine that covers this is described in the website maintenance guide for small business owners.

Before pointing DNS at the server, run through this list:

CheckCommand or actionExpected result
System up to datesudo apt update && apt list --upgradableNothing important pending
Named sudo user worksssh alex@server, then sudo whoamiroot
Root SSH login refusedssh root@serverPermission denied
Password SSH login refusedssh -o PubkeyAuthentication=no alex@serverPermission denied
Effective SSH settings`sudo sshd -T \grep -Ei 'permitroot\passwordauth'`Both no
Firewall activesudo ufw status verboseDeny incoming; only SSH, 80, 443 allowed
Nothing unexpected listeningsudo ss -tulpnDatabases and app servers on 127.0.0.1 only
Security updates automaticcat /etc/apt/apt.conf.d/20auto-upgradesBoth values "1"
Brute-force protectionsudo fail2ban-client status sshd or sudo ufw status shows LIMITJail active, or rate limit in place
Clock synchronisedtimedatectlSystem clock synchronized: yes
Swap presentfree -hSwap total above 0
Uptime monitoringExternal check configuredAlert received on a test
BackupsSnapshot plus off-server backup takenTest restore completed
Provider console accessLog in to the provider's web consoleYou can reach a login prompt

This baseline is a starting point, not the finish line. Next come the web server, TLS certificates and the application itself; if you are deploying a Node.js or Next.js app, the next step is putting it behind Nginx with a process manager. Keep a written record of what you changed and why, so that the next person who logs in, or you in six months, can tell intentional configuration from accidents. Treat changes to the server like changes to the site, tested first and released deliberately, as described in the staging vs production deployment workflow.

If you would rather not own server patching, backups and monitoring yourself, website maintenance covers that work on an ongoing basis, from the first hardening pass through to monthly updates and restore tests.

Related posts

Shared vs VPS vs Managed Hosting for a Small Business Website or Store article cover image
Hosting, VPS & DevOps••11 min read

Shared vs VPS vs Managed Hosting for a Small Business Website or Store

A plain comparison of shared hosting, VPS and managed hosting or PaaS for small business sites and online stores: responsibilities, isolation and performance, the signs a WooCommerce store or Next.js app has outgrown shared hosting, GDPR data residency, and a decision table.

Read article →

Deploy a Next.js 16 App on a VPS with Nginx, systemd or PM2, and HTTPS article cover image
Hosting, VPS & DevOps••11 min read

Deploy a Next.js 16 App on a VPS with Nginx, systemd or PM2, and HTTPS

A working guide to running Next.js 16 on your own VPS: Node.js LTS, build-time versus runtime environment variables, a systemd unit and PM2 alternative, an Nginx server block with certbot HTTPS, the standalone output option, logs, and a two-port release script.

Read article →

Business Email Compromise and Invoice Fraud: How Small Businesses Stop It article cover image
Cyber Security••12 min read

Business Email Compromise and Invoice Fraud: How Small Businesses Stop It

A defensive guide to business email compromise and invoice fraud for small businesses: how the scams work at a high level, the red flags, the payment and email controls that stop them, and what to do in the first hours after a fraudulent transfer in the US, UK and EU.

Read article →

Author

Anushka Dahanayake

Anushka Dahanayake builds SEO-focused websites, e-commerce platforms, dashboards, and automation systems for businesses worldwide.