Hugo is a static site generator written in Go, famous for building thousands of pages in seconds. You write content in Markdown, pick a theme, and Hugo produces plain HTML, CSS and JS files. Serving those from Nginx on a VPS is about as fast, cheap and secure as web hosting gets: there’s no database and no PHP, and very little to attack.
For the trade-offs between static and dynamic sites, see static vs dynamic websites. Prefer JavaScript tooling? See Astro.
1. Install Hugo
Install the extended edition, which is needed by many themes for Sass. On your computer or build machine:
# macOS
brew install hugo
# Debian/Ubuntu: distro packages can be old – use the .deb from GitHub releases
wget https://github.com/gohugoio/hugo/releases/download/v0.140.2/hugo_extended_0.140.2_linux-amd64.deb
sudo apt install ./hugo_extended_*_linux-amd64.deb
hugo version
(Use the current release.) Many themes also need Git, and some need Dart Sass or Node for asset pipelines.
2. Create a site with a theme
hugo new site mysite
cd mysite
git init
git submodule add https://github.com/adityatelange/hugo-PaperMod themes/PaperMod
echo "theme = 'PaperMod'" >> hugo.toml
Themes can also be added as Hugo Modules (Go modules). Check your theme’s docs.
Basic hugo.toml:
baseURL = 'https://example.com/'
languageCode = 'en-us'
title = 'My Site'
theme = 'PaperMod'
[params]
description = 'Notes on servers and self-hosting'
3. Write content
hugo new content posts/first-post.md
---
title: "First Post"
date: 2026-10-14
draft: false
tags: ["servers"]
---
Hello from **Hugo**.
Preview locally with live reload:
hugo server -D # -D includes drafts
# open http://localhost:1313
4. Build for production
hugo --gc --minify
The finished site is in public/. Upload that folder to the server.
5. Serve it with Nginx
On the VPS:
sudo mkdir -p /var/www/example.com
sudo chown -R deploy:deploy /var/www/example.com
server {
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;
location ~* \.(css|js|png|jpg|jpeg|gif|svg|webp|avif|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
Hugo fingerprints asset filenames (when the theme uses resources.Fingerprint), so long cache lifetimes are safe. Then add HTTPS:
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d example.com -d www.example.com
See Nginx server blocks, Let’s Encrypt and Nginx static file caching. Enable Brotli or gzip too. See Nginx compression.
6. Deploy
From your computer with rsync:
hugo --gc --minify && rsync -avz --delete public/ [email protected]:/var/www/example.com/
--delete removes files on the server that no longer exist in the build. See rsync.
Automatically with GitHub Actions on every push:
name: Deploy
on:
push:
branches: [main]
jobs:
build-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { submodules: true, fetch-depth: 0 }
- uses: peaceiris/actions-hugo@v3
with: { hugo-version: 'latest', extended: true }
- run: hugo --gc --minify
- name: Deploy with rsync
env:
SSH_KEY: ${{ secrets.DEPLOY_KEY }}
run: |
mkdir -p ~/.ssh && echo "$SSH_KEY" > ~/.ssh/id_ed25519 && chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -H example.com >> ~/.ssh/known_hosts
rsync -az --delete public/ [email protected]:/var/www/example.com/
See GitHub Actions deploy to a VPS.
Tips
- Images: use Hugo’s image processing (resize, WebP) in templates for fast pages.
- Search: client-side search (Pagefind, Fuse.js) works without a server.
- Forms and comments need external services or small self-hosted backends, since the site itself is static.
- One small VPS can host many Hugo sites. Each is just a folder plus a server block.
Quick reference
| Task | Command |
|---|---|
| New site | hugo new site mysite |
| New post | hugo new content posts/name.md |
| Preview | hugo server -D |
| Build | hugo --gc --minify |
| Deploy | rsync -avz --delete public/ user@host:/var/www/site/ |
Related: CDN for your website and website speed optimization.