虽然 Hexo 的博客可以部署到 GitHub 、Cloudflare Pages 等免费服务上,但是如果你有一台访问超快的 VPS,也可以选择直接部署到自己的 VPS 上。
将 Hexo 博客部署到自己的 VPS 上,核心原理是在服务器上创建一个Git 裸仓库(Bare Repository),并通过 Git 的 post-receive 钩子(Hook),在每次本地执行 hexo d 推送静态文件时,自动将最新代码检出(Checkout)到 Web 服务器的网站根目录下。
以下是完整的手动配置流程,文末附带一键自动化部署脚本。
一、 手动配置步骤
假设你的服务器系统为 Debian / Ubuntu,目标是将博客源码推送到 /var/repo/hexo.git,并将生成的静态网页自动同步到 /var/www/hexo。
1. 安装 Git 并创建专用用户
在 VPS 上执行,出于安全考虑,限制 git 用户通过 SSH 登录交互式 Shell。
首先确保服务器已安装 Git,然后创建一个名为 git 的系统用户,并将其默认 Shell 修改为 git-shell(仅允许执行 Git 推送/拉取操作,禁止常规命令行登录)。
1 | # 1. 安装 Git |
2. 配置 SSH 公钥免密登录
避免每次 hexo d 都需要输入服务器密码
在本地电脑上获取你的 SSH 公钥(如果还没有,先在本地运行 ssh-keygen 生成):
1 | cat ~/.ssh/id_ed25519.pub |
生成 SSH 私钥命令
1 | ssh-keygen -t ed25519 -C "hexo-deploy" |
回到 VPS 服务器,将复制的公钥写入 git 用户的授权列表中,并严格设置目录权限(权限不对会导致 SSH 验证失败):
1 | sudo -u git mkdir -p /home/git/.ssh |
3. 初始化 Git 裸仓库与网站目录
裸仓库只保存 Git 版本历史,不保存工作区源文件
创建用于存放 Git 仓库的目录 /var/repo 和存放网页文件的目录 /var/www/hexo,并将所有权交给 git 用户, 钩子脚本有权限写入文件。
1 | # 1. 创建并初始化裸仓库 |
4. 编写 post-receive 自动同步钩子
实现推送即部署的核心步骤
当本地向裸仓库推送代码完成后,Git 会自动触发 hooks/post-receive 脚本。我们利用它将文件强制部署到 /var/www/hexo。
1 | # 创建并写入钩子内容 |
5. 配置本地 Hexo 并推送测试
在你的本地电脑博客根目录下操作
首先确保本地安装了 Git 部署插件:
1 | npm install hexo-deployer-git --save |
打开本地 Hexo 根目录下的 _config.yml 文件,修改文末的 deploy 配置项:
1 | deploy: |
保存后,在本地终端执行三连命令即可完成发布:
1 | hexo clean && hexo g && hexo d |
推送成功后,去服务器的 /var/www/hexo 目录下查看,你会发现静态网页文件已经全部自动生成好了。随后只需用 Nginx 或 Caddy 将网站根目录指向 /var/www/hexo 即可访问。
进阶:一键自动化部署脚本
如果你觉得上述手动步骤太繁琐,或者后续还想在同一台 VPS 上快速部署其他静态站点(如 VitePress、Hugo、纯静态主页等),可以直接在服务器上使用下面整理好的自动化脚本。
脚本亮点
- 幂等性设计:可重复执行,已存在的用户、公钥或目录会自动跳过,方便随时新增第二个、第三个静态站点仓库。
- 安全隔离:自动配置
git-shell与严格的.ssh权限,无需手动切换用户调整chown/chmod。 - 灵活传参:既支持命令行直接传参(如
./git-deploy-repo.sh hexo /var/www/hexo),也支持不带参数交互式运行。
使用方法
登录 VPS,创建脚本文件
git-deploy-repo.sh:1
vim git-deploy-repo.sh
将下方完整代码粘贴进去(注意将第 12 行的
PUBKEY替换为你本地电脑的真实 SSH 公钥):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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# 用 git + post-receive hook 部署静态站点(如 Hexo)
# 用法:git-deploy-repo.sh [repo 名] [部署目录]
# 例:git-deploy-repo.sh hexo /var/www/hexo
# 不带参数时交互输入。可重复执行,用于快速新增不同的 repo。
set -u
GIT_USER="git"
REPO_ROOT="/var/repo"
PUBKEY="PUBKEY"
# ── sudo 包装:非 root 自动加 sudo ────────────────────────────────
if [ "$(id -u)" -eq 0 ]; then
SUDO=""
else
if command -v sudo >/dev/null 2>&1; then
SUDO="sudo"
else
echo "需要 root 权限,但未找到 sudo。请以 root 身份执行。" >&2
exit 1
fi
fi
# 以 git 用户身份执行命令(root 下用 runuser,否则用 sudo -u)
as_git() {
if [ "$(id -u)" -eq 0 ]; then
runuser -u "$GIT_USER" -- "$@"
else
sudo -u "$GIT_USER" "$@"
fi
}
if ! command -v git >/dev/null 2>&1; then
echo "未找到 git,正在安装..."
$SUDO apt update && $SUDO apt install -y git || exit 1
fi
# ── 参数 ──────────────────────────────────────────────────────────
REPO_NAME="${1:-}"
if [ -z "$REPO_NAME" ]; then
read -r -p "repo 名称(如 hexo): " REPO_NAME </dev/tty
fi
REPO_NAME="${REPO_NAME%.git}"
if [ -z "$REPO_NAME" ]; then
echo "repo 名称不能为空。" >&2
exit 1
fi
WORK_TREE="${2:-}"
if [ -z "$WORK_TREE" ]; then
read -r -p "部署目录 [默认 /var/www/$REPO_NAME]: " WORK_TREE </dev/tty
WORK_TREE="${WORK_TREE:-/var/www/$REPO_NAME}"
fi
REPO_DIR="$REPO_ROOT/$REPO_NAME.git"
# ── 1. git 用户 ───────────────────────────────────────────────────
GIT_SHELL="$(command -v git-shell)"
if [ -z "$GIT_SHELL" ]; then
echo "未找到 git-shell。" >&2
exit 1
fi
if ! grep -qx "$GIT_SHELL" /etc/shells; then
echo "$GIT_SHELL" | $SUDO tee -a /etc/shells >/dev/null
fi
if id "$GIT_USER" >/dev/null 2>&1; then
echo "用户 $GIT_USER 已存在,跳过创建。"
else
$SUDO adduser --disabled-password --gecos "" --shell "$GIT_SHELL" "$GIT_USER" || exit 1
echo "已创建用户 $GIT_USER。"
fi
if [ "$(getent passwd "$GIT_USER" | cut -d: -f7)" != "$GIT_SHELL" ]; then
$SUDO usermod -s "$GIT_SHELL" "$GIT_USER"
echo "已将 $GIT_USER 的 shell 设为 $GIT_SHELL。"
fi
# ── 2. SSH 公钥 ───────────────────────────────────────────────────
GIT_HOME="$(getent passwd "$GIT_USER" | cut -d: -f6)"
AUTH_KEYS="$GIT_HOME/.ssh/authorized_keys"
as_git mkdir -p "$GIT_HOME/.ssh"
as_git touch "$AUTH_KEYS"
if $SUDO grep -qF "$PUBKEY" "$AUTH_KEYS"; then
echo "公钥已存在,跳过。"
else
echo "$PUBKEY" | as_git tee -a "$AUTH_KEYS" >/dev/null || exit 1
echo "已添加公钥。"
fi
$SUDO chmod 700 "$GIT_HOME/.ssh"
$SUDO chmod 600 "$AUTH_KEYS"
# ── 3. 裸仓库 ─────────────────────────────────────────────────────
$SUDO mkdir -p "$REPO_ROOT"
$SUDO chown "$GIT_USER:$GIT_USER" "$REPO_ROOT"
if [ -d "$REPO_DIR" ]; then
echo "仓库 $REPO_DIR 已存在,跳过 init。"
else
as_git git init --bare "$REPO_DIR" || exit 1
fi
# ── 4. 部署目录 ───────────────────────────────────────────────────
$SUDO mkdir -p "$WORK_TREE"
$SUDO chown "$GIT_USER:$GIT_USER" "$WORK_TREE"
# ── 5. post-receive hook ──────────────────────────────────────────
HOOK="$REPO_DIR/hooks/post-receive"
as_git tee "$HOOK" >/dev/null << EOF
#!/bin/sh
git --work-tree="$WORK_TREE" --git-dir="$REPO_DIR" checkout -f
EOF
$SUDO chmod +x "$HOOK"
echo
echo "完成:"
echo " 仓库:$REPO_DIR"
echo " 部署:$WORK_TREE"
echo " 远程地址:$GIT_USER@$(hostname -f 2>/dev/null || hostname):$REPO_DIR"
echo " Hexo _config.yml 中 deploy.repo 填上面的远程地址即可。"赋予执行权限并运行:
1
2chmod +x git-deploy-repo.sh
./git-deploy-repo.sh hexo /var/www/hexo
评论