📋 Background
My blog was previously hosted on a Raspberry Pi 5 with Cloudflare Tunnel for HTTPS. While this worked, it had several issues:
- GFW interference: Cloudflare Tunnel kept timing out due to GFW blocking edge nodes
- 530 errors: Website would randomly return 530 errors when tunnel disconnected
- Manual deployment: Had to manually upload files via script
- Resource usage: Tunnel consumed Pi5 resources unnecessarily
✅ Solution: Cloudflare Pages
Cloudflare Pages hosts static sites directly on Cloudflare's CDN, eliminating the need for a tunnel. Combined with Git auto-deploy, it provides:
- ✅ No tunnel needed - Direct CDN hosting
- ✅ Auto-deploy on git push - No manual uploads
- ✅ Better performance - Global CDN edge caching
- ✅ Free HTTPS - Automatic SSL certificates
- ✅ Bypasses GFW - Cloudflare CDN is more reliable than Tunnel
🔧 Migration Steps
1. Prepare Git Repository
First, I needed to push my website to GitHub. But there was a problem:
Initial attempt (failed):
# SSH connection kept timing out
ssh -T git@github.com
# Connection timed out
Solution: Use HTTPS instead of SSH
# Configure Git to use HTTPS
git config --global url."https://github.com/".insteadOf git@github.com:
# Generate GitHub Personal Access Token
# Go to: https://github.com/settings/tokens
# Create fine-grained token with: Repository permissions → Contents → Read and write
# Cache credentials (one-time setup)
git config --global credential.helper store
echo "https://USERNAME:TOKEN@github.com" > ~/.git-credentials
chmod 600 ~/.git-credentials
repo scope for this specific repository.
Test connection:
cd /home/henry/website
git remote add origin https://github.com/henryjin8s/henryjin8s-blog.git
git push -u origin main
2. Create Cloudflare Pages Project
Steps:
- Go to Cloudflare Dashboard
- Workers & Pages → Create Application
- Select Pages (diamond icon ◇)
- Connect to Git → Select
henryjin8s/henryjin8s-blog - Configure:
- Production branch:
main - Build command: (leave empty - static site)
- Deploy command:
true(required field) - Root directory:
/
- Production branch:
- Save and Deploy
3. Configure Custom Domain
After the initial deployment:
- Go to Pages project → Custom domains
- Add
henryjin8s.xyz - Cloudflare will show DNS configuration:
Type: CNAME
Name: @
Target: henryjin8s-blog.pages.dev
Proxy: Enabled (orange cloud) ☁️
⚠️ DNS Propagation: Changes may take 5-30 minutes to propagate.
4. Test Git Auto-Deploy
Now the fun part - test if pushing to Git triggers automatic deployment:
# Make a change
cd /home/henry/website
git add .
git commit -m "Update homepage with new posts"
git push
# Watch Cloudflare Dashboard → Deployments
# Should see: "Production deployment" → "Success" in ~1-2 minutes
- Detects the new commit via webhook
- Pulls the latest code from GitHub
- Deploys to global CDN
- Invalidates cache
🔍 Troubleshooting
Problem 1: Confused by UI Changes (2026)
Symptom: Not sure if creating Pages or Worker - both show lightning icon (⚡).
Solution: Cloudflare merged Pages into Workers platform in 2025. Look for:
- For static sites: Select "Pages" during project creation (even with lightning icon)
- For serverless functions: Select "Worker"
- Hint: If you see "Connect to Git" option, you're in the right place for Pages
Problem 2: Git Push Permission Denied
Symptom: fatal: unable to access: The requested URL returned error: 403
Solution: GitHub token doesn't have write permissions. Update token scopes:
# Fine-grained token needs:
# Repository permissions → Contents → Read and write
Problem 3: Old Content Still Showing
Symptom: Website shows old content after deployment.
Solution: Cloudflare cache might be stale. Purge cache:
# Cloudflare Dashboard → Caching → Configuration → Purge Everything
# Or wait 5-10 minutes for automatic refresh
📊 Before vs After
| Aspect | Before (Tunnel) | After (Pages) |
|---|---|---|
| Deployment | Manual script | Git push (auto) |
| Uptime | ~90% (tunnel drops) | ~99.9% (CDN) |
| Performance | Single server (Pi5) | Global CDN |
| Resource Usage | Pi5 CPU + bandwidth | Zero (offloaded) |
| GFW Resilience | ❌ Frequent blocks | ✅ Much better |
🔐 Security Considerations
GitHub Token Security
- Use fine-grained tokens instead of classic tokens
- Set expiration (I used 30 days)
- Limit scope to only required repositories
- Never commit tokens to Git (use
~/.git-credentials) - Rotate regularly and delete old tokens
What I Did
# Token stored locally (not in Git)
~/.git-credentials (chmod 600)
# Token configuration:
- Type: Fine-grained
- Expiration: 30 days
- Repository: henryjin8s-blog only
- Permissions: Contents (Read and write)
🔄 Current Workflow
Now my blog update workflow is simple:
# 1. Edit content locally
cd /home/henry/website
vim posts/new-post.html
# 2. Commit and push
git add .
git commit -m "Add new blog post"
git push
# 3. Wait ~1-2 minutes
# Cloudflare automatically deploys
# 4. Visit https://henryjin8s.xyz
# New content is live!
💡 Lessons Learned
- UI Update (2026): Cloudflare merged Pages into Workers platform. Both use lightning icon (⚡) now. Focus on selecting "Pages" workflow during creation, not the icon.
- HTTPS > SSH in China: SSH connections to GitHub are frequently blocked. HTTPS with token works reliably.
- Fine-grained tokens: More secure than classic tokens. Can limit to specific repos and set expiration.
- Cloudflare CDN > Tunnel: For static sites in China, direct CDN hosting is more reliable than Tunnel.
- Test before migrating domain: Test on
.pages.devsubdomain first, then switch custom domain.
✅ Conclusion
The migration from Cloudflare Tunnel to Cloudflare Pages was a huge improvement. The site is now more reliable, faster, and requires zero maintenance. Git auto-deploy means I can focus on writing content instead of managing infrastructure.
Total migration time: ~2 hours (including debugging the Worker vs Pages confusion)
Cost: Free (Cloudflare Pages free tier)
Next steps: Maybe add a CMS for non-technical content updates? 🤔