🛠️ Tutorials · 11 min read

Self-Hosting Your Website in 2026: Complete Guide (VPS, Docker, HTTPS)

2026 technical guide to self-hosting on a VPS: choosing plans, Docker setup, Let's Encrypt HTTPS, security, and real costs. Compare self-hosting vs. cloud.

S By Selfhostr Team · independent tests
ⓘ This article may contain affiliate links (no extra cost to you, it supports our tests). See the disclosure.

Hosting your own website in 2026 is no longer reserved for sysadmin experts or geeks with symmetric fiber connections. With the maturity of containerization tools, the democratization of free SSL certificates, and the drop in cloud infrastructure costs, the barrier to entry has decreased significantly. Yet, the temptation of the “click-and-go” SaaS platforms (Wix, Shopify, WordPress.com) remains strong.

This guide does not aim to convince you to become a full-time system administrator, but to provide a precise technical roadmap to regain control of your data, bandwidth, and long-term costs. We will deconstruct the process of installing a site (static, WordPress, or web application) on a VPS (Virtual Private Server), emphasizing robustness, security, and reproducibility via Docker.

Before diving into terminal commands, it is crucial to understand the arena you are entering. Physical self-hosting at home and VPS self-hosting are two distinct paradigms, with very different technical and financial trade-offs.

Self-hosting vs VPS: The technical duel

The first decision to make is hardware-related. Do you have a PC running 24/7 in your living room, or will you rent a remote server?

Physical self-hosting (Home Lab)

This is the “old-school” option. You use an old laptop, a Raspberry Pi, or a mini-PC as a server.

VPS (Virtual Private Server)

A VPS is a dedicated virtual machine rented from a host (Hetzner, OVH, DigitalOcean, AWS Lightsail).

For the rest of this tutorial, we will assume you have opted for a VPS. It is the most reliable way to ensure your site stays online, fast, and secure without having to manage the noise of your fan at 3 AM.

Step 1: Choosing the right VPS offer

The VPS market in 2026 is saturated, but quality varies. Avoid generalist consumer hosts that sell “cloud” at rock-bottom prices but with poor disk I/O performance. Prefer hosts specialized in infrastructure.

Benchmarks and real costs

Here is a comparative analysis based on the average needs of a modern website in 2026.

CriterionEntry-level (Blog/Static)Mid-range (WordPress/DB)High Performance (App/API)
CPU1 vCPU (e.g., AMD EPYC)2 vCPU4+ vCPU
RAM1 GB - 2 GB4 GB8 GB+
Storage20 GB NVMe SSD50 GB NVMe SSD100 GB+ NVMe
Bandwidth1-2 TB/month2-5 TB/monthUnlimited or very high
Monthly Cost~€3 - €5~€8 - €15~€20 - €40
Host TypesHetzner Cloud, DigitalOceanHetzner, OVH, LinodeAWS, GCP, DigitalOcean

Technical Analysis: For a WordPress site with a local database, 1 GB of RAM is often too tight if you don’t do aggressive tuning. The MySQL/MariaDB engine consumes a lot of memory. 4 GB is the comfortable floor to avoid swap (which kills disk performance). For a static site (Hugo, Jekyll) or a lightweight Node.js app, 1 GB is more than enough.

Our recommendation: Start small. It is always possible to scale up on most modern cloud platforms without migrating your data. Choose a host that allows easy disk or snapshot migration.

Step 2: Initial server hardening

Once your VPS is provisioned, you receive an IP address and a root password via email. Do not connect directly with root via password for the rest of the process. The first thing to do is to harden SSH access.

  1. Create a dedicated user:

    adduser monuser
    usermod -aG sudo monuser
  2. Configure SSH keys: Generate an SSH key on your local machine (ssh-keygen) and copy it to the server (ssh-copy-id monuser@IP_OF_VPS).

  3. Disable root login and password login: Edit /etc/ssh/sshd_config:

    PermitRootLogin no
    PasswordAuthentication no

    Restart the SSH service (systemctl restart sshd).

  4. Configure the firewall (UFW): Enable the firewall and allow only necessary traffic.

    sudo ufw allow OpenSSH
    sudo ufw allow 80/tcp   # HTTP
    sudo ufw allow 443/tcp  # HTTPS
    sudo ufw enable

These steps eliminate 90% of automated attacks (brute force) that plague servers exposed to the public internet.

Step 3: Installing Docker and Docker Compose

