๐ŸŒ How to Self-Host a Website

Complete beginner's guide โ€” from zero to your own website on your own server

โฌค Beginner-friendly ~2 hours โ‚ฌ5โ€“10/month
๐Ÿ“‹ Table of Contents
  1. Why self-host your website?
  2. What you need (and what it costs)
  3. Step 1 โ€” Buy a domain name
  4. Step 2 โ€” Rent a VPS
  5. Step 3 โ€” Connect to your server via SSH
  6. Step 4 โ€” Secure your server (basic hardening)
  7. Step 5 โ€” Install Caddy (web server + automatic HTTPS)
  8. Step 6 โ€” Point your domain at your server
  9. Step 7 โ€” Deploy your website files
  10. Step 8 โ€” Going further โ€” WordPress, static sites, Docker
  11. Step 9 โ€” Backups โ€” never skip this
?
Why self-host your website?

When you use WordPress.com, Wix, Squarespace, or similar platforms, you are renting space on their servers under their rules. They can delete your content, raise prices, go bankrupt, change their Terms of Service, or insert ads. You own nothing.

When you self-host, you control everything: the server, the software, the data, the cost. Your website cannot be taken down by a platform decision. You pay for raw compute power, not for someone else's profit margin on top of it.

A basic self-hosted website costs ~โ‚ฌ5โ€“10/month for the VPS and โ‚ฌ10โ€“15/year for a domain. That's often cheaper than Squarespace, and you get complete freedom.

โ˜…
What you need (and what it costs)
WhatWhyCost
Domain nameYour address on the internet (e.g. yourname.com)โ‚ฌ10โ€“15/year
VPS (Virtual Private Server)The computer that serves your websiteโ‚ฌ4โ€“10/month
SSH clientTo connect to your server (built-in on Mac/Linux, free on Windows)FREE
Caddy (web server)Serves your files and handles HTTPS automaticallyFREE
Your website filesHTML/CSS or a CMS like WordPressFREE to start
๐Ÿ’ก Total beginner cost: About โ‚ฌ15/year (domain) + โ‚ฌ5/month (VPS) = roughly โ‚ฌ75/year. That is less than most hosted platforms, and you own everything.
1
Buy a Domain Name

A domain name is your address on the internet โ€” like yourname.com. You rent this from a domain registrar, typically for โ‚ฌ10โ€“15 per year.

Recommended registrars:

  • Namecheap โ€” simple, affordable, good privacy defaults
  • Porkbun โ€” very competitive pricing, beginner-friendly
  • OVH โ€” European option with strong privacy practices

What to pick: A .com or .net is safe and universally recognised. Country domains (.lu, .de, .fr) work great if you're targeting a local audience. Avoid unusual extensions for your main site.

๐Ÿ’ก Tip: Enable WHOIS privacy when you buy your domain. This hides your personal details (name, address, email) from the public domain lookup database. Most registrars offer this free.

Once you have your domain, log into your registrar's dashboard. You'll come back here in Step 6 to point it at your server.

2
Rent a VPS (Virtual Private Server)

A VPS is a virtual computer running in a data centre that is always online. You pay monthly for access. Think of it as renting a computer that never gets turned off.

Recommended providers (European-hosted, privacy-respecting):

  • Hetzner Cloud (Germany) โ€” best price-to-performance in Europe. CX22: 2 vCPU, 4GB RAM, 40GB SSD for ~โ‚ฌ4.5/month. My top recommendation for beginners.
  • OVH (France) โ€” solid European provider, good for scaling up later
  • Netcup (Germany) โ€” excellent value, reliable

What to choose when setting up your VPS:

  • Operating system: Choose Debian 12 (Bookworm). It's stable, well-documented, and perfect for servers.
  • Location: Pick a data centre close to your audience (Germany or Finland for Europe)
  • Size: The smallest plan (1โ€“2 vCPU, 2โ€“4GB RAM) is enough to start
๐Ÿ’ก SSH Key: When your provider asks if you want to add an SSH key, do it. We explain SSH keys in Step 3. Most providers will generate one for you or let you paste your public key.

