用 1Panel + OpenResty + Halo + SSL 搭建自己的个人博客(Ubuntu 24.04 实战)
一、Halo 是什么,为什么选它
Halo 是一款用 Java 写的开源建站工具,GPL-3.0 协议,GitHub 上接近 4 万 star。它和 WordPress 的定位类似,但技术栈完全不同:单体 Java 应用(Spring Boot + R2DBC),插件是独立的 JAR 包,主题是一套模板包,升级时不用担心某个 PHP 插件把整站拖崩。
它能做的事:
写作:Markdown 和富文本双编辑器,支持文章版本对比、批量调整图片宽度;
建站:文章、独立页面、分类、标签、导航菜单,主题市场一键换皮;
扩展:插件市场提供搜索、评论、Sitemap、图床、AI 助手等能力;
多用户:角色权限(RBAC),适合团队博客;
存储:附件可放本地,也可接 S3 / MinIO 等对象存储;
备份:Console 内置整站备份与恢复,换机器、换数据库都能还原;
API:完整的 REST API,配合个人令牌可以做自动化发布。
版本说明:本文基于 Halo 2.26 / 2.27 一系(社区版镜像 halohub/halo,2026 年 10 月官方文档的示例版本为 2.27)编写。1Panel 应用商店里直接选最新的稳定版即可,不必照抄小版本号。
几个必须记住的默认值:
顺带说清楚:Halo 有社区版和付费版(专业版、商城版),付费版镜像带
-pro后缀,需要许可证激活。个人博客用社区版完全够,功能上不缺东西。
二、整体架构与前置准备
先看清楚流量怎么走,后面排错才不会瞎猜:
用户浏览器 ──HTTPS(443)──▶ OpenResty(1Panel 网站)
│ 反代 + SSL 卸载 + 强制跳转 + gzip
▼
http://127.0.0.1:8090 ──▶ Halo 容器
│
▼
PostgreSQL(1Panel 应用)
(仅在容器网络内,不对外)关键设计:Halo 的 8090 只监听在 127.0.0.1,公网唯一入口是 OpenResty 的 443。这样既不用为 Halo 单独配证书,也不会把管理后台裸奔在公网;数据库更是连宿主机端口都不映射,只有 Halo 容器能连。
前置清单
端口规划:
域名与端口,先做这三件事
# 1. 确认域名已解析到本机(IPv4 用 A 记录,纯 IPv6 用 AAAA 记录)
dig +short blog.example.com
# 2. 系统防火墙放行
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw status
# 3. 云服务器还要去控制台安全组放行 80/443最常见的翻车点:系统防火墙开了,云厂商安全组没开。表现就是浏览器打不开、证书申请一直失败,而你在服务器上
curl 127.0.0.1一切正常。装之前先把这三层(安全组 / 系统防火墙 / 1Panel 防火墙)都过一遍。
三、安装 1Panel
用官方一键脚本,会自动装好 Docker 与 Docker Compose:
bash -c "$(curl -sSL https://resource.fit2cloud.com/1panel/package/v2/quick_start.sh)"安装过程中会让你设置面板端口,装完终端会输出三样东西,务必复制保存:
面板访问地址(
http://<IP>:<端口>/<安全入口>)初始用户名
初始密码
装完立刻做三件加固的事:
改掉默认用户名和密码:面板设置 → 用户。
修改面板端口与安全入口:面板设置 → 安全。默认端口是全网扫描器的重点目标。
开启两步验证:面板设置 → 安全 → MFA,用 Authenticator 绑定。
1Panel 面板端口建议不要保留默认。改完端口记得在系统防火墙放行新端口,否则会把自己锁在门外。
四、安装 OpenResty 与数据库
4.1 安装 OpenResty
左侧 应用商店 → 搜索 OpenResty → 安装,参数保持默认。
1Panel 的 OpenResty 默认使用 host 网络,直接占用宿主机的 80/443,并且能反代到 127.0.0.1 上的服务——这正是我们要的。
装之前先确认 80/443 没被占用:
sudo ss -lntp | grep -E ':(80|443)\s'如果有输出,说明系统里已经装了 nginx / apache / caddy,先停掉再装,否则 OpenResty 起不来:
sudo systemctl disable --now nginx 2>/dev/null
sudo systemctl disable --now apache2 2>/dev/null第一次进入「网站」菜单时,如果还没装 OpenResty,1Panel 也会主动提示安装,按提示一键装完即可。
4.2 安装数据库
应用商店 → 搜索 PostgreSQL → 安装。Halo 官方 Docker Compose 示例默认就是 PostgreSQL,兼容性和性能都更稳,推荐 15 或 16。
参数建议:
MySQL 8 也能跑(Halo 支持 PostgreSQL / MySQL / MariaDB / H2),但既然是从零开始,直接按官方推荐走。
数据库不用手动建库建用户。 下一步装 Halo 时,1Panel 会在你选的数据库实例里自动创建
halo库和对应的用户、授权、密码,全部自动写进应用的.env。手工去建反而容易和面板记录对不上。
装完在「应用商店 → 已安装」里确认 PostgreSQL 状态是「已启动」,再往下走。
五、安装 Halo
5.1 安装参数
应用商店 → 搜索 Halo → 点卡片 → 安装。逐项说明:
外部访问地址为什么关键? Halo 用它生成 RSS、Sitemap、邮件里的链接、后台回调地址和附件绝对路径。填错(比如填了 IP 或 http),后面会看到 RSS 里全是 http://127.0.0.1:8090、后台登录后跳回 http、浏览器报混合内容。这个值之后再改需要重建容器,能一次填对就别返工。
如果域名和证书都还没准备好,也可以先填
http://服务器IP:8090装起来,等证书签好再改——但那就多一次重建,还要清一次缓存,不如按本文顺序一次到位。
5.2 验证安装
点「确认」后等 1~2 分钟,镜像拉取 + 数据库初始化。装完检查三处:
# 1. 容器状态
docker ps --format '{{.Names}}\t{{.Status}}' | grep -i halo
# 2. 启动日志(应能看到 Version: 2.27.x)
docker logs --tail 60 1Panel-halo-xxxx
# 3. 健康检查(返回 200 才算真的就绪)
curl -I http://127.0.0.1:8090/actuator/health/readiness日志里出现 Version: 2.27.x 和 Started Application in xx seconds 就说明起来了。第一次启动会慢一些(要建表、装默认插件、生成默认文章),健康检查通过前 Dashboard 可能显示「启动中」,属正常。
5.3 先别急着打开 IP:8090 初始化
官方文档写得很明确:需要域名访问时,先配好反向代理和域名解析,再做初始化。
原因很实际——初始化会写入站点设置、生成默认文章和 RSS 缓存。如果此时外部地址还是 IP:8090,这些内容里带的就是 IP 链接;等你换了域名再改配置,还得清缓存、改数据库记录,麻烦程度远超「先建站」。
所以顺序是:域名解析 → 装 Halo(填 https 域名)→ 建站 → 签证书 → 打开 https 域名初始化。
六、创建网站与反向代理
6.1 创建网站
网站 → 创建网站 → 类型选 反向代理(部分版本会显示「已装应用」,能直接选 Halo,更省事):
主域名:
blog.example.com代理地址:
http://127.0.0.1:8090其他保持默认 → 确认
如果面板提供「已装应用」下拉框,直接选刚装的 Halo,1Panel 会自动填好代理地址和请求头。
坑:502 且代理地址写的是容器名。 1Panel 的 OpenResty 默认是 host 网络,
127.0.0.1指的就是宿主机,所以填http://127.0.0.1:8090一定对。反过来,如果你的 OpenResty 被改成 bridge 网络,127.0.0.1会指向 OpenResty 容器自己,这时要改用宿主机的容器网络 IP,或者把 OpenResty 改回 host 网络。判断方法:docker inspect 1Panel-openresty-xxxx --format '{{.HostConfig.NetworkMode}}',输出host就对。
6.2 补齐关键请求头(重点)
1Panel 模板生成的默认反代配置足够「能打开」,但不够「能正常用」。必须补齐下面几项:
在「网站 → 设置 → 反向代理 → 编辑」里替换成下面这段(推荐直接改「配置文件」,一次到位):
location ^~ / {
proxy_pass http://127.0.0.1:8090;
# —— 基础转发头 ——
proxy_http_version 1.1;
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;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
# —— WebSocket / 长连接(Console 部分功能用得到)——
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# —— 超时:备份、恢复、装插件别 504 ——
proxy_connect_timeout 60s;
proxy_send_timeout 600s;
proxy_read_timeout 600s;
# —— 上传体积(Halo 官方示例就是 1024m)——
client_max_body_size 1024m;
# —— Console 实时日志走 SSE,不要缓冲 ——
proxy_buffering off;
proxy_redirect off;
}改完保存,1Panel 会自动校验并重载 OpenResty。等价的命令行操作:
docker exec -it 1Panel-openresty-xxxx openresty -t
docker exec -it 1Panel-openresty-xxxx openresty -s reload6.3 验证反代通了
# 应返回 200 或 302(Halo 会把根路径重定向到 /login 或首页)
curl -I http://blog.example.com
# 直接看 Halo 的健康状态
curl -s http://blog.example.com/actuator/health/readiness此刻访问 http://blog.example.com/console 应该能看到 Halo 的初始化页面(先别急着填,签完证书再初始化)。
七、申请 SSL 证书并开启 HTTPS
7.1 创建 ACME 账户
网站 → 证书 → ACME 账户 → 创建账户:
邮箱:填真实邮箱(Let's Encrypt 到期提醒会用)
类型:
Let's Encrypt密钥算法:
EC 256(比 RSA 2048 更短更快,兼容性没问题)
7.2 选择验证方式
DNS 验证要先去 证书 → DNS 账户 添加服务商 API 密钥。以阿里云为例:RAM 控制台建一个只授权「DNS 修改」的子账号 AccessKey,不要用主账号 AK。
7.3 申请证书
证书 → 申请证书:
主域名:
blog.example.com其他域名:可留空(要泛域名就填
*.example.com,仅 DNS 验证支持)ACME 账户:选刚创建的
验证方式:HTTP 或 DNS
勾选「自动续签」
点确认后等 1~2 分钟,日志出现「申请证书成功」即可。
7.4 站点启用 HTTPS
回到 网站 → 设置 → HTTPS:
启用 HTTPS:开
SSL 选项:选择已有证书 → 选中刚才申请的
HTTP 选项:访问 HTTP 自动跳转到 HTTPS
支持协议:勾选 HTTP/2(有性能收益)
HSTS:先别急着开,等整站跑通一周再开,否则证书出问题时浏览器会强制走 HTTPS,连回退的机会都没有
保存后访问 https://blog.example.com,地址栏出现小锁,HTTP 会自动 301 到 HTTPS。
验证一下证书和跳转:
# 证书签发者与有效期
echo | openssl s_client -connect blog.example.com:443 -servername blog.example.com 2>/dev/null \
| openssl x509 -noout -issuer -dates
# 跳转是否生效(应看到 301 + location: https://...)
curl -I http://blog.example.comLet's Encrypt 单证书有效期 90 天,1Panel 的自动续签会在到期前完成,只要「自动续签」勾了就不用管。续签失败的常见原因是 80 端口被占、DNS API 失效、或者站点被 CDN 代理导致验证请求绕不到源站。
用了 Cloudflare 的话:SSL/TLS 模式必须选 Full (strict),选 Flexible 会造成「重定向次数过多」的死循环——因为源站看到的是 http,又把你跳回 https。
八、Halo 初始化与基础配置
8.1 完成初始化
浏览器打开 https://blog.example.com/console,首次会进入初始化页面(也可以在「应用商店 → 已安装 → Halo → 跳转」进入):
站点名称:博客名
管理员用户名、邮箱、密码:邮箱要填真的,找回密码和评论通知都靠它
提交后进入 Console 仪表盘,前台地址就是 https://blog.example.com。
8.2 基础设置
Console → 设置 → 基本设置:
站点标题、副标题、站点描述(影响 SEO)
Logo、Favicon
备案号(国内服务器需要,会显示在页脚)
8.3 装主题
Console → 主题 → 安装,两种方式:
上传主题 zip 包(从 Halo 主题市场下载);
直接从内置主题市场在线安装(需要服务器能访问外网)。
装完点「启用」,前台立刻换皮。主题的菜单、卡片布局一般在「主题 → 设置」里调。
8.4 装插件
Console → 插件,建议至少装这几个:
从 2.26 开始,全新安装的站点会预设并自动启用 AI Foundation 插件——它是 Halo 官方 AI 能力底座,统一管理模型供应商和默认模型。不配置模型它不会工作,也不会影响正常写作;想用 AI 摘要、AI 问答再去填 API Key 即可。
8.5 附件与固定链接
附件:默认存本地(容器内
/root/.halo2/attachments,宿主机在应用数据目录)。图片多的话建议接 S3 / MinIO / 七牛等对象存储,省服务器带宽。固定链接:设置 → 文章 → 固定链接,常见格式
/archives/{slug}。建站初期定好就别改,改了老链接全 404。
8.6 检查时区
发布文章的时间差 8 小时,就是容器时区的问题。1Panel 安装 Halo 时如果参数里有「时区」,选 Asia/Shanghai;没有的话进容器确认:
docker exec -it 1Panel-halo-xxxx date如果输出是 UTC,编辑应用参数加上环境变量 TZ=Asia/Shanghai,或者给 JVM 加 -Duser.timezone=Asia/Shanghai,重建容器后生效。
8.7 如果当初填的是 IP / HTTP,怎么改回来
应用商店 → 已安装 → Halo → 参数,把「外部访问地址」改成 https://blog.example.com,保存(会重建容器)。重建完成后:
前台强制刷新(Ctrl+F5),确认 CSS/JS 都走 https;
看 RSS:
https://blog.example.com/rss.xml,里面的链接必须是域名;看 Sitemap:
https://blog.example.com/sitemap.xml,同样不能出现 IP。
Halo 不支持部署在子目录。
https://example.com/blog这种形式官方明确不支持,必须用独立域名或子域名指向站点根路径。想「一个域名挂多个站」,正确的做法是子域名 + 多个网站。
九、备份、升级与迁移
9.1 Halo 自带备份(最方便)
Console → 备份 → 创建备份,会打包整站数据(文章、设置、主题、插件、附件元信息)。备份文件落在数据目录的 backups/ 下:
# 先找到数据目录的真实位置
docker inspect 1Panel-halo-xxxx --format '{{ range .Mounts }}{{ .Source }} -> {{ .Destination }}{{ "\n" }}{{ end }}'
# 通常是 /opt/1panel/apps/halo/halo/data
ls -lh /opt/1panel/apps/halo/halo/data/backups恢复时在同一页面选文件即可,恢复过程会覆盖现有数据,结束后提示重启 Halo。恢复前先做一次当前状态的备份,别把自己唯一的退路覆盖掉。
9.2 1Panel 计划任务(推荐生产用)
计划任务 → 创建任务,建两个:
备份目录建议指到另一块盘或对象存储,别和站点放在同一个目录里——磁盘挂了,备份跟着一起没。
9.3 手动冷备(迁移或大版本升级前)
# 1. 停容器,保证数据一致
docker stop 1Panel-halo-xxxx
# 2. 打包应用目录
sudo tar -czf /root/halo-data-$(date +%F).tar.gz -C /opt/1panel/apps/halo/halo data
# 3. 导出数据库(容器名先用 docker ps | grep postgres 确认)
docker exec -t 1Panel-postgresql-xxxx pg_dump -U halo halo > /root/halo-db-$(date +%F).sql
# 4. 启回来
docker start 1Panel-halo-xxxx9.4 升级
推荐路径:1Panel 应用商店 → 已安装 → Halo → 升级 → 选目标版本。面板会拉新镜像并重建容器,数据卷不受影响。
升级前必做两件事:
备份(9.1 或 9.3 任选,生产环境建议两个都做);
看 Release Notes,注意破坏性变更。比如 2.23 升级了 Spring Boot 4,官方明确提示部分插件不兼容(包括应用市场插件),要求先把应用市场插件升到 1.16.0 再升 Halo。
升级后检查:容器状态、docker logs 有无异常、插件列表是否报错、前台文章能否正常打开、Console 能否登录。
9.5 迁移到新服务器
新机器装同版本 1Panel,装 OpenResty、同版本 PostgreSQL、同版本 Halo(先别初始化);
停掉新旧两边的 Halo 容器;
把旧机器的
data目录拷到新机器对应位置,权限保持一致;导入数据库 dump;
启动新容器,访问域名确认文章、主题、插件都在;
改 DNS 解析到新机器,旧机器观察几天再下线。
十、性能与安全收尾
10.1 能立刻做的性能项
开 gzip:网站 → 配置 → gzip,压缩 HTML/CSS/JS,文本类资源体积能降 60% 以上;
开 HTTP/2:HTTPS 设置里勾上,多资源加载明显更快;
静态资源缓存:Halo 自带
Cache-Control,不用额外配;如果前面加了 CDN,缓存规则交给 CDN;图片:优先 WebP,大图走对象存储或图床,别让 8090 端口扛图片流量;
内存:2 GB 机器给 Halo 1 GB 上限够用;文章量特别大(几千篇)再往上加。
10.2 安全清单
8090 未对公网暴露(只监听 127.0.0.1)
PostgreSQL 端口未映射到宿主机
1Panel 面板端口已改、已设安全入口、已开 MFA
站点强制 HTTP → HTTPS,证书自动续签已开
管理员密码是强密码,邮箱真实可用
附件目录不在 Web 可直接访问的路径下(Halo 默认已处理)
每天自动备份,且备份不在同一块盘
系统和 1Panel 保持更新,Halo 升级前先看 Release Notes
十一、常见问题排查
特殊环境补充
纯 IPv6 服务器:域名加 AAAA 记录即可对外服务;证书务必用 DNS 验证,ACME 机构回连 IPv4 会失败。另外 Halo 容器默认走 IPv4 出口,纯 IPv6 机器上装主题、装插件、检查更新可能超时——需要给容器接一个带 IPv6 的网络,或在应用参数里加
JVM_OPTS=-Djava.net.preferIPv6Addresses=true并确保 Docker 已开启 IPv6。80 端口被运营商封掉:只能用 DNS 验证签证书,访问时手动带
https://(HTTP 跳转用不了)。服务器在国内:域名需要备案,否则 80/443 会被拦截;这一步绕不过去,提前办。
写在最后
整套流程跑下来其实只有五步:装 1Panel → 装 OpenResty 和 PostgreSQL → 装 Halo(填好 https 域名)→ 建站反代 + 签证书 → 打开 /console 初始化。
真正会卡住人的,从来不是「怎么装」,而是「顺序」和「那几个请求头」:
顺序:域名先解析、证书先签好、再初始化,能省掉一次重建和一堆混合内容的排查;
请求头:
X-Forwarded-Proto和Host这两条,决定了后台会不会莫名跳回 http;外部地址:填错一次,RSS、Sitemap、邮件链接全跟着错,改起来还要重建容器。
日常运维记住两句话就够了:升级前先备份,出问题先看 docker logs。
参考链接
Halo 官方文档 - 使用 1Panel 部署:https://docs.halo.run/getting-started/install/1panel
Halo 官方文档 - 使用 Docker Compose 部署(含 Nginx 反代示例):https://docs.halo.run/getting-started/install/docker-compose
Halo 官方文档 - 备份与恢复:https://docs.halo.run/guide/use/backup
1Panel 官方文档 - 应用商店安装 Halo:https://1panel.pro/docs/v2/user_manual/appstore/halo/
1Panel 官方文档 - 安装 1Panel:https://1panel.cn/docs/
Halo 版本发布说明:https://www.halo.run/archives

