Host Your Own Blog for Free with Hugo and Cloudflare Pages
This blog runs on Hugo and Cloudflare Pages. Total cost: $0/month. No servers to maintain, no databases to back up, no security patches to apply. Just markdown files that become a fast, globally-distributed website.
Here’s exactly how to set it up.
Why This Stack?
- Hugo: Blazing fast static site generator written in Go. Builds this entire blog in under 100ms
- Cloudflare Pages: Free hosting with global CDN, automatic HTTPS, and unlimited bandwidth
- Markdown: Write posts in your favorite editor, version control with git
- No JavaScript required: The site works without JS, loads instantly
Step 1: Install Hugo
# macOS
brew install hugo
# Linux
sudo apt install hugo
# Windows
choco install hugo-extended
# Verify installation
hugo version
You need the extended version for SCSS support (most themes use it).
Step 2: Create Your Blog
# Create a new site
hugo new site my-blog
cd my-blog
# Initialize git
git init
Step 3: Add a Theme
I use hugo-coder, a clean developer-focused theme:
# Add theme as git submodule
git submodule add https://github.com/luizdepra/hugo-coder.git themes/hugo-coder
Other great themes:
Browse more at themes.gohugo.io.
Step 4: Configure Your Site
Create config.toml:
baseURL = "https://yourdomain.com/"
languageCode = "en-us"
title = "Your Name"
theme = "hugo-coder"
# Code highlighting
pygmentscodefences = true
pygmentsstyle = "dracula"
[pagination]
pagerSize = 20
[markup.goldmark.renderer]
unsafe = true # Allow raw HTML in markdown
[params]
author = "Your Name"
description = "My coding blog"
colorscheme = "dark" # or "light", "auto"
# Social links
[[params.social]]
name = "GitHub"
icon = "fa fa-2x fa-github"
url = "https://github.com/yourusername"
[[params.social]]
name = "Twitter"
icon = "fa fa-2x fa-twitter"
url = "https://twitter.com/yourusername"
[menu]
[[menu.main]]
name = "Posts"
url = "/post/"
weight = 10
Step 5: Write Your First Post
hugo new post/my-first-post.md
This creates content/post/my-first-post.md:
---
title: "My First Post"
date: "2025-12-14"
draft: true
---
Hello, world! This is my first blog post.
## Code Example
```python
def hello():
print("Hello from my blog!")
An Image

Put images in `static/images/` and reference them with `/images/filename.png`.
## Step 6: Preview Locally
```bash
# Start local server with drafts
hugo serve --buildDrafts
# Open http://localhost:1313
Hugo has live reload—edit a file and the browser updates instantly.
Step 7: Deploy to Cloudflare Pages
Option A: Connect GitHub (Easiest)
- Push your blog to GitHub
- Go to Cloudflare Pages
- Create a project → Connect to Git
- Select your repository
- Build settings:
- Build command:
hugo - Build output directory:
public - Environment variable:
HUGO_VERSION=0.140.0
- Build command:
Cloudflare rebuilds and deploys automatically on every push.
Option B: Deploy with Wrangler (More Control)
# Install wrangler
npm install -g wrangler
# Login to Cloudflare
wrangler login
# Build your site
hugo --destination=public
# Deploy
npx wrangler pages deploy public --project-name=my-blog
I use a Makefile to simplify this:
.PHONY: serve build deploy
serve:
hugo serve --buildDrafts
build:
hugo --destination=public-prod
deploy: build
npx wrangler pages deploy public-prod --project-name=my-blog --branch=main
Now make deploy builds and deploys in one command.
Step 8: Custom Domain (Optional)
- In Cloudflare Pages dashboard, go to your project
- Custom domains → Add custom domain
- Enter your domain (e.g.,
blog.yourdomain.com) - If domain is on Cloudflare DNS, it auto-configures
- Otherwise, add the CNAME record it provides
HTTPS is automatic and free.
My Workflow
# 1. Create new post
cat > content/post/$(date +%Y-%m-%d)-my-new-post.md << 'EOF'
---
title: "My New Post"
date: "$(date +%Y-%m-%d)"
---
Content here...
EOF
# 2. Preview locally
make serve
# Check http://localhost:1313
# 3. Deploy when ready
make deploy
# 4. Commit to git
git add .
git commit -m "Add post: My New Post"
git push
Tips
Syntax Highlighting
Hugo uses Chroma for syntax highlighting. Set your style in config.toml:
pygmentsstyle = "dracula" # or monokai, github, etc.
Preview styles at xyproto.github.io/splash/docs/.
Custom CSS
Create static/css/custom.css and reference it:
[params]
customCSS = ["css/custom.css"]
RSS Feed
Hugo generates /index.xml automatically. Link to it:
[[params.social]]
name = "RSS"
icon = "fa fa-2x fa-rss"
url = "/index.xml"
Remove Draft Status
When ready to publish, either:
- Remove
draft: truefrom front matter - Or delete the line entirely
Only non-draft posts appear in production builds.
Cost Breakdown
| Service | Cost |
|---|---|
| Hugo | Free (open source) |
| Cloudflare Pages | Free (unlimited sites, bandwidth) |
| Custom domain | ~$10/year (optional) |
| Total | $0-10/year |
Compare to:
- Medium: Free but they own your content
- Substack: Free but paywalled features
- Ghost: $9-25/month
- WordPress.com: $4-25/month
Why Not [X]?
Why not Medium/Substack? You don’t own your content. They can change terms, add paywalls, or shut down.
Why not WordPress? Overkill for a blog. Databases, PHP, security updates, hosting costs.
Why not GitHub Pages? Also great! Cloudflare Pages has faster global CDN and easier custom domains.
Why not Netlify? Also excellent. I use Cloudflare because my DNS is already there.
Conclusion
A Hugo + Cloudflare Pages blog gives you:
- Full ownership of your content
- Version-controlled posts in git
- Sub-second build times
- Global CDN distribution
- Zero monthly costs
- No maintenance burden
The entire setup takes about 30 minutes. Your posts live forever in markdown files you control.