Forget manual installation of Nginx, PHP, MySQL, or Node.js on the filesystem. In 2026, the standard is container isolation. Docker allows you to deploy your web stack in a reproducible, clean, and easy-to-update manner.

Installing Docker

The safest method is to use the official installation script or the packages from the Docker repository.

# System update
sudo apt update && sudo apt upgrade -y

# Install dependencies
sudo apt install ca-certificates curl gnupg

# Add the official GPG key
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

# Add the repository
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# Install
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Verify the installation:

sudo docker run hello-world
sudo docker compose version

Project structure

Create a directory for your site. For example, for a WordPress site or static app, the file structure will be crucial for maintenance.

/home/monuser/web-app/
├── docker-compose.yml
├── .env
├── nginx/
│   └── nginx.conf
├── app/
│   └── (your source code here)
└── data/
    └── (for databases or uploads)

Step 4: Deploying a site (Concrete cases)

Case A: Static Site (HTML/CSS/JS or generator)

This is the simplest and most performant case. You only need an Nginx container serving static files.

Create a docker-compose.yml file:

version: '3.8'

services:
  web:
    image: nginx:alpine
    container_name: mon_site_statique
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./app:/usr/share/nginx/html:ro
      - ./nginx/conf.d:/etc/nginx/conf.d
      - ./certs:/etc/nginx/certs
    depends_on:
      - certbot

  certbot:
    image: certbot/certbot
    container_name: certbot
    volumes:
      - ./certs:/etc/letsencrypt
      - ./webroot:/var/www/certbot
    entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"

Note: This simplified approach uses a shared volume for certificates. For robust production, it is better to use a dedicated reverse proxy (see below).

Case B: WordPress with Docker

WordPress is more complex as it requires a database. Let’s use MariaDB and PHP-FPM via Nginx.

version: '3.8'

services:
  db:
    image: mariadb:10.6
    container_name: wp_db
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASS}
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wp_user
      MYSQL_PASSWORD: ${DB_PASS}
    volumes:
      - db_data:/var/lib/mysql
    networks:
      - wp_network

  wordpress:
    image: wordpress:php8.2-fpm
    container_name: wp_app
    restart: always
    depends_on:
      - db
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wp_user
      WORDPRESS_DB_PASSWORD: ${DB_PASS}
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - wp_data:/var/www/html
      - ./uploads.ini:/usr/local/etc/php/conf.d/uploads.ini
    networks:
      - wp_network

  nginx:
    image: nginx:alpine
    container_name: wp_nginx
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d
      - wp_data:/var/www/html
      - ./certs:/etc/nginx/certs
    depends_on:
      - wordpress
    networks:
      - wp_network

volumes:
  db_data:
  wp_data:

networks:
  wp_network:

In this scenario, you must configure an nginx.conf file in the nginx/conf.d/ folder to proxy_pass to the PHP-FPM container (wordpress:9000). This is the standard method for running PHP in Docker.

Step 5: HTTPS and Let’s Encrypt Certificates

Having a site over HTTP in 2026 is unacceptable. Browsers mark insecure sites as “Not Secure,” which affects SEO and user trust.

The most reliable method is not to generate certificates inside each container, but to use a Reverse Proxy or an external tool that handles automatic renewal.

To keep things simple and without heavy external dependencies, we will use a standard Nginx configuration coupled with Certbot, but in a secure manner.

However, the current best practice is to use Cloudflare Tunnel (if you use Cloudflare) or to configure Certbot in standalone or webroot mode.

Here is how to obtain a valid SSL certificate for your domain monsite.com:

  1. Install Certbot on the host (not in a container to simplify port 80/443 management):

    sudo apt install certbot
  2. Stop Nginx if you installed it manually, or ensure it is not listening on 80/443 yet.

  3. Obtain the certificate:

    sudo certbot certonly --standalone -d monsite.com -d www.monsite.com
  4. Configure Nginx (in your container or host) to serve files from the /etc/letsencrypt/live/monsite.com/ folder.

  5. Automate renewal: Certificates expire every 90 days. Create a cron job on the server:

    sudo crontab -e
    # Add this line to check every day at midnight
    0 0 * * * /usr/bin/certbot renew --quiet --post-hook "systemctl restart nginx"

If you use Docker, the cleanest way is to use the certbot/certbot image in a dedicated container that mounts certificate volumes, as shown in the static example above.

Step 6: Backups and Recovery