After provisioning, your provider will give you: an IP address (like 65.21.100.42), a username (usually root), and either a password or SSH key. Save these.

3
Connect to Your Server via SSH

SSH (Secure Shell) is how you talk to your server from your own computer. It's a secure, encrypted connection to a command-line interface on your VPS.

On Linux or macOS: Open your terminal. On Windows: Open PowerShell or Windows Terminal โ€” SSH is built in since Windows 10.

your-computer:~$
# Replace 65.21.100.42 with your actual IP address ssh root@65.21.100.42 The authenticity of host '65.21.100.42' can't be established. ECDSA key fingerprint is SHA256:abc123... Are you sure you want to continue connecting? (yes/no): yes root@your-server:~#

You're now logged into your server. The prompt root@your-server:~# means you're the administrator (root) on the remote machine. Everything you type here runs on the server, not your own computer.

โš  Security: We'll disable root login and set up a regular user in the next step. Never leave root login enabled on a production server.
4
Secure Your Server (Basic Hardening)

A fresh VPS connected to the internet will be scanned by bots within minutes. These basic steps massively reduce your attack surface.

root@your-server:~#
# Update the system first apt update && apt upgrade -y # Create a regular user (replace "alice" with your username) adduser alice usermod -aG sudo alice # Copy your SSH key to the new user (on your LOCAL machine, not the server) # Run this from your own computer: ssh-copy-id alice@65.21.100.42 # Install fail2ban (blocks brute-force login attempts) apt install fail2ban -y systemctl enable fail2ban && systemctl start fail2ban # Configure the firewall apt install ufw -y ufw allow 22/tcp # SSH ufw allow 80/tcp # HTTP ufw allow 443/tcp # HTTPS ufw enable Firewall is active and enabled on system startup # Disable root SSH login (edit the SSH config) nano /etc/ssh/sshd_config # Find "PermitRootLogin" and change it to: PermitRootLogin no # Find "PasswordAuthentication" and change it to: PasswordAuthentication no systemctl restart sshd
๐Ÿ’ก Test before you logout: Open a second terminal and make sure you can still SSH in as your new user (ssh alice@65.21.100.42) before closing the root session. If you lock yourself out, use your VPS provider's emergency console.

From now on, connect as your regular user: ssh alice@65.21.100.42

5
Install Caddy (Web Server + Automatic HTTPS)

Caddy is a web server that automatically handles HTTPS (SSL certificates) for you. You don't need to configure Let's Encrypt manually, renew certificates, or touch any of that โ€” Caddy does it all.

alice@your-server:~$
# Install Caddy sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list sudo apt update && sudo apt install caddy -y โœ“ Caddy installed and running # Check it's running sudo systemctl status caddy โ— caddy.service - Caddy Active: active (running)

Caddy is configured with a Caddyfile. Here's a minimal one that serves a static website:

alice@your-server:~$ sudo nano /etc/caddy/Caddyfile
# Replace yourdomain.com with your actual domain yourdomain.com { root * /var/www/yourdomain.com file_server }
alice@your-server:~$
# Create the web root directory sudo mkdir -p /var/www/yourdomain.com sudo chown -R caddy:caddy /var/www/yourdomain.com # Reload Caddy after config changes sudo systemctl reload caddy
๐Ÿ’ก How automatic HTTPS works: Once your domain points to your server (next step), Caddy automatically requests a free SSL certificate from Let's Encrypt and renews it before it expires. Zero configuration needed.
6
Point Your Domain at Your Server (DNS)

DNS (Domain Name System) is the phonebook of the internet. It translates your domain name into your server's IP address. You need to create two DNS records in your registrar's control panel.

Go to your domain registrar (Namecheap, Porkbun, etc.), find the DNS settings for your domain, and add these records:

DNS Records to Add
Type Name Value TTL A @ 65.21.100.42 300 โ†‘ "@" means your root domain (yourdomain.com) โ†‘ Replace 65.21.100.42 with YOUR server's IP A www 65.21.100.42 300 โ†‘ This makes www.yourdomain.com work too

