I write in Markdown. I think in Markdown. Having to go through a web CMS to publish felt like an extra step that was getting in the way of actually writing.
Hexo fits the way I naturally work: write a .md file in my editor, run one command, and the site updates. The generated output is plain static HTML that Nginx serves at full speed — no PHP, no database, no application server to manage. My Hexo blog consistently loads in under a second even from regions far from the server.
The part of this setup I'm most pleased with is the automated deployment pipeline: write on my laptop, push to GitHub, and the server pulls and rebuilds automatically. I only touch the server once to set it up.
I run this on Tencent Cloud Lighthouse. Hexo generates static files — the entry-level plan handles this perfectly, since serving static HTML with Nginx uses almost no server resources. The fixed monthly bandwidth allowance means traffic spikes from a viral post won't generate surprise bills. For readers in different regions, adding Tencent Cloud EdgeOne as a CDN layer delivers blog content from nodes close to each reader, significantly improving load times globally.
- Key Takeaways
| Feature | Hexo | WordPress | Ghost |
|---|---|---|---|
| Database required | No (static) | Yes | Yes |
| Server resources | Minimal (Nginx + static files) | High | Medium |
| Writing experience | Markdown in any editor | Browser/Gutenberg | Browser |
| Build time | Very fast (seconds) | N/A | N/A |
| Themes | 300+ open-source themes | Thousands | Hundreds |
| Comments | Via Disqus/utterances | Built-in | Built-in |
| Best for | Developers, technical blogs | General websites | Newsletters |
| Requirement | Notes |
|---|---|
| Cloud server | Tencent Cloud Lighthouse Ubuntu 22.04 |
| Local machine | Node.js 16+ installed |
| A domain name | For HTTPS |
ssh ubuntu@YOUR_SERVER_IP
sudo apt update && sudo apt upgrade -y
sudo apt install -y git nginx nodejs npm
sudo ufw allow ssh
sudo ufw allow 'Nginx Full'
sudo ufw enable
# Create web root
sudo mkdir -p /var/www/hexo
sudo chown ubuntu:www-data /var/www/hexo
sudo chmod 755 /var/www/hexo
The deployment workflow: your local Hexo project pushes generated HTML to a "bare" Git repository on the server. A Git hook copies the files to the Nginx web root automatically.
sudo adduser git
sudo mkdir -p /home/git/.ssh
sudo chmod 700 /home/git/.ssh
Copy your SSH public key to the git user:
# Paste your public key content
sudo nano /home/git/.ssh/authorized_keys
sudo chmod 600 /home/git/.ssh/authorized_keys
sudo chown -R git:git /home/git/.ssh
sudo -u git bash
mkdir ~/hexo-blog.git
git init --bare ~/hexo-blog.git
nano ~/hexo-blog.git/hooks/post-receive
#!/bin/bash
GIT_REPO=/home/git/hexo-blog.git
TMP_DIR=/tmp/hexo-deploy
DEPLOY_DIR=/var/www/hexo
rm -rf $TMP_DIR
git clone $GIT_REPO $TMP_DIR
rsync -av --delete $TMP_DIR/ $DEPLOY_DIR/
rm -rf $TMP_DIR
echo "Hexo blog deployed to $DEPLOY_DIR"
chmod +x ~/hexo-blog.git/hooks/post-receive
exit # Back to ubuntu user
sudo nano /etc/nginx/sites-available/hexo
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
root /var/www/hexo;
index index.html index.htm;
access_log /var/log/nginx/hexo_access.log;
error_log /var/log/nginx/hexo_error.log;
location / {
try_files $uri $uri/ =404;
}
# Custom 404
error_page 404 /404.html;
# Cache static assets
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|svg)$ {
expires 30d;
add_header Cache-Control "public, no-transform";
}
# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml;
gzip_vary on;
}
sudo ln -s /etc/nginx/sites-available/hexo /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
On your local machine:
# Install Hexo CLI globally
npm install -g hexo-cli
# Create a new blog
hexo init my-blog
cd my-blog
npm install
# Install the Git deployment plugin
npm install hexo-deployer-git --save
Edit _config.yml:
# Site info
title: My Developer Blog
subtitle: "Notes on code and infrastructure"
description: "Personal blog about software development"
author: Your Name
language: en
timezone: America/New_York
# URL
url: https://yourdomain.com
# Deploy configuration
deploy:
type: git
repo: git@YOUR_SERVER_IP:/home/git/hexo-blog.git
branch: master
message: "Deploy: {{ now('YYYY-MM-DD HH:mm:ss') }}"
Test the local server:
hexo server
# Visit http://localhost:4000
hexo new post "My First Blog Post"
# Creates: source/_posts/my-first-blog-post.md
Edit the file:
---
title: My First Blog Post
date: 2026-04-20 10:00:00
tags:
- tech
- tutorial
categories:
- Cloud
cover: /images/cover.jpg
---
This is my first post on my self-hosted Hexo blog, running on Tencent Cloud Lighthouse.
<!-- more -->
The rest of the post appears after the "Read more" fold.
## Section Heading
Content continues here...
# Generate static files
hexo clean && hexo generate
# Deploy to server
hexo deploy
# Or both in one command:
hexo clean && hexo g -d
Visit http://yourdomain.com — your post should appear.
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
Update the URL in your local _config.yml:
url: https://yourdomain.com
Redeploy: hexo clean && hexo g -d
For automatic deployment when you push to GitHub (building on the server avoids Hexo dependency on your local machine):
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
npm install -g hexo-cli
sudo -u git nano ~/hexo-blog.git/hooks/post-receive
#!/bin/bash
GIT_REPO=/home/git/hexo-blog.git
TMP_DIR=/tmp/hexo-source
DEPLOY_DIR=/var/www/hexo
rm -rf $TMP_DIR
git clone $GIT_REPO $TMP_DIR
cd $TMP_DIR
# Build Hexo on the server
npm install 2>/dev/null
hexo generate
# Deploy to web root
rsync -av --delete $TMP_DIR/public/ $DEPLOY_DIR/
rm -rf $TMP_DIR
echo "Hexo blog built and deployed"
Push your source (not the generated public/ folder) to GitHub, then trigger a server build:
name: Deploy Hexo Blog
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Trigger server build
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.SERVER_IP }}
username: ubuntu
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /tmp
git clone git@localhost:/home/git/hexo-blog.git hexo-temp
cd hexo-temp && npm install && hexo generate
rsync -av --delete public/ /var/www/hexo/
rm -rf /tmp/hexo-temp
# Example: Fluid theme (clean, modern)
npm install --save hexo-theme-fluid
# In _config.yml:
theme: fluid
cp node_modules/hexo-theme-fluid/_config.yml _config.fluid.yml
Edit _config.fluid.yml to customize:
npm install hexo-generator-searchdb --save
# In _config.yml:
search:
path: search.xml
field: post
If your blog URL has a subfolder (e.g., yourdomain.com/blog/), all generated asset paths must include that prefix. Update in _config.yml:
url: https://yourdomain.com/blog
root: /blog/
For a root deployment (yourdomain.com), leave root: / (the default).
Missing this causes broken CSS, JavaScript, and image links on the deployed site. Always match url and root to your actual deployment path.
# Local development
hexo server # Start local dev server at localhost:4000
hexo server -p 4001 # Custom port
# Build
hexo clean # Remove generated files
hexo generate # Generate static files (hexo g)
hexo g -d # Generate and deploy
# Posts
hexo new post "Title" # Create new post
hexo new page "about" # Create new page
hexo new draft "Title" # Create draft (not published)
hexo publish "Title" # Move draft to posts
# Deploy
hexo deploy # Deploy to configured destination (hexo d)
| Issue | Likely Cause | Fix |
|---|---|---|
| Connection refused | Service not running or wrong port | Check systemctl status SERVICE and verify firewall rules |
| Permission denied | Wrong file ownership or permissions | Check file ownership with ls -la and use chown/chmod to fix |
| 502 Bad Gateway | Backend service not running | Restart the backend service; check logs with journalctl -u SERVICE |
| SSL certificate error | Certificate expired or domain mismatch | Run sudo certbot renew and verify domain DNS points to server IP |
| Service not starting | Config error or missing dependency | Check logs with journalctl -u SERVICE -n 50 for specific error |
| Out of disk space | Logs or data accumulation | Run df -h to identify usage; clean logs or attach CBS storage |
| High memory usage | Too many processes or memory leak | Check with htop; consider upgrading instance plan if consistently high |
| Firewall blocking traffic | Port not open in UFW or Lighthouse console | Open port in Lighthouse console firewall AND sudo ufw allow PORT |
How long does it take to set up Hexo blog on a cloud server?
With the appropriate Lighthouse application image (if available), setup takes 10–30 minutes. Manual installation from a plain Ubuntu image typically takes 30–90 minutes following this guide.
What server specifications do I need for Hexo blog?
Check the prerequisites section of this guide for specific requirements. As a general rule: start with the minimum recommended spec, monitor actual usage, and upgrade from the Lighthouse console if needed.
How do I keep Hexo blog updated?
Follow the update procedure specific to how it was installed (package manager, Docker pull, or binary replacement). Take a Lighthouse snapshot before any major update as a safety net.
How do I back up Hexo blog data?
Use Lighthouse snapshots for full-server recovery plus the application's own export/backup mechanism for granular recovery. Both together provide comprehensive backup coverage.
htop and expand the server spec or attach CBS storage as needed.Start your developer blog today:
👉 Tencent Cloud Lighthouse — Ubuntu VPS for static sites
👉 View current pricing and promotions
👉 Explore all active deals and offers