Deploy Hugo static site to Hetzner
Recently I ditched my AWS account and moved my personal small projects to a Hetzner VPS, what a nice feeling tinkering with your own server again! As a part of this process I also had to change how I deploy this blog, so I thought I would share my setup in case anyone else is interested.
This blog uses Hugo as a static site generator, so my server only needs to serve static files. I chose to use Caddy as my web server because it is easy to set up and automatically manages HTTPS certificates via Let’s Encrypt.
Install Caddy
First, let’s set up our Hetzner VPS with all the necessary components.
Install it using the official installation script:
|
|
Keep Caddy running
There are numerous advantages to using a service manager to keep it running, such as ensuring it starts automatically when the system reboots and to capture stdout/stderr logs.
|
|
Prepare static file directory
We’ll come back to this part later when we set up deployment, but for now, create the directory where your Hugo site will be served from. I’ve found it most convenient to put the static site directory inside the directory /var/www
. In my case it’s /var/www/blog
, it seems to be a common location, so there’s a decent chance it will be familiar to other developers. Make sure Caddy has permission to read from this directory.
|
|
Configure Caddy
It’s time to write a Caddyfile, which will tell Caddy how to serve our static site.
|
|
The configuration is quite simple in my case:
|
|
Use systemctl reload caddy
to tell Caddy to use the new version. Your static site should appear at http://your-server-ip once you deploy it.
HTTPS
Caddy will automatically manage HTTPS certificates for you, but you need to have a domain name pointing to your server’s IP address. Once you have that set up, modify your Caddyfile to use your domain name:
|
|
Tell Caddy to use the new version of the file by running systemctl reload caddy
.
Wait a few moments and check the runtime logs using systemctl status caddy
. Hopefully you’ll see messages about Caddy sucessfully getting a TLS certificate.
Now you should be able to access your site securely at https://your-domain.com
Manual Deployment
You can deploy manually using rsync from your local machine:
|
|
The --delete
flag removes files on the server that no longer exist in your local build, keeping your deployment clean.
GitHub Actions Automation
Obviously, doing this manually is tedious and error-prone. For automated deployments, I use GitHub Actions with SSH and rsync. Here’s my workflow configuration:
|
|
Set up these secrets in your GitHub repository:
SSH_PRIVATE_KEY
: Your private SSH key for server accessSSH_HOST
: Your server’s IP address or domainSSH_USER
: Your server username
SSH Key Setup
Generate an SSH key pair for deployment:
|
|
Then copy the public key to your server:
|
|
Benefits of This Setup
- Automatic HTTPS: Caddy handles SSL certificates automatically
- Fast deployments: rsync only transfers changed files
- Version control: All changes are tracked in git
- Cost effective: Much cheaper than managed hosting services
- Full control: Complete control over your hosting environment
Troubleshooting
Check Caddy Status
|
|
Verify File Permissions
|
|
Feedback
As always, please reach out to me on X with questions, corrections, or ideas!