DNS changes take a few minutes up to 24 hours to propagate worldwide (usually 5โ€“30 minutes in practice). You can check if your domain resolves with:

your-computer:~$
dig yourdomain.com +short 65.21.100.42 # If you see your IP, DNS is working
7
Deploy Your Website Files

Now put your website files on the server. The simplest option is a static HTML page:

alice@your-server:~$
# Create a basic index.html sudo nano /var/www/yourdomain.com/index.html
/var/www/yourdomain.com/index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>My Self-Hosted Site</title> </head> <body> <h1>I own this server.</h1> <p>No platform. No landlord. Just code and freedom.</p> </body> </html>

Visit your domain in a browser โ€” you should see your page, served over HTTPS with a valid certificate. Congratulations. You self-host your first website.

To upload files from your computer, use scp or an SFTP client like FileZilla (or the VS Code Remote SSH extension):

your-computer:~$
# Upload a file from your computer to the server scp /path/to/local/file.html alice@65.21.100.42:/var/www/yourdomain.com/ # Upload an entire folder scp -r /path/to/local/folder/ alice@65.21.100.42:/var/www/yourdomain.com/
8
Going Further โ€” WordPress, Static Site Generators, Docker

A static HTML site is perfect to start, but you have options to grow:

Option A โ€” WordPress (dynamic CMS): Familiar interface, thousands of themes. Runs on PHP + MySQL. More complex to maintain but very powerful.

alice@your-server:~$
# Install WordPress with Docker Compose (easiest method) sudo apt install docker.io docker-compose -y mkdir ~/wordpress && cd ~/wordpress nano docker-compose.yml
~/wordpress/docker-compose.yml
services: wordpress: image: wordpress:latest ports: - "8080:80" environment: WORDPRESS_DB_HOST: db WORDPRESS_DB_USER: wpuser WORDPRESS_DB_PASSWORD: changeme WORDPRESS_DB_NAME: wordpress volumes: - wordpress_data:/var/www/html depends_on: - db db: image: mariadb:latest environment: MYSQL_DATABASE: wordpress MYSQL_USER: wpuser MYSQL_PASSWORD: changeme MYSQL_ROOT_PASSWORD: changeme_root volumes: - db_data:/var/lib/mysql volumes: wordpress_data: db_data:
alice@your-server:~/wordpress$
docker-compose up -d โœ“ WordPress running on port 8080

Then update your Caddyfile to proxy requests to WordPress:

/etc/caddy/Caddyfile
yourdomain.com { reverse_proxy localhost:8080 }

Option B โ€” Static site generators: Hugo, Zola, or Eleventy generate pure HTML from Markdown. Blazing fast, extremely secure (no PHP, no database), perfect for blogs and documentation sites. Highly recommended.

๐Ÿ’ก Recommendation for beginners: Start with static HTML, then try Hugo. Only add WordPress or a database if you genuinely need dynamic content.
!
Step 9 โ€” Backups: Never Skip This

Self-hosting means you are responsible for your own data. Hard drives fail. Mistakes happen. Ransomware exists. Backups are not optional.

Set up automatic backups with Restic โ€” a simple, encrypted backup tool:

alice@your-server:~$
sudo apt install restic -y # Initialise a local backup repository (backup to a different location in production) restic init --repo /backup/mysite # Back up your website files restic -r /backup/mysite backup /var/www/yourdomain.com # Automate daily backups via cron crontab -e # Add this line: runs backup every day at 2am 0 2 * * * restic -r /backup/mysite backup /var/www/yourdomain.com
โš  A backup that stays on the same server is not a backup. Use Restic's S3-compatible backend to send backups to Hetzner Object Storage, Backblaze B2, or another server. If your server dies, you need your data somewhere else.

๐ŸŽ‰ You Did It โ€” What's Next?

โ†’ Next tutorial: Set up your own email server with Mailcow โ†’ Switch to private messaging: Proton, Signal, Matrix โ†’ Explore Hugo themes for a better-looking static site โ†’ Discover more services to self-host (Awesome Self-Hosted list) โ†’ Join r/selfhosted โ€” the best community for getting unstuck