Halo 博客从零搭建:Docker + nps + nginx 全链路
摘要
家里已经有一个 WordPress 博客(Sakurairo 主题、看板娘、Pio 换装,应有尽有),为什么还要再搭一个 Halo?这篇把新博客 blog.028000.xyz 的完整搭建过程记录下来:CT702 上 Docker 跑 Halo 2.26.1(H2 数据库、256-512M 堆内存),nps 隧道把 8090 送到公网,nginx 挂 Let's Encrypt 证书做反向代理,Hao 主题 + 6 个插件,站点、菜单、页面一次配齐。重点写了初始化真正的入口(/system/setup 而不是 /console/setup)、fail2ban 防爆破、静态资源缓存优化这几个关键决策。
目录
- 一、为什么再搭一个 Halo
- 二、架构总览
- 三、部署:Docker Compose 一把梭
- 四、为什么用 H2 而不是 MySQL/PostgreSQL
- 五、初始化:真正的入口是 /system/setup
- 六、主题与插件
- 七、站点配置:菜单、页面、SEO
- 八、公网化:nps 隧道 + nginx 反代
- 九、性能优化:静态资源缓存
- 十、安全加固:限流 + fail2ban
- 十一、验收清单
- 十二、踩坑记录
- 十三、总结
一、为什么再搭一个 Halo
家里已经有一个 WordPress 博客跑得好好的(Sakurairo 主题、看板娘、速度优化做到 TTFB 0.4s),再搭一个 Halo 不是重复建设,而是分工不同:
| 维度 | WordPress(028000.xyz) | Halo(blog.028000.xyz) |
|---|---|---|
| 定位 | 个人主页 + 折腾展示,重度定制 | 纯写作,干净的技术博客 |
| 技术栈 | PHP + MySQL,老牌 CMS | Java + H2,现代化博客系统 |
| 主题生态 | Sakurairo 重度二次开发 | Hao 主题开箱即用 |
| 维护成本 | 插件多、定制多,升级要小心 | 轻量,升级平滑 |
简单说:WordPress 是"家",Halo 是"书房"。写技术文章这种事,需要一个干扰少、排版好、发布流程顺的写作环境,Halo 的编辑器体验和主题设计正好对这个胃口。两个博客内容上也会错开:WordPress 继续承载个人主页和折腾展示,Halo 承载这 20 篇技术文章这样的长文系列。
二、架构总览
公网用户 → https://blog.028000.xyz
→ VPS 443(nginx:LE 证书、HTTP/2、gzip、HSTS、限流)
→ 127.0.0.1:28505(nps 隧道,任务 15)
→ 192.168.0.127:8093(CT702 宿主端口)
→ 容器内 8090(Halo)
→ /opt/halo2 数据目录(H2 数据库文件)
各层职责:VPS 的 nginx 负责 TLS 终止、安全头、限流;nps 隧道负责把公网流量送进家里;CT702 上的 Docker 负责跑 Halo 本体;数据全部落在 /opt/halo2,容器重建不丢数据。
三、部署:Docker Compose 一把梭
Halo 部署在 CT702 上,宿主端口 8093 映射到容器内 8090(8090 段已经被 EasyImage 等应用占了,端口规划要先查后用)。compose 文件放在 /opt/halo2/docker-compose.yml:
# /opt/halo2/docker-compose.yml(关键片段)
services:
halo:
image: halohub/halo:2.26.1
ports:
- "8093:8090"
volumes:
- /opt/halo2/data:/root/.halo2
environment:
# JVM 堆内存:小内存环境够用就好
- JAVA_OPTS=-Xms256m -Xmx512m
JVM 堆设了 Xms256m/Xmx512m。VPS 只有约 1 GiB 可用内存,PVE 宿主机内存也是主要瓶颈,Halo 这种轻量博客不需要大堆,256-512M 跑得很稳。docker compose up -d 之后,先在内网验证容器 healthy,再往下走。
四、为什么用 H2 而不是 MySQL/PostgreSQL
Halo 支持 H2、MySQL、PostgreSQL 三种数据库,这次选了默认的 H2(嵌入式文件数据库),理由很实际:
| 维度 | H2(本次选择) | MySQL/PostgreSQL |
|---|---|---|
| 部署 | 零额外部署,数据就是 /opt/halo2 下的几个文件 | 要单独起数据库容器,配账号、配备份 |
| 资源 | 几乎零额外内存 | 至少再吃几百 M 内存 |
| 备份 | 停 Halo 后直接拷文件就行 | 要走 mysqldump/pg_dump |
| 适用规模 | 个人博客完全够用 | 高并发、多实例才需要 |
H2 的缺点也清楚:不支持多实例共享(单机博客不需要)、极端情况下不如独立数据库好调优。但对个人博客这个量级,H2 是"够用且省心"的最优解。备份策略:Halo 停机后直接备份 /opt/halo2 整个目录,恢复时原样拷回去就行,简单到不需要写脚本。
一个提醒:H2 的数据文件在容器重建时靠 volume 保留,/opt/halo2/data 这个宿主目录本身要进 NAS 备份视野。数据库选得轻,备份可不能轻。
五、初始化:真正的入口是 /system/setup
Halo 首次启动后要初始化管理员账号,这里有个大坑:真正的初始化入口是 /system/setup,而不是文档里常提的 /console/setup。访问 /console/setup 会被直接踢到登录页,转一圈又回来,表现得像"系统坏了"。
# 正确的初始化地址(内网或经隧道访问)
http://192.168.0.127:8093/system/setup
在这个页面设置管理员用户名(admin)、邮箱(admin@028000.xyz)、密码。密码只存 Secure Vault,文章、日志、聊天记录里一律不出现明文——这是这套家庭数据中心的铁律。
初始化完成后,用 admin 登录一次后台,确认能正常进入控制台,再继续后面的配置。
六、主题与插件
主题
| 主题 | 版本 | 状态 |
|---|---|---|
| Hao | 1.7.3 | 启用,主力主题 |
| Sakura | 2.5.0 | 备用 |
Hao 主题的设计语言和"折腾 · 记录 · 分享"的定位很搭:首页信息流、文章页排版、暗色模式都开箱即用。Sakura 留作备用,换主题就是后台点一下的事。
有几个 Hao 主题的视觉开关(图片懒加载、顶部 Banner、首页第一屏等)需要进后台手动检查确认,自动化点不动。这类"最后 5% 的手动活"别省,主题好不好看就差在这几下。
插件(全部启用)
| 插件 | 版本 | 用途 |
|---|---|---|
| sitemap | 1.3.0 | 生成 sitemap.xml,SEO 必备 |
| moments | 1.19.0 | 瞬间(碎碎念时间线) |
| links | 2.3.0 | 友链页面 |
| photos | 2.1.2 | 图库页面 |
| highlightjs | 1.3.2 | 代码高亮,技术博客刚需 |
| page-cache | 1.6.0 | 页面缓存,提速 |
插件策略是"少而精":每个插件都有明确用途,不装"可能有用"的。highlightjs 和 page-cache 是技术博客的刚需,一个管代码好看,一个管访问快。
七、站点配置:菜单、页面、SEO
站点标题「028000 · 博客」,副标题「折腾 · 记录 · 分享」,SEO 关键词和描述都填了。页面用插件建了 6 个:
| 页面 | 路径 | 来源 |
|---|---|---|
| 瞬间 | /moments | moments 插件 |
| 图库 | /photos | photos 插件 |
| 友链 | /links | links 插件 |
| 关于 | /about | 手动建 |
| (另两个页面略) |
主菜单 8 项:首页、归档、瞬间、图库、友链、关于,再加两个自定义。菜单是博客的骨架,一次配齐,免得以后文章多了再返工。
SEO 方面:sitemap.xml 实测返回 200,搜索引擎能正常抓取。站点描述写清楚"折腾 · 记录 · 分享"的定位,关键词覆盖家庭数据中心、Docker、NAS、PVE 几个主要方向。
八、公网化:nps 隧道 + nginx 反代
nps 侧新增任务 15:VPS 28505 → 192.168.0.127:8093。VPS 的 nginx 新建 blog.028000.xyz 站点,配置要点:
# 关键片段
server {
listen 443 ssl http2;
server_name blog.028000.xyz;
ssl_protocols TLSv1.2 TLSv1.3; # 仅 1.2/1.3
# HSTS、gzip 开启
# 限流:登录接口 5 次/分钟,全站 API 60 次/分钟
location /apis/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://127.0.0.1:28505;
}
location / {
proxy_pass http://127.0.0.1:28505;
}
}
证书走 Let's Encrypt,certbot 自动续期。限流分两档:登录接口 5 次/分钟(防爆破),全站 API 60 次/分钟(防刷)。这两档是和 vaultwarden、OpenList 对齐过的,全家统一标准。
九、性能优化:静态资源缓存
Halo 本身是 Java 应用,动态渲染有一定开销。优化思路:动态部分靠 page-cache 插件做服务端缓存,静态部分靠 nginx 告诉浏览器"放心缓存"。
# 静态资源 location:主题、附件、上传目录
location ~* ^/(themes|assets|upload)/ {
proxy_pass http://127.0.0.1:28505;
# 透传 Halo 自带的 Cache-Control: max-age=31536000
# 注意:location 内一旦写 add_header,会覆盖 server 级的 HSTS
# 所以这里要把 HSTS 再加一遍
add_header Strict-Transport-Security "max-age=31536000" always;
}
这里有个 nginx 的隐蔽行为:location 块里一旦出现任何 add_header,server 级的 add_header 会被整体覆盖,包括 HSTS。所以静态资源 location 里必须把 HSTS 再声明一遍,否则这部分资源就丢了 HSTS 保护。这个坑不踩一次很难记住。
另外 Halo 后台开了 Pjax(无刷新跳转)、图片 lazy-src(懒加载),page-cache 插件做服务端整页缓存。实测 VPS 经隧道到 Halo 的 TTFB 约 0.4s,对个人博客来说完全够用。
没做的优化:VPS 整页缓存。原因是首页会带 XSRF-TOKEN Cookie,整页缓存有 CSRF 污染风险。性能和安全的权衡里,安全优先,0.4s 的 TTFB 不值得冒这个险。
十、安全加固:限流 + fail2ban
nginx 层的限流(上一节)是第一道,fail2ban 是第二道。照抄 vaultwarden 的成熟模式,新建 halo-blog-limit jail:
# /etc/fail2ban/jail.d/halo-blog-limit.conf(示意)
[halo-blog-limit]
enabled = true
filter = halo-blog-limit
# 10 分钟内 8 次触发 429,封 1 小时
maxretry = 8
findtime = 600
bantime = 3600
逻辑链条:nginx 限流先挡,超限返回 429;fail2ban 盯着 429 日志,10 分钟内 8 次就封 IP 1 小时。实测从 VPS 本机单 IP 打限流,429 正常触发,fail2ban 正常封禁。
测试限流必须从 VPS 本机打,不能从沙箱/本地电脑打。因为沙箱出口经 Cloudflare,每次请求换 IP,fail2ban 的"单 IP 计数"永远攒不满,测了等于没测。这个细节之前在 vaultwarden 那篇也提过,全家通用。
十一、验收清单
| 测试项 | 结果 |
|---|---|
| 公网 HTTPS 访问 | 正常,证书有效 |
| HTTP 跳 HTTPS | 301 正常 |
| 首页/文章页/归档页 | 渲染正常 |
| sitemap.xml | 200 |
| 主题 Hao 生效 | 正常 |
| 6 个插件全部启用 | 正常 |
| 限流(本机单 IP) | 429 正常触发 |
| fail2ban 封禁 | 正常 |
| TTFB | 约 0.4s |
Dashy 导航页也加了 Halo 博客入口,图床入口换成了公网地址 https://img.028000.xyz,博客文章里的配图可以直接用图床外链。
十二、踩坑记录
坑 1:初始化入口是 /system/setup,不是 /console/setup
访问 /console/setup 会被踢到登录页,表现得像系统故障。正确的初始化入口是 /system/setup。这个坑的隐蔽之处在于:URL 只差一个词,报错信息又没有任何提示。
坑 2:location 内 add_header 覆盖 server 级 HSTS
nginx 的静态资源 location 里一旦写了 add_header,server 级的 HSTS 就没了。解法:location 内把 HSTS 再加一遍。以后写任何带 add_header 的 location,都先检查一遍 server 级还有什么 header 会被覆盖。
坑 3:VPS 整页缓存不能做
首页带 XSRF-TOKEN Cookie,整页缓存会污染 CSRF token。解法:不做 VPS 整页缓存,靠 page-cache 插件做服务端缓存。性能优化永远不能以安全为代价。
坑 4:Hao 主题部分开关要手动确认
图片懒加载、顶部 Banner、首页第一屏等视觉开关,自动化点不动,需要进后台手动检查。这类"最后 5%"别省,主题好不好看就差在这几下。
Halo 的备份与升级
博客跑起来只是开始,备份和升级才是长期主义。
备份:H2 是文件数据库,备份就是停 Halo 后拷 /opt/halo2 整个目录:
# 备份(先停容器,保证数据一致)
pct exec 702 -- docker stop halo
cp -r /opt/halo2 /mnt/nas/backup/halo/halo2-$(date +%F)
pct exec 702 -- docker start halo
/opt/halo2 要进 NAS 备份视野,和 PVE 备份体系衔接。恢复就是反向操作:停容器、拷回去、起容器。
升级:Halo 升级就是换镜像 tag:
# 升级前先备份(上面那步),再换镜像
# 改 docker-compose.yml 里的 image: halohub/halo:2.26.1 → 新版本
pct exec 702 -- docker compose -f /opt/halo2/docker-compose.yml up -d
# 进后台确认版本、检查主题插件兼容性
主题和插件在后台一键升级,但大版本升级前先看 changelog,Hao 主题和插件的兼容性要逐个确认。升级顺序:备份 → 升 Halo → 升主题 → 升插件,每步都进前台点一点确认正常。
写作流程:从 Markdown 到发布
博客搭好是"台子",写作流程是"戏"。这 20 篇技术文章的写作流程,固定四步:
第一步:本地写 Markdown。 Typora 或 VSCode,按统一模板(frontmatter、封面行、摘要、目录、正文、总结)。模板是"写作契约":格式统一,读者看着顺,发布时不用返工。图片先放本地,发布前批量上传图床替换链接。
第二步:自检。 写完放一天,第二天再看。检查三件事:事实对不对(数字、路径、版本号和实际一致)、有没有敏感信息(密码、Token、内网细节该脱敏的脱敏)、读得顺不顺(大声读一遍,拗口就改)。
第三步:发布。 Halo 后台新建文章,粘贴 Markdown,设分类标签,传封面图,点发布。发布后看一眼公开 URL:排版对不对、代码高亮正不正常、图片加载了没、手机上看崩没崩。
第四步:归档。 Markdown 源文件存 NAS,和发布的文章一一对应。博客是"发布版",NAS 是"底稿"。哪天换博客系统,底稿还在,内容不丢。
流程固定的好处:写第 20 篇和写第 1 篇,动作一样,不会漏。写作是长期事,流程是耐力的来源。
十三、总结
Halo 博客从零到上线,完整链条:Docker 部署(H2 数据库、256-512M 堆)→ /system/setup 初始化 → Hao 主题 + 6 插件 → 菜单页面 SEO 配齐 → nps 隧道 + nginx 反代公网化 → 静态资源缓存优化 → 限流 + fail2ban 加固 → 逐项验收。几个关键决策:H2 而不是 MySQL(省资源、备份简单)、page-cache 而不是 VPS 整页缓存(避开 CSRF 污染)、fail2ban 照抄 vaultwarden 的成熟模式。新博客定位是纯写作的技术书房,后面 20 篇文章就发在这里。
相关阅读:《自建图床 EasyImage 部署与公网化全记录》、《fail2ban 实战:从 SSH 到 Web 登录接口的防护》。