Most VPS guides either skip the preparation or skip the hardening. This one does neither. What follows is a realistic timeline for standing up a VPS that is properly secured, properly configured, and ready to run software. Each phase is a day of focused work, not eight hours of sitting at a terminal.
The target: a Debian or Ubuntu LTS server running Apache with a valid SSL certificate, locked down, monitored, and backed up. Everything beyond that (WordPress, Nextcloud, Mattermost, whatever you actually want to run) builds on this foundation.
This guide assumes you are starting from scratch with no existing VPS. If you already have a server running but want to harden it, skip to T+1. If you are mid-setup and something has gone wrong, get in touch and we can pick up from wherever you are.
Contracts, scope, and preparation
Before any server exists, get the paperwork done and the plan agreed. If you are working with Techster Consulting, this is when we sign the engagement letter and agree the Statement of Work. Even if you are doing this alone, write down what you want to run before you spend a penny. Scope creep during setup is the most common reason people end up with a messy, half-configured server.
What to decide at this stage
- What software you intend to run (website, file storage, chat, git hosting, etc.)
- How much RAM and storage you genuinely need. A basic website and a few self-hosted apps run comfortably on 2 vCPU / 4 GB RAM / 40 GB SSD. Do not over-provision yet; you can resize later.
- Which region to host in. For GDPR purposes, choose a provider with data centres in the EU. Germany, France, and Finland are common choices.
- Your domain name, if you do not already have one. You will need it at T+3.
Recommended providers
- 🇬🇧 Fasthosts: UK-native, Tier-IV data centres in Worcester and London, 100% renewable energy, ISO 27001 certified. VPS 1 (1 vCPU, 2 GB RAM, 60 GB NVMe) from £2/month for the first year, then £3/month. UK-based support, excellent latency for UK clients. Watch out for renewal price jumps on promotional plans.
- 🇬🇧 Krystal: smaller UK-owned provider, B Corp certified, London data centre. Plans from around £9/month. UK-based support team with fast response times. Good choice if you want a genuinely independent UK host with strong ethics credentials.
- 🇬🇧 IONOS UK: German-owned but with UK data centres, strong GDPR compliance, 99.99% uptime SLA, unlimited traffic at 1 Gbit/s. Entry VPS from £1/month promotional, settling around £3 to £5/month. Includes a personal consultant on every plan.
- 🇩🇪 Hetzner: consistently the best value in Europe. CPX21 or CX22 (2 vCPU, 4 GB RAM, 40 GB SSD) around £4 to £7/month. No frills, excellent reliability, transparent pricing, no introductory rate tricks. Data centres in Nuremberg, Falkenstein, and Helsinki.
- 🇫🇷 OVHcloud: French provider, strong EU data sovereignty story, VPS Starter or Value range at similar pricing to Hetzner. Good if a French data centre matters for your compliance requirements.
- 🇩🇪 Contabo: more RAM per pound than almost anyone else. Good for memory-heavy workloads (Nextcloud, Mattermost, Grafana stacks). Support is slower than Hetzner. Data centres in Germany, US, and Asia.
If your clients are UK-based and latency or data residency within the UK matters, Fasthosts or Krystal are the natural first choices. If cost is the primary driver and EU residency is sufficient, Hetzner is hard to beat.
Choose Ubuntu 24.04 LTS or Debian 12 (Bookworm) as your operating system. Both have long support windows, large communities, and all the packages you will need. Avoid non-LTS releases on production servers.
Accounts, SSH keys, and the setup plan
This is the last day before the server exists. Use it to generate your SSH key pair and create your provider account. Do not skip the SSH key step; you will need it at T+0.
Generate your SSH key pair
On your local machine (Linux, macOS, or Windows WSL):
ssh-keygen -t ed25519 -C "your@email.com"
When prompted for a file location, accept the default (~/.ssh/id_ed25519). Set a strong passphrase. This generates two files: id_ed25519 (private key, never share this) and id_ed25519.pub (public key, this goes on the server).
View your public key:
cat ~/.ssh/id_ed25519.pub
Copy the output. You will paste it into your provider's control panel when creating the server.
Create your provider account
Register at your chosen provider. Use a strong, unique password and enable two-factor authentication on the account immediately. Your provider account controls your server; it is a high-value target.
Plan your firewall rules before you need them
Write down which ports you intend to open. At minimum you need:
- 22/TCP: SSH (you will change this to a non-standard port at T+1)
- 80/TCP: HTTP (required for Let's Encrypt certificate issuance)
- 443/TCP: HTTPS
Everything else stays closed until you have a specific reason to open it.
VPS creation and first login
Create the server through your provider's control panel. Select your chosen OS, paste your public SSH key when prompted, and note the server's IPv4 address once it provisions (usually under two minutes).
First login
Most providers provision with a root user. Log in:
ssh root@YOUR_SERVER_IP
You are in. Now do not do anything else yet. The server is currently open to the world on every port. Your first job is hardening, not installing software.
Set the hostname
hostnamectl set-hostname yourservername
Set the timezone
timedatectl set-timezone Europe/London
Check the provider firewall
Most providers offer a network-level firewall (Hetzner calls it a Firewall, OVH calls it a Security Group) that sits in front of your server. Enable it now and configure it to allow only ports 22, 80, and 443 inbound. This is your first line of defence and it costs nothing extra.
Do not skip the provider-level firewall. Even with ufw running on the server itself, a network firewall stops traffic before it reaches your machine. Two layers are better than one.
Basic hardening: users, SSH, firewall, fail2ban
This is the most important day. A server skipping this step is not a server you should put data on. Take your time here.
1. Create a non-root admin user
Running everything as root is dangerous. Create a dedicated admin user:
adduser adminusername
usermod -aG sudo adminusername
Copy your SSH public key to the new user:
mkdir -p /home/adminusername/.ssh
cp /root/.ssh/authorized_keys /home/adminusername/.ssh/
chown -R adminusername:adminusername /home/adminusername/.ssh
chmod 700 /home/adminusername/.ssh
chmod 600 /home/adminusername/.ssh/authorized_keys
Open a second terminal and verify you can log in as the new user before closing the root session:
ssh adminusername@YOUR_SERVER_IP
2. Harden SSH
Edit the SSH daemon configuration:
sudo nano /etc/ssh/sshd_config
Set or confirm these values:
Port 2222
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
X11Forwarding no
MaxAuthTries 3
LoginGraceTime 20
Changing the port to something non-standard (2222 is common, pick your own) eliminates the vast majority of automated SSH brute-force attempts. Update your provider firewall to allow the new port and remove port 22.
Restart SSH and verify the new config works before logging out:
sudo systemctl restart sshd
Test from a new terminal: ssh -p 2222 adminusername@YOUR_SERVER_IP
3. Configure ufw (Uncomplicated Firewall)
sudo apt install ufw -y
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 2222/tcp comment 'SSH'
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'
sudo ufw enable
sudo ufw status verbose
You now have two firewall layers: the provider-level network firewall and ufw on the server itself. Both enforce the same rules.
4. Install and configure fail2ban
fail2ban monitors log files for repeated failed authentication attempts and bans the offending IP addresses automatically.
sudo apt install fail2ban -y
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
Create a local configuration file (never edit the default; it gets overwritten on updates):
sudo nano /etc/fail2ban/jail.local
Paste:
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
backend = systemd
[sshd]
enabled = true
port = 2222
logpath = %(sshd_log)s
sudo systemctl restart fail2ban
sudo fail2ban-client status
5. Install essential utilities
sudo apt update && sudo apt install -y \
htop \
curl \
wget \
git \
unzip \
build-essential \
ca-certificates \
gnupg \
lsb-release \
net-tools \
dnsutils \
ncdu \
tmux
htop gives you a live view of CPU, memory, and processes. ncdu shows disk usage interactively. tmux keeps sessions alive if your SSH connection drops during a long installation. build-essential provides the C compiler toolchain needed by many packages. The rest are networking and utility staples you will reach for constantly.
System updates and security tooling
Before installing any application software, get the OS fully patched and automatic security updates in place.
1. Full system update
sudo apt update && sudo apt upgrade -y && sudo apt autoremove -y
Reboot if the kernel was updated:
sudo reboot
Reconnect after 30 seconds and confirm the new kernel is running:
uname -r
2. Enable unattended security updates
sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure --priority=low unattended-upgrades
Select "Yes" when prompted. This enables automatic installation of security patches only, leaving you in control of everything else.
Verify the configuration:
cat /etc/apt/apt.conf.d/20auto-upgrades
You should see:
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
3. Install logwatch for daily log summaries
sudo apt install logwatch -y
Configure it to email you a daily summary:
sudo nano /etc/logwatch/conf/logwatch.conf
Set:
Output = mail
MailTo = you@yourdomain.com
MailFrom = logwatch@yourservername
Detail = Low
Service = All
You will need a working mail setup for this to send email. As an alternative, run sudo logwatch --output stdout --detail low manually each week to review what your server has been doing.
4. Check for open ports
Verify that only the ports you expect are listening:
sudo ss -tlnp
You should see only SSH (on your custom port), and nothing else at this stage. If you see unexpected services, investigate before continuing.
5. Set up automatic backups
Before installing any application software, configure backups. The sequence matters: back up before you put data on it, not after.
Enable your provider's snapshot feature and set it to daily. This typically costs £1 to £3/month and gives you a full server restore point.
For application-level backups, install a simple cron-based script. Create the backup directory:
sudo mkdir -p /var/backups/local
sudo chown adminusername:adminusername /var/backups/local
Add a daily backup job to your crontab:
crontab -e
# Daily backup of home directory and /etc at 02:00
0 2 * * * tar -czf /var/backups/local/home-$(date +\%F).tar.gz /home/adminusername
0 2 * * * tar -czf /var/backups/local/etc-$(date +\%F).tar.gz /etc
# Keep only last 7 days
0 3 * * * find /var/backups/local -name "*.tar.gz" -mtime +7 -delete
Offsite backups come later when you have actual data to protect, but the habit starts now.
Web server, Docker, SSL, and your first site
The server is hardened, patched, and backed up. Now you install application software. The order still matters: web server first, SSL second, applications third.
1. Install Apache
sudo apt install apache2 -y
sudo systemctl enable apache2
sudo systemctl start apache2
Enable the modules you will need:
sudo a2enmod rewrite headers ssl proxy proxy_http
2. Configure your domain's DNS
At your domain registrar, create an A record pointing your domain to your server's IPv4 address. If you want to also handle IPv6, add an AAAA record pointing to your server's IPv6 address. DNS propagation takes between a few minutes and 48 hours depending on your registrar and TTL settings. You cannot issue an SSL certificate until the A record resolves correctly.
Verify propagation:
dig +short yourdomain.com
Once it returns your server's IP, proceed.
3. Issue an SSL certificate with Certbot
sudo apt install certbot python3-certbot-apache -y
sudo certbot --apache -d yourdomain.com -d www.yourdomain.com
Certbot will modify your Apache configuration automatically and set up auto-renewal. Verify renewal works:
sudo certbot renew --dry-run
Certificates expire after 90 days. Certbot installs a systemd timer that renews automatically. Confirm it is active:
sudo systemctl status certbot.timer
4. Harden Apache
Edit the Apache security configuration:
sudo nano /etc/apache2/conf-available/security.conf
Set or confirm:
ServerTokens Prod
ServerSignature Off
TraceEnable Off
Add a security headers file:
sudo nano /etc/apache2/conf-available/security-headers.conf
<IfModule mod_headers.c>
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-XSS-Protection "1; mode=block"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
</IfModule>
sudo a2enconf security security-headers
sudo systemctl reload apache2
5. Install Docker
Many applications (Mattermost, Vaultwarden, Grafana, Nextcloud) run most reliably in Docker containers. Install it using the official repository, not the version in the Ubuntu/Debian repos (it is usually outdated):
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Add your admin user to the docker group so you do not need sudo for every docker command:
sudo usermod -aG docker adminusername
newgrp docker
Verify:
docker run hello-world
6. Deploy a basic website
Place your site files in the web root:
sudo mkdir -p /var/www/yourdomain.com/html
sudo chown -R adminusername:www-data /var/www/yourdomain.com
sudo chmod -R 755 /var/www/yourdomain.com
Create an Apache virtual host:
sudo nano /etc/apache2/sites-available/yourdomain.com.conf
<VirtualHost *:80>
ServerName yourdomain.com
ServerAlias www.yourdomain.com
Redirect permanent / https://yourdomain.com/
</VirtualHost>
<VirtualHost *:443>
ServerName yourdomain.com
ServerAlias www.yourdomain.com
DocumentRoot /var/www/yourdomain.com/html
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/yourdomain.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/yourdomain.com/privkey.pem
<Directory /var/www/yourdomain.com/html>
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/yourdomain.com-error.log
CustomLog ${APACHE_LOG_DIR}/yourdomain.com-access.log combined
</VirtualHost>
sudo a2ensite yourdomain.com.conf
sudo a2dissite 000-default.conf
sudo apache2ctl configtest
sudo systemctl reload apache2
Visit https://yourdomain.com in a browser. You should see your site over HTTPS with a valid certificate. HTTP requests should redirect to HTTPS automatically.
At this point you have a hardened, patched, firewall-protected VPS running Apache with a valid SSL certificate, automatic renewal, security headers, Docker installed, and daily backups configured. That is a production-ready foundation. Everything else you want to run (WordPress, Nextcloud, Mattermost, Vaultwarden, Grafana) installs on top of this.
What comes next
The timeline above ends with a secure, working web server and Docker ready to go. The next steps depend on what you actually want to run. Installing WordPress takes about 20 minutes on this foundation. Nextcloud takes a similar amount of time. Mattermost, Vaultwarden, and Grafana all have official Docker images with documented setup procedures.
The pattern for each application is the same: pull the Docker image, configure a reverse proxy in Apache, point your domain, issue a certificate. Once you have done it once, the second and third installations are much faster.
Ongoing maintenance
A well-configured server does not demand much. Budget roughly one to two hours per month for:
- Reviewing
fail2banlogs for unusual activity:sudo fail2ban-client status sshd - Checking disk usage:
ncdu / - Reviewing the
ufwlog for unexpected traffic:sudo tail -50 /var/log/ufw.log - Confirming your SSL certificate is not about to expire:
sudo certbot certificates - Checking that your backup files exist and have current timestamps
Unattended upgrades handle security patches automatically. Application updates (Docker containers, WordPress plugins, etc.) you manage yourself on your own schedule.
If any step in this guide is unclear, or if something has gone differently on your server than described here, get in touch. Debugging other people's setups is something we do regularly, and it is usually faster than it looks.