A static blog is a folder of HTML, CSS and images. That is the whole of it. There is no database to back up, no PHP version to chase and no admin login for bots to hammer. Hosting one yourself is mostly a matter of putting that folder somewhere a web server can read it, and then leaving it alone.

This guide covers the practical steps: where the server lives, which web server to run, HTTPS, where the files go, who owns them, and how new files get there.

Pick where it lives

There are three realistic homes for a self-hosted static site.

A small VPS

A virtual private server gives you a Linux machine with root access and a public IP address. For a static blog, the smallest plan any mainstream provider sells is plenty. A static site serves thousands of visitors on hardware that would struggle to run a busy WordPress install.

Prices move, so check before you commit. At the time of writing, Hetzner’s cheapest cloud server, the CX23, is €5.49 a month excluding VAT in its German and Finnish locations, following a price rise in June 2026. Other providers sit in a similar range.

This is the option the rest of this guide assumes. It’s also the most instructive, because you control every layer.

Shared hosting

If you already pay for shared hosting with a cPanel account, you can put a static site there today. You won’t configure the web server yourself; you’ll upload into a folder the host already serves, usually public_html. Many shared hosts now provide free certificates automatically, but check your control panel rather than assuming. The trade-off is less control and occasionally slower support for things like SSH keys.

A box at home

A Raspberry Pi or an old laptop can serve a blog perfectly well. The difficulties are all networking: residential IP addresses change, some broadband providers put you behind carrier-grade NAT so inbound connections never reach you, and some forbid running servers in their terms. If you want to try it anyway, a tunnelling service such as Cloudflare Tunnel runs a small daemon that makes outbound-only connections, so you don’t need to open ports on your router. Your electricity, your uptime, your problem.

Point your domain at it

Before HTTPS will work, your domain has to resolve to the server. In your DNS provider’s panel, create:

DNS changes can take a while to propagate. Check with dig +short example.com from your own machine before moving on.

Choose a web server

For a static site you want something small, fast and boring. Two sensible choices:

Caddy

Caddy’s selling point for this job is automatic HTTPS. If your domain resolves to the server and ports 80 and 443 are reachable, Caddy obtains certificates from Let’s Encrypt (falling back to ZeroSSL) and renews them in the background. There’s nothing else to install.

Install it from Caddy’s official package repository (the instructions on caddyserver.com cover Debian, Ubuntu and others), then put this in /etc/caddy/Caddyfile:

example.com {
	root * /var/www/example.com
	encode zstd gzip
	file_server
}

www.example.com {
	redir https://example.com{uri} permanent
}

Reload it:

sudo systemctl reload caddy

That is a complete, production-ready configuration for a static blog, HTTPS included.

nginx

nginx is everywhere and extremely well documented. It needs a separate tool for certificates, but that tool does most of the work. On Debian or Ubuntu, install it with sudo apt install nginx, then create /etc/nginx/sites-available/example.com:

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    root /var/www/example.com;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }

    error_page 404 /404.html;
}

Enable it, test the configuration and reload:

sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Add HTTPS

With Caddy, you already have it. With nginx, use Certbot, the Let’s Encrypt client from the EFF. The snap package is the method Certbot recommends:

sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/local/bin/certbot
sudo certbot --nginx -d example.com -d www.example.com

Certbot edits your nginx configuration to add the certificate and an HTTP-to-HTTPS redirect. It also installs a timer that renews certificates before they expire. Confirm renewal works with:

sudo certbot renew --dry-run

Open the firewall, and only that

If you use ufw, allow SSH first so you don’t lock yourself out, then the web ports:

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

Port 80 stays open even though you serve everything over HTTPS. Certificate challenges and the redirect both use it.

Where the files go, and who owns them

Convention on Linux is /var/www/<domain>. The directory that holds your site should be:

Create a dedicated upload user rather than publishing as root:

sudo adduser --disabled-password --gecos "" deploy
sudo mkdir -p /var/www/example.com
sudo chown deploy:deploy /var/www/example.com
sudo chmod 755 /var/www/example.com

The permissions you want are 755 for directories and 644 for files: the owner can write, everyone else can read. The web server doesn’t need to write anything, and it shouldn’t be able to. If uploaded files arrive as 600, visitors will see 403 Forbidden. Fix them in one go:

find /var/www/example.com -type d -exec chmod 755 {} +
find /var/www/example.com -type f -exec chmod 644 {} +

Add your public SSH key to /home/deploy/.ssh/authorized_keys so the deploy account can log in without a password. Once key login works, consider setting PasswordAuthentication no in /etc/ssh/sshd_config.

Getting files onto the server

Your static site generator, whatever it is, produces a folder: public/, _site/, dist/. Publishing means making the server’s /var/www/example.com match that folder.

The standard ways to do that are SFTP and rsync, both of which run over SSH, so the key you set up above is all the authentication you need. For a one-off copy:

rsync -avz --delete public/ deploy@example.com:/var/www/example.com/

The trailing slash on public/ matters: it copies the folder’s contents rather than the folder itself. --delete removes files on the server that no longer exist locally, which is what you want for a mirror, and exactly what you don’t want if you point it at the wrong directory. Add --dry-run the first time.

SFTP has more to it than fits here: host keys, fingerprints, paths on shared hosting and NAS boxes, and the errors you’re likely to meet. That’s all in how to publish a website over SFTP.

Keeping it running

A static server needs very little maintenance, but not none.

That’s it. No plugin updates, no database dumps, no admin panel to secure. This is the quiet reward of static hosting.

Where Selfish fits

Selfish is a native app for iPhone, iPad and Mac that writes your blog and generates the static site for you, then publishes it over SFTP to a server like the one above, with the host key pinned on first connection. It’s on the App Store as a one-time purchase, at a £3.99 introductory price, rising to £9.99 in December. The publishing docs show what it needs from your server: the same host, user, key and path you’ve just set up.