直接回答:把 Hexo 博客部署到阿里云 ECS 需要四步——域名解析到服务器、服务器装好 Nginx、本地构建后用 rsync 同步静态文件、申请 HTTPS 证书。整个过程可以固化成一个脚本,之后每次发布只需要一条命令。

准备工作

开始之前需要确认四件事:

项目 说明
一台 ECS 实例 1 核 2G 起步;系统选 Ubuntu 22.04 或 Alibaba Cloud Linux
备案 域名指向中国大陆服务器需要完成 ICP 备案
安全组 放行 80 和 443 端口(22 用于 SSH)
本地环境 能正常 npm run build 生成 public/ 目录

备案是最容易被忽略的时间成本。 如果还没备案,建议先去走流程,等备案通过期间可以先把博客搭起来。

一、域名解析

在域名管理后台为博客添加一条记录:

记录类型 主机记录 记录值
A devnotes 你的 ECS 公网 IP

生效后本机验证:

1
2
dig +short devnotes.cuiyuehui.cn
# 应该输出你的服务器 IP

二、服务器初始化

1
2
3
4
5
6
7
8
9
10
11
# 安装 Nginx 与同步工具
sudo apt update
sudo apt install -y nginx rsync

# 创建站点目录
sudo mkdir -p /var/www/devnotes
sudo chown -R $USER:$USER /var/www/devnotes

# 放行防火墙端口
sudo ufw allow 'Nginx Full'
sudo ufw allow OpenSSH

阿里云还需要在控制台的安全组里放行 80 / 443,只配服务器内的防火墙是不够的。

三、Nginx 配置

创建 /etc/nginx/conf.d/devnotes.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
server {
listen 80;
server_name devnotes.cuiyuehui.cn;

root /var/www/devnotes;
index index.html;

# 静态资源压缩,减少传输体积
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/javascript application/json
application/xml image/svg+xml;

location / {
try_files $uri $uri/ $uri/index.html =404;
}

# 带指纹的静态资源长期缓存,HTML 不缓存
location ~* \.(css|js|woff2?|png|jpg|jpeg|gif|svg|webp|ico)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}

location ~* \.html$ {
add_header Cache-Control "no-cache";
}

