I wanted a blog on my VPS that would cost almost nothing in resources. Static site generators were the obvious choice, and after looking at a few options I went with Hugo.
Why Hugo
The main appeal is simplicity. Hugo is a single binary. There is no runtime, no dependency tree, no Node modules folder growing to 800 MB. You write Markdown, run one command, and get a folder of HTML files ready to serve.
Build times are measured in milliseconds. For a small site like this one, the entire build takes under 100 ms. That matters less for a blog with ten posts, but it means the tooling never gets in the way.
Choosing a theme
I picked PaperMod. It is minimal, fast, and does not pull in external resources. No Google Fonts requests, no analytics scripts, no CDN dependencies. The HTML it produces is clean and light.
Installing it as a Git submodule keeps things manageable:
git submodule add --depth=1 https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod
The theme configuration lives in hugo.yaml. The defaults are reasonable, and you only need to override what you actually want to change.
Project structure
The directory layout is straightforward:
site/
├── content/
│ └── posts/
├── static/
├── themes/
│ └── PaperMod/
└── hugo.yaml
Posts go in content/posts/ as Markdown files with YAML front matter. Static assets go in static/. That is the whole structure.
Writing a post
Each post is a Markdown file with a small header:
---
title: "Your post title"
date: 2025-09-07
tags: ["example"]
draft: false
---
The rest is standard Markdown. Hugo supports code blocks with syntax highlighting out of the box, which is useful for a technical blog. You can also add a description field for SEO and a tags list to organize posts by topic.
Local development
Hugo has a built-in development server with live reload:
hugo server -D
The -D flag includes draft posts. The server watches for file changes and rebuilds automatically. It runs at http://localhost:1313/ by default. Every time you save a Markdown file, the browser refreshes within milliseconds.
This tight feedback loop is one of the things that makes Hugo pleasant to use. You write, save, and immediately see the result.
Building and deploying
Building the site produces a public/ directory with plain HTML, CSS, and a small amount of JavaScript from the theme:
hugo build
For deployment, I copy the public/ folder to the web root on the server. A simple rsync does the job:
rsync -avz --delete public/ user@yourserver:/var/www/packetlog.org/
You could automate this with a Git hook or a small shell script, but for a site that updates a few times a month, running it manually is fine.
Serving with Nginx
On the VPS side, Nginx serves the static files. The configuration is minimal:
server {
listen 80;
server_name packetlog.org;
root /var/www/packetlog.org;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
Static files are cheap to serve. Nginx handles this with barely any memory or CPU usage. On a small VPS with 1 GB of RAM, the overhead is negligible.
What I skipped
I deliberately left out a few things:
- Comments. I do not need them. If someone wants to respond, they can write their own post or send an email.
- Analytics. I do not want to track visitors or load third-party scripts.
- Custom fonts. The system font stack works fine and loads instantly.
The result is a site that loads fast, costs nothing extra to run, and is easy to maintain. Hugo’s documentation covers everything else you might need.
Thoughts so far
This setup took about an hour from start to finish. Most of that time was spent reading the PaperMod documentation and tweaking the hugo.yaml configuration. The actual deployment was a few minutes.
For a personal blog on a VPS, this is exactly the level of complexity I wanted. No database, no server-side rendering, no build pipelines. Just Markdown files and a static site generator.