Setting Up a Hugo Blog on Netlify (The Easy Way)
Table of Contents
In my last post I walked through hosting a Hugo blog on AWS S3 with CloudFront. It works, but let’s be honest — it’s a lot of moving parts. S3 bucket, CloudFront distribution, IAM policies, DNS records, cache invalidation. If your blog is the only thing on the line, that’s a fair bit of overhead.
A few people asked whether there’s a simpler way. There is.
This post covers hosting the same Hugo blog on Netlify — the zero-config, free HTTPS, push-to-deploy alternative.
Markdown → Hugo Build → Netlify Deploy → blog.example.com
One service. No bucket policies. No CloudFront invalidation. Just push and it works.
Why Netlify?
| Feature | AWS S3 + CloudFront | Netlify |
|---|---|---|
| Setup complexity | Medium | Zero |
| Custom domain | Manual DNS + ACM | Automatic SSL |
| Deploy method | CLI sync | Git push or drag-and-drop |
| Cache invalidation | Manual | Instant (new deploy) |
| Preview URLs | No | Yes (per-PR) |
| Free tier cost | ~$1-2/month | Free |
| CI/CD pipeline | Build yourself | Built-in |
If you just want your blog live and forgotten, Netlify is the way.
Prerequisites
- A Netlify account (free)
- Your Hugo site built (
hugo --minify) - Your domain (e.g.,
blog.example.com) with DNS managed anywhere (Ionos, Cloudflare, wherever)
That’s it. No AWS account needed.
Step 1: Build Your Hugo Site
Same as before:
cd /workspace/hugo-blog
hugo --minify
This generates your public/ directory. That’s the folder Netlify deploys.
Step 2: Deploy via Netlify CLI (Recommended for Local Workflows)
Install the CLI
npm install -g netlify-cli
Login
netlify login
This opens a browser for authentication. Once done, you’re authenticated locally.
Create the Site
cd /workspace/hugo-blog
netlify init
You’ll be prompted through a few questions:
- How do you want to set up this site? → “Set up & connect to a new site”
- What’s your team/org name? → (your account name)
- What’s your site name? → pick something like
my-hugo-blog - Base directory? →
public(this is crucial — tell Netlify to serve frompublic/) - Build command? →
hugo --minify(or just leave blank if deploying pre-built files) - Publish directory? →
public(confirm)
Netlify creates a netlify.toml file in your project root. It should look like:
[build]
publish = "public"
command = "hugo --minify"
The publish setting tells Netlify which folder contains your built site. The command tells it how to build — if you push source files instead of pre-built ones.
Deploy
netlify deploy --prod
This pushes your public/ folder to Netlify. You’ll get a live URL like https://random-name-12345.netlify.app.
Your blog is now live. Point it to your browser and check it out.
Step 2 (Alt): Deploy by Drag & Drop (No CLI)
If you don’t want to install anything:
- Build your site:
hugo --minify - Go to app.netlify.com
- Click Sites → Add new site → Import an existing project → Deploy manually
- Drag and drop your entire
public/folder onto the upload area
Done. Netlify gives you a *.netlify.app URL instantly.
Note: The drag-and-drop method doesn’t give you CI/CD — every update means manually uploading again. The CLI method is better if you’ll be updating regularly.
Step 3: Connect a Git Repository (Automatic Deployments)
This is where Netlify really shines. Connect your Git repo and every push auto-deploys.
- Go to your site dashboard on Netlify
- Click Set up Continuous Deployment
- Choose your Git provider (GitHub, GitLab, Bitbucket)
- Authorize and pick your repository
- Configure the build settings:
- Build command:
hugo --minify - Publish directory:
public
- Build command:
- Click Save
Now every time you push to your default branch, Netlify:
- Pulls your code
- Runs
hugo --minify - Deploys the result
You never have to think about deployment again. Write a post, push, it goes live in ~30 seconds.
Pro tip: If you’re already using Git (which you should be), this is a one-time setup that pays for itself immediately.
Step 3 (Alt): Preview Deployments
For every pull request, Netlify creates a unique preview URL. This is great if you want to:
- Review posts before they go live
- Share drafts with others
- Test changes in production
Enable it in Site settings → Build & deploy → Post-processing → Continue as before (PR builds are on by default for connected repos).
When you open a PR, you get a comment with a preview URL like https://deploy-preview-42--my-hugo-blog.netlify.app.
Step 4: Set Up Your Custom Domain
On Netlify
- Go to Domain settings in your site dashboard
- Click Add custom domain
- Enter
blog.example.com - Click Verify
Netlify will automatically provision a free SSL certificate. This happens in seconds — no ACM, no certificate manager, no manual provisioning.
On Your DNS Provider (Ionos)
Log in to your Ionos Control Center and navigate to DNS → Records.
Add a CNAME Record
| Type | Host | Value | TTL |
|---|---|---|---|
| CNAME | blog | random-name-12345.netlify.app (from Netlify) | 600 |
Or if you want to use the root domain (e.g., example.com), use a CNAME Flattening / ANAME record — Netlify provides an IP to point to.
Alternative: Use Netlify’s Nameservers
Netlify can also be your DNS provider:
- In Domain settings, click Add custom domain
- Enter your domain
- Click Use Netlify DNS
- Update your domain registrar’s nameservers to the ones Netlify provides
This gives you DNS management inside Netlify’s dashboard. Some people prefer this because everything is in one place.
Step 5: Configure Hugo for Netlify
There’s one important setting. Open hugo.toml and make sure your baseURL uses a relative path (no hardcoded domain):
baseURL = '/'
This is critical. If you hardcode https://blog.example.com, your local development and Netlify’s preview URLs will break. Keeping it as '/' means all links resolve relative to the current host — it works everywhere.
Step 6: Verify
Visit https://blog.example.com — your Hugo blog should load with full HTTPS, no configuration required on your part.
Netlify handles:
- Automatic HTTP → HTTPS redirect
- OCSP stapling
- HSTS headers
- Modern TLS ciphers
All free. All automatic.
Keeping It Updated
With Git integration set up, updating is trivial:
cd /workspace/hugo-blog
# Write your post in content/en/posts/
git add content/en/posts/my-new-post/index.md
git commit -m "Add my new post"
git push origin main
That’s it. Netlify detects the push, rebuilds, and deploys. You get:
- A deployment log (check the Netlify dashboard)
- A new URL if you want to share:
https://<deploy-hash>--my-hugo-blog.netlify.app - Automatic URL invalidation (no manual cache clearing)
No hugo --minify on your machine. No aws s3 sync. No aws cloudfront create-invalidation.
Just git push.
The netlify.toml Configuration Reference
For more control, you can customize your netlify.toml. Here’s a production-ready version:
[build]
publish = "public"
command = "hugo --minify"
[build.environment]
HUGO_VERSION = "0.162.0"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
force = true
What each part does:
| Section | Purpose |
|---|---|
publish | Which folder Netlify serves |
command | How to build (optional if you deploy pre-built) |
HUGO_VERSION | Pin Hugo version for reproducible builds |
[[redirects]] | SPA-style routing — redirects all 404s to index.html |
Note on redirects: The catch-all redirect to
index.htmlis needed if you want deep linking to work correctly on Netlify. Without it, refreshing a post URL directly might return a 404. This is a known Hugo + Netlify gotcha.
Bonus: Environment Variables
If you need environment-specific configuration (like analytics IDs, API keys, or different baseURL for staging):
- Go to Site settings → Environment variables
- Add variables like
HUGO_BASEURLorGOOGLE_ANALYTICS - Reference them in your Hugo templates:
{{ with getenv "GOOGLE_ANALYTICS" }} <script async src="https://www.googletagmanager.com/gtag/js?id={{ . }}"></script> {{ end }}
Or set them in netlify.toml for consistent versions across environments:
[build.environment]
HUGO_BASEURL = "https://blog.example.com"
Netlify vs S3: Honest Comparison
Here’s where I stand after using both:
Choose Netlify when:
- You want simplicity. One service, zero config.
- You value speed over control. Push to deploy in seconds.
- You need preview URLs. Every PR gets a live preview.
- You’re solo. No team, no compliance requirements, no enterprise needs.
- Cost matters. Free tier covers ~100GB bandwidth/month — plenty for a blog.
Choose S3 + CloudFront when:
- You already have AWS infrastructure. If you’re paying for AWS anyway, S3 is marginal cost.
- You need fine-grained access control. S3 supports private buckets with CloudFront signed URLs.
- You’re comfortable with AWS. If you already manage S3/CloudFront for other things, adding a blog is trivial.
- You want to learn cloud infrastructure. There’s educational value in understanding S3 policies and CloudFront distributions.
The numbers
| Netlify | S3 + CloudFront | |
|---|---|---|
| Monthly cost | $0 | ~$1-2 |
| Time to first deploy | 5 minutes | 30+ minutes |
| Time to update | Git push (automated) | CLI command + invalidation |
| SSL | Automatic | Manual (ACM or CloudFront default) |
| CDN | Built-in | CloudFront |
| Support | Community | AWS support (paid) |
The $1-2/month on S3 is real, but compared to the time saved on Netlify, it’s not even a rounding error unless you’re hosting something massive.
Troubleshooting
404 on Post Pages
- Make sure
publishinnetlify.tomlpoints topublic - Add the catch-all redirect (see netlify.toml reference above)
- Rebuild and push:
hugo --minify && netlify deploy --prod
Build Fails on Netlify
- Check the Deploys tab — it shows the build log with error details
- Make sure the Hugo version matches: add
HUGO_VERSIONto environment variables - Run
hugo --minifylocally first to verify it works
Custom Domain Not Working
- Wait for DNS propagation (5-30 minutes usually)
- Check your DNS provider for the correct CNAME value from Netlify
- Verify SSL is provisioning: Domain settings → check for “Certificate provisioning” status
Blank Page / CSS Not Loading
- Your
baseURLinhugo.tomlshould be'/', not a hardcoded domain - Clear your browser cache — Netlify deploys instantly, but browsers cache aggressively
Wrapping Up
After running the S3 setup, the Netlify path feels almost too simple. But that’s the point — hosting a blog shouldn’t be a side project.
The Netlify pipeline is:
Write markdown → git push → blog updates
The S3 pipeline is:
Write markdown → hugo build → aws s3 sync → (optional) aws cloudfront invalidate → git push
Both work. Both put your content online. But one lets you focus on writing and the other on infrastructure.
For a personal blog? I’d pick Netlify every time.
Previous post: Setting Up a Hugo Blog on AWS S3 with Custom Domain