# 评论服务反向代理(可选,见第六节)
location /comments/ {
proxy_pass http://127.0.0.1:8360/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

检查并生效:

1
2
sudo nginx -t
sudo systemctl reload nginx

关于缓存的一个教训

我一开始给所有文件都加了长缓存,结果每次发布新文章,读者看到的还是旧的首页。HTML 必须不缓存,带指纹的资源才应该长缓存。 上面配置里把 .html 单独设成 no-cache,就是踩了这个坑之后加上去的。

四、申请 HTTPS 证书

用 Certbot 自动申请并配置续期:

1
2
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d devnotes.cuiyuehui.cn

Certbot 会自动改写 Nginx 配置、加上跳转,并注册一个定时续期任务。验证续期是否正常:

1
sudo certbot renew --dry-run

也可以使用阿里云签发的免费证书,在控制台下载后手动配置 ssl_certificatessl_certificate_key,效果相同。

五、部署脚本

在博客目录创建 tools/deploy.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/env bash
set -euo pipefail

SERVER="root@your-server-ip"
TARGET="/var/www/devnotes/"

echo "==> 本地构建"
npm run clean
npm run build

echo "==> 同步到服务器"
rsync -avz --delete \
--exclude '.DS_Store' \
public/ "${SERVER}:${TARGET}"

echo "==> 完成,访问 https://devnotes.cuiyuehui.cn"

几个关键参数:

参数 作用
--delete 删除服务器上本地已不存在的文件,避免旧页面残留
-z 传输压缩,提升跨地域同步速度
--exclude 排除系统残留文件

第一次部署前先做一次空跑确认删除范围:

1
rsync -avz --delete --dry-run public/ "${SERVER}:${TARGET}"

--dry-run 会列出所有将被删除的文件。在没有确认之前不要去掉这个参数——路径写错一个字符,可能删光整个目录。

六、评论服务(可选)

如果希望读者能直接留言,可以在同一台服务器上用 Docker 跑一套 Waline + MySQL。 一份 docker-compose.yml 同时拉起两个容器,MySQL 不对外暴露端口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# docker-compose.yml
services:
db:
image: mysql:8.0
restart: always
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
# Waline 的 MySQL 驱动不支持 MySQL 8 默认的 caching_sha2_password 认证,
# 不加这一行会报 ER_NOT_SUPPORTED_AUTH_MODE
- --default-authentication-plugin=mysql_native_password
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: waline
MYSQL_USER: waline
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
volumes:
- ./mysql-data:/var/lib/mysql
# 首次启动自动导入表结构
- ./waline.sql:/docker-entrypoint-initdb.d/10-waline.sql:ro

waline:
image: lizheming/waline:latest
restart: always
depends_on:
db:
condition: service_healthy
ports:
- "127.0.0.1:8360:8360"
environment:
SITE_NAME: DevNotes
SITE_URL: https://devnotes.cuiyuehui.cn
MYSQL_HOST: db
MYSQL_PORT: "3306"
MYSQL_DB: waline
MYSQL_USER: waline
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
# 必填,否则容器起不来。生成方式:openssl rand -hex 24
JWT_TOKEN: ${JWT_TOKEN}
SECURE_DOMAINS: devnotes.cuiyuehui.cn

密码不要写在 compose 文件里,放在同目录的 .env(记得加进 .gitignore):

1
2
3
MYSQL_ROOT_PASSWORD=随机串
MYSQL_PASSWORD=另一个随机串
JWT_TOKEN=随机串

Waline 自己不会建表。 需要把官方的 waline.sql 放在 compose 同目录,MySQL 首次初始化时会自动导入,建出 wl_Comment / wl_Counter / wl_Users 三张表。少了它,接口会直接返回 500 no such table: wl_Comment——这是我在本地实测时踩到的第一个坑。

配合前面 Nginx 里的 /comments/ 反代,前端配置只需要填:

1
2
3
4
comments:
provider: waline
waline:
serverURL: https://devnotes.cuiyuehui.cn/comments

同域反代的好处是不用处理跨域,也不需要再申请一个子域名和证书。

部署完记得打开 https://devnotes.cuiyuehui.cn/comments/ui/register 注册管理员账号 ——第一个注册的账号自动成为管理员,注册后建议关闭注册入口。

七、备份

静态站点本身不需要备份(源文件在 Git 里),但有两样东西要定期备份:评论数据库、Nginx 与证书配置。

留言在 MySQL 里,用 mysqldump 导出成压缩文件,每天备份一次并只保留最近 14 天。 --single-transaction 保证备份期间不锁表,服务不用停:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/usr/bin/env bash
set -euo pipefail

source /opt/waline/.env # 读取 MYSQL_USER / MYSQL_PASSWORD / MYSQL_DB
BACKUP_DIR="/backup/waline"
mkdir -p "${BACKUP_DIR}"

TARGET="${BACKUP_DIR}/waline-$(date +%F_%H%M).sql.gz"
docker exec devnotes-waline-mysql \
mysqldump -u"${MYSQL_USER}" -p"${MYSQL_PASSWORD}" \
--single-transaction --default-character-set=utf8mb4 "${MYSQL_DB}" \
| gzip > "${TARGET}"

find "${BACKUP_DIR}" -type f -name 'waline-*.sql.gz' -mtime +14 -delete

恢复时把备份解开灌回去即可:

1
2
gunzip -c /backup/waline/waline-2026-09-20_0300.sql.gz \
| docker exec -i devnotes-waline-mysql mysql -uwaline -p你的密码 waline

waline.sqldocker-compose.ymlbackup-waline.sh 这几个文件在仓库的 deploy/waline/ 目录里都有现成的,不用自己敲。

踩坑记录

现象 原因 解决
部署后访问 403 目录权限或 SELinux 限制 确认 Nginx 用户可读;必要时调整 SELinux 策略
首页一直是旧内容 HTML 被长缓存 .html 设置 no-cache
新文章 404 构建失败或同步遗漏 检查 public/ 里是否真的生成了文件
证书续期失败 80 端口被别的服务占用 释放 80 端口后重跑 certbot renew
部署很慢 每次都全量传输 使用 -z 并确认增量同步生效
留言接口返回 500 ER_NOT_SUPPORTED_AUTH_MODE MySQL 账号用了 caching_sha2_password 认证,Waline 的驱动不支持 MySQL 启动参数加上 --default-authentication-plugin=mysql_native_password
留言接口返回 500 no such table 没有导入建表脚本 把官方 waline.sql 放进 compose 同目录,MySQL 首次启动会自动导入

常见问题(FAQ)

静态博客需要多大配置的服务器?

1 核 2G 足够。Nginx 服务静态文件的资源消耗极低,一台最低配的 ECS 可以轻松支撑每天数千次访问。真正吃资源的是评论服务,如果需要自建 Waline 并启用数据库,建议 2 核 4G 起步。

为什么要用 rsync 而不是直接覆盖目录?

rsync 只传输变化过的文件,一次增量发布通常只需要几秒,而且不会把没在本地生成的文件误删。直接覆盖目录在文件多的时候会明显变慢,也容易留下旧文件。

部署后访问出现 403 或 404 是什么原因?

绝大多数是 Nginx 的 root 路径指向错误,或者文件权限不对。先确认 root 指向的目录里确实有 index.html,再确认 Nginx 运行用户对该目录有读取权限。

小结

  • 顺序是:解析 → 装 Nginx → 同步文件 → 上证书。
  • 缓存策略记住一条:HTML 不缓存,带指纹的资源长缓存
  • 用 rsync 增量同步,改动后用 --dry-run 确认。
  • 同域反代评论服务,省掉跨域和第二个证书。

完整的配置文件和脚本都在本站的 deploy/tools/ 目录里,可以直接取用。部署过程中卡在哪一步,欢迎到留言板说说具体报错。

如果站点还没搭起来,先看《用 Hexo + Stellar 搭一个对 AI 友好的技术博客》;如果之后要改地址结构,注意看《站点改版后 URL 变了,怎么保住已有的引用》

站内搜索

没有找到内容!