A server without backups is a server waiting for disaster. In self-hosting, you are your own disaster recovery team.

3-2-1 Strategy

Technical implementation

For a VPS, critical data includes:

  1. Your application files (app/, wp-content/uploads).
  2. The database.
  3. Configurations (docker-compose.yml, Nginx configs, SSH keys).

Simple backup script (backup.sh):

#!/bin/bash
DATE=$(date +%Y-%m-%d)
BACKUP_DIR="/home/monuser/backups"
REMOTE_DIR="user@remote-server:/backups"

# 1. Database backup (MySQL example)
docker exec wp_db mysqldump -u root -p${DB_ROOT_PASS} wordpress > $BACKUP_DIR/db_$DATE.sql

# 2. Archive files
tar -czf $BACKUP_DIR/files_$DATE.tar.gz /home/monuser/web-app/app

# 3. Upload to remote storage (SCP or Rclone to S3/Backblaze)
scp $BACKUP_DIR/* $REMOTE_DIR/

# 4. Local cleanup (keep only 7 days)
find $BACKUP_DIR -type f -mtime +7 -delete

Make this script executable and schedule it via crontab (e.g., every day at 2 AM). Sending to object storage (S3 compatible) is highly recommended as it is immutable and inexpensive.

Which choice for your profile?

We have seen the technical steps. But technology is just a means. Here is how to choose your approach based on your reality.

Profile 1: The Curious Beginner

Profile 2: The Developer / Freelancer

Profile 3: The Enterprise / Critical Site

FAQ: Frequently Asked Questions

Can I migrate from WordPress.com to my own VPS?

Yes, but be aware of the complexity. WordPress.com (SaaS) handles updates, security, and caching. On your VPS, you have to do everything yourself. Use WordPress’s native export/import tool to retrieve your posts and media. Think about configuring a fast cache (Redis or Varnish) and a CDN (Cloudflare) to compensate for the performance loss of the SaaS.

How long does the initial setup take?

If you are a beginner, count 4 to 8 hours for the first installation, including understanding Docker, SSL configuration, and SSH hardening. Once the base is in place, deploying a new site takes 15 minutes. The learning curve is steep at first, then flattens out.

Is Docker mandatory?

No, but it is highly recommended. You could install Nginx and PHP directly on the OS (apt install nginx php-fpm). However, this creates a “snowflake server” (a unique and hard-to-reproduce server). If the server crashes, you have to reinstall everything manually. With Docker, you restore your server by recreating containers from a docker-compose.yml file. It is a matter of reproducibility and cleanliness.

What if my VPS is hacked?

Prevention is key (OS updates, fail2ban, SSH keys). If the worst happens, do not try to “clean” the server. Start from a clean image (snapshot), restore your data from the last known good backup, and rebuild the infrastructure. Reinstallation is often faster and safer than detoxifying a compromised system.

Conclusion

Hosting your own website in 2026 is a rewarding technical exercise that offers total control over your data and costs. Going through a VPS and Docker represents the current standard for combining performance, security, and ease of maintenance.

Do not underestimate the initial hardening phase and the setup of backups. These two pillars distinguish an unorganized hobbyist from a professional self-hosting practitioner. Start small, document your procedures, and do not hesitate to iterate. Your infrastructure is your code; treat it with the same rigor.

Tags: VPSDockerHTTPSLet's EncryptSelf-HostingWeb HostingCloudSecurityDevOps2026 Guide

Related

🛠️ Tutorials

Hosting Nextcloud on a VPS in 2026: Complete Guide (Docker, HTTPS, Performance, Backups)

Comprehensive 2026 technical guide for deploying Nextcloud on a VPS using Docker. Covers server sizing, PostgreSQL and Redis optimization, HTTPS configuration, and reliable backup strategies.

Read
🛠️ Tutorials

Hosting n8n on a VPS in 2026: Complete Self-Hosted Guide (Docker, HTTPS, Backups)

2026 technical guide to self-hosting n8n on a VPS using Docker Compose, HTTPS, and security best practices. Compare cloud vs on-premise costs to automate workflows without subscriptions.

Read
🛠️ Tutorials

Hosting a Discord Bot 24/7 on a VPS in 2026: Complete Guide (Node/Python, systemd, Docker)

2026 technical guide for hosting a Discord bot continuously on a VPS. Compare Node vs Python, systemd vs Docker, sizing, costs, and token security for developers.

Read