Halo个人博客部署指南

Halo个人博客部署指南

_

Halo 个人博客部署指南


目录

  1. 引言:为什么选择 Halo
  2. 环境准备
  3. 安装步骤(核心)
  4. 基础配置
  5. 域名绑定与 HTTPS
  6. 常见问题排查 FAQ
  7. 结语

一、引言:为什么选择 Halo

如果你一直想拥有一个完全属于自己的网站——写技术笔记、记录生活、沉淀作品,那么 Halo 几乎是目前最适合个人开发者的选择之一。

Halo 是什么

Halo 是一款使用 Java(基于 Spring Boot 框架)编写的开源博客建站系统。所谓"开源",即源代码完全公开,任何人都可以免费使用、自由修改和二次分发。与 WordPress 等老牌系统相比,Halo 更年轻、更现代,也更适合中文用户。

核心优势

优势说明
轻量级单 Jar 包即可运行,最低 1 核 1GB 内存的小规格云服务器也能流畅部署
高性能基于 Spring Boot 构建,启动快、资源占用低
开源免费MIT/GPL 友好许可,无需任何授权费用
插件生态丰富评论、统计、友链、RSS 等功能均可通过插件按需安装
主题市场后台一键安装精美主题,无需手动上传文件
管理界面友好开箱即用的现代化后台(Console),Markdown 写作体验流畅

目标读者

本教程面向希望快速搭建个人站点的开发者或技术爱好者:你只需要会最基础的命令行操作(复制粘贴命令即可),无需任何 Java 开发经验。全文以当前主流的 Halo 2.x 版本为准进行讲解。

版本说明:Halo 1.x 是旧版本,仅需 JDK 11 即可运行,但官方已停止维护,不建议新项目使用。Halo 2.x 要求 JDK 17+,架构全面重构(插件化、主题市场等),本教程全部基于 2.x。


二、环境准备

2.1 前置条件清单

在开始之前,请确认你已经具备以下条件:

  • 一台可长期运行的机器:云服务器(阿里云 / 腾讯云 / 华为云等,推荐)或家用 Linux 服务器 / NAS(Network Attached Storage,家用网络存储设备,可兼作小型服务器)
  • 操作系统:推荐 Linux(Ubuntu 22.04+ / Debian 12+ / CentOS Stream 9 等主流发行版);Windows、macOS 同样支持,但生产环境首推 Linux
  • 一个可以远程连接服务器的终端工具(如 Windows Terminal、PuTTY、Xshell)
  • (可选)一个已备案或海外注册的域名,用于第五章的域名绑定与 HTTPS

2.2 硬件建议

配置项最低要求推荐配置
CPU1 核2 核及以上
内存1 GB(建议开启 Swap)2 GB 及以上(搭配 PostgreSQL 时尤其重要)
磁盘10 GB40 GB 及以上(图片附件较多时)
带宽1 Mbps3 Mbps 及以上

💡 内存只有 1GB 的轻量服务器也能跑 Halo,但如果同时运行数据库,建议配置 Swap 交换分区作为缓冲,否则可能出现内存不足(OOM)导致进程被系统杀掉,详见 FAQ 第 6.5 节

2.3 软件依赖:两种部署方式的区别

Halo 提供两种主流部署方式,依赖完全不同,请先选定一种再看对应小节:

部署方式需要安装 Java 吗需要安装的软件适用人群
方式一:Docker 部署(推荐)❌ 不需要Docker + Docker Compose绝大多数用户,升级/迁移最方便
方式二:Jar 包直接运行✅ 需要 JDK 17+JDK 17+(可选 systemd 做服务管理)有 Java 基础、希望精细控制进程的用户

检查 Java 版本(仅 Jar 包部署需要)

在终端执行:

java -version

正确输出示例(版本号 ≥ 17 即可):

openjdk version "17.0.11" 2024-04-16
OpenJDK Runtime Environment (build 17.0.11+9)

如果提示 command not found 或版本低于 17,请先安装 JDK 17+:

# Ubuntu / Debian
sudo apt update
sudo apt install -y openjdk-17-jdk

# CentOS / Rocky Linux
sudo dnf install -y java-17-openjdk

安装完成后再次执行 java -version 确认。

安装 Docker 与 Docker Compose(仅 Docker 部署需要)

# 一键安装 Docker(官方脚本,适用于主流 Linux 发行版)
# 该方式会执行远程官方脚本,请确认来源为 get.docker.com 官方域名后再运行
curl -fsSL https://get.docker.com | sh

# 启动并设置开机自启
sudo systemctl enable --now docker

# 验证安装
docker --version
docker compose version   # 新版 Docker 已内置 compose 子命令

Docker 是一种容器技术,可以把 Halo 及其运行环境整体打包,避免"在我电脑上能跑"的依赖问题;Docker Compose 则是用于编排多个容器(例如 Halo + 数据库)的配置工具。

2.4 检查端口占用

Halo 默认使用 8090 端口。部署前先确认该端口未被占用:

sudo ss -lntp | grep 8090

无输出即代表端口空闲。若已被占用,处理方式见 FAQ 第 6.1 节


三、安装步骤(核心)

本章提供两种部署方式,任选其一即可,新手强烈建议选方式一(Docker)。

方式一:Docker 部署(推荐)

3.1 使用 docker run 快速启动

Halo 官方在 Docker Hub 发布的镜像为 halohub/halo:2.x。以下以 2.20 为例,实际请使用 Docker Hub 上的最新稳定 tag(也可参考 官方文档):

⚠️ 安全提示-p 8090:8090 默认绑定宿主机的所有网卡,一旦云厂商安全组放行了 8090 端口,你的初始化页面和 /console 后台即对全网可见,可能被他人抢先完成初始化或对后台账号发起暴力破解。请务必在部署后第一时间完成初始化并设置强密码(详见 4.1 节)。

docker run \
  -d \
  --name halo \
  -p 8090:8090 \
  -v ~/.halo2:/root/.halo2 \
  --restart=unless-stopped \
  halohub/halo:2.20

关键参数说明

  • -d:后台运行容器(detach 模式),不占用当前终端。
  • --name halo:容器命名为 halo,方便后续用 docker logs halo 等命令管理。
  • -v ~/.halo2:/root/.halo2:这是最重要的一行。Halo 的所有数据(文章、附件、配置、内置 H2 数据库文件,H2 是一种无需额外安装的嵌入式轻量数据库)都保存在容器内 /root/.halo2,挂载后即使删除容器,数据依然保留在宿主机的 ~/.halo2 目录中。备份博客 = 备份这个目录
  • -p 8090:8090:端口映射,宿主机 8090 转发到容器内 8090。如果宿主机 8090 端口被占用,可改为 -p 9090:8090(仅改冒号左侧),之后通过 http://IP:9090 访问。
  • --restart=unless-stopped:重启策略,除手动停止外,开机或异常退出后自动拉起容器。
  • halohub/halo:2.20:使用的官方镜像及版本标签。

💡 官方镜像内的进程以 root 身份运行,并且通过 -v 挂载了宿主机目录,请注意宿主机 ~/.halo2 目录的访问权限,避免被其他本地用户读取。

启动后执行以下命令确认容器正常运行:

docker ps            # 查看容器状态,STATUS 应为 Up
docker logs -f halo  # 实时查看启动日志,看到 Halo started 字样即成功,按 Ctrl+C 退出

3.2 使用 Docker Compose 编排 Halo + PostgreSQL(生产推荐)

Halo 2.x 内置 H2 数据库,开箱即用,适合个人轻量使用;但生产环境更推荐 PostgreSQL 或 MySQL,稳定性和性能更有保障。下面给出 Halo + PostgreSQL 的完整编排示例。

数据库是用来存储文章、用户、评论等数据的组件;Halo 通过环境变量决定连接哪个数据库,切换时无需修改代码

在任意目录(例如 ~/halo)创建 docker-compose.yml 文件:

services:
  halo:
    image: halohub/halo:2.20          # 官方镜像,以下以 2.20 为例,实际请使用 Docker Hub 上的最新稳定 tag
    container_name: halo
    restart: unless-stopped            # 重启策略:开机与异常退出自动拉起
    depends_on:
      halo-db:                         # 依赖数据库服务先启动
        condition: service_healthy
    networks:
      - halo-network
    volumes:
      - ~/.halo2:/root/.halo2          # 数据持久化目录(关键:备份即备份此目录)
    environment:
      - JVM_OPTS=-Xmx512m              # JVM 最大堆内存,1GB 内存的服务器建议 256m~512m
      - SPRING_R2DBC_URL=r2dbc:pool:postgresql://halo-db:5432/halo
      # ↑ 数据库连接地址(关键配置):r2dbc:pool:postgresql:// 为完整的驱动 + 连接池前缀,主机名使用 Compose 内部服务名 halo-db
      - SPRING_SQL_INIT_PLATFORM=postgresql
      # ↑ 告知 Halo 按 PostgreSQL 方言初始化数据库结构,与 R2DBC URL 配套
      - SPRING_R2DBC_USERNAME=halo
      # ↑ 数据库用户名(关键配置):必须与下方 POSTGRES_USER 一致
      - SPRING_R2DBC_PASSWORD=你的强密码
      # ↑ 数据库密码(关键配置):必须与下方 POSTGRES_PASSWORD 一致,请使用强密码
    ports:
      - "8090:8090"                    # 对外暴露端口,被占用时仅修改冒号左侧即可

  halo-db:
    image: postgres:16                 # PostgreSQL 数据库
    container_name: halo-db
    restart: unless-stopped
    networks:
      - halo-network
    volumes:
      - ./db-data:/var/lib/postgresql/data   # 数据库数据持久化(关键:同样需要备份)
    environment:
      - POSTGRES_PASSWORD=你的强密码    # 数据库密码,与上方 SPRING_R2DBC_PASSWORD 保持一致
      - POSTGRES_USER=halo             # 数据库用户
      - POSTGRES_DB=halo               # 数据库名
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "halo", "-d", "halo"]
      interval: 10s
      timeout: 5s
      retries: 5

networks:
  halo-network:                        # 自定义网络:让 halo 与 halo-db 互相隔离并可按服务名访问
    driver: bridge

volumes: {}                            # 本示例使用宿主机路径挂载,如需命名卷可在此声明

关键配置项加粗说明

  1. SPRING_R2DBC_URL / SPRING_R2DBC_USERNAME / SPRING_R2DBC_PASSWORD:Halo 通过这三个环境变量切换到 PostgreSQL。若改用 MySQL,则替换为对应的 SPRING_DATASOURCE_URLSPRING_DATASOURCE_USERNAMESPRING_DATASOURCE_PASSWORD(具体变量名以官方文档为准)。不配置任何数据库变量时,Halo 默认使用内置 H2 数据库
  2. 两个 volumes 挂载~/.halo2(应用数据)与 ./db-data(数据库数据)共同构成完整备份范围。
  3. depends_on + healthcheck:确保数据库真正就绪后 Halo 才启动,避免启动时连接失败。
  4. networks: halo-network:自定义 bridge 网络使容器间可通过服务名(halo-db)互相访问,且与宿主机其他容器隔离。

⚠️ 密码安全提示:上方 compose 文件中是明文密码,仅为方便演示。生产环境建议将密码移入同目录的 .env 文件(例如写成 POSTGRES_PASSWORD=${DB_PASSWORD}SPRING_R2DBC_PASSWORD=${DB_PASSWORD},Compose 会自动读取),并执行 chmod 600 .env 限制读取权限。切勿将含密码的 compose / .env 文件粘贴到论坛、截图或提交到公开代码仓库

启动编排:

cd ~/halo
docker compose up -d          # 后台启动所有服务
docker compose ps             # 确认两个容器均为 Up (healthy) / Up 状态
docker compose logs -f halo   # 跟踪 Halo 启动日志

若你暂时不想折腾数据库,也可以删掉 halo-db 服务及 halo 服务中的三个 SPRING_R2DBC_* 环境变量与 depends_on,直接使用内置 H2 数据库,后续再通过备份迁移。

方式二:Jar 包直接运行

3.3 下载官方 Jar 包

  1. 打开 Halo 官方发布页:https://github.com/halo-dev/halo/releases
  2. 找到最新的 2.x 稳定版本(避免选择带 -rc-beta 后缀的预览版)
  3. 在发布页的 Assets 区域下载以 .jar 结尾的构建产物(例如 halo-2.20.0.jar,注意实际文件名包含完整补丁号)

服务器上可以使用 wget 直接下载(请将链接替换为发布页中的实际地址):

# 创建工作目录
mkdir -p /opt/halo && cd /opt/halo

# 下载 jar 包(以下 URL 请以 GitHub Releases 页面实际链接为准)
wget https://github.com/halo-dev/halo/releases/download/v2.20.0/halo-2.20.0.jar -O halo.jar

3.4 前台启动验证

先手动启动一次,确认环境没有问题:

cd /opt/halo
java -jar halo.jar

看到类似 Halo started successfully 的日志后,浏览器访问 http://服务器IP:8090 应能看到初始化页面。验证成功后按 Ctrl+C 停止,接下来配置 systemd 实现后台常驻。

💡 如需更换端口或指定数据目录,可通过启动参数实现,例如:java -jar halo.jar --server.port=9090 --halo.work-dir=/data/halo2

3.5 使用 systemd 管理后台服务

systemd 是 Linux 的系统服务管理器,可以把 Halo 注册为系统服务,实现开机自启、崩溃自动重启。

创建服务文件 /etc/systemd/system/halo.service

sudo nano /etc/systemd/system/halo.service

写入以下内容(注意将 User 替换为你的实际运行用户,jar 路径与 WorkingDirectory 保持一致):

[Unit]
Description=Halo Blog Platform
Documentation=https://docs.halo.run
After=network-online.target
Wants=network-online.target

[Service]
User=www                                    # 运行用户(建议新建非 root 用户)
Group=www
WorkingDirectory=/opt/halo                  # 工作目录:Halo 默认数据目录 ~/.halo2 将位于该用户家目录下
ExecStart=/usr/bin/java -Xmx512m -jar /opt/halo/halo.jar   # 启动命令(关键)
Restart=on-failure                          # 异常退出后自动重启(关键)
RestartSec=5
SuccessExitStatus=143                       # 正常停止信号不计为失败
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target

重载 systemd 配置并管理服务:

sudo systemctl daemon-reload        # 重载服务文件
sudo systemctl enable halo          # 设置开机自启
sudo systemctl start halo           # 立即启动
sudo systemctl status halo          # 查看运行状态(显示 active (running) 即成功)

日常管理速查:

sudo systemctl stop halo            # 停止
sudo systemctl restart halo         # 重启
journalctl -u halo -f               # 实时查看日志

⚠️ Jar 包方式下,数据默认保存在运行用户的 ~/.halo2 目录,备份策略与 Docker 方式一致。


四、基础配置

4.1 首次访问与初始化

安装完成后,在浏览器打开:

http://你的服务器IP:8090

首次访问会自动进入初始化界面,需要创建管理员账号:

  1. 填写站点名称(即博客名字,之后可在后台修改)
  2. 填写管理员用户名(登录后台使用)
  3. 填写邮箱密码(密码建议 12 位以上,含大小写与数字)
  4. 点击初始化按钮,等待跳转

⚠️ 安全提示:初始化时必须设置强密码(建议 12 位以上、含大小写字母与数字)。公网 HTTP 阶段请避免在后台进行敏感操作,建议尽快完成第五章的域名与 HTTPS 配置。

初始化完成后,前台地址为 http://IP:8090后台(Console)地址固定为 http://IP:8090/console。所谓后台,就是管理文章、主题、插件的控制中心。

4.2 登录后台

访问 http://你的服务器IP:8090/console,输入刚才创建的用户名与密码登录:

4.3 设置站点基本信息

登录后按以下路径完成基础设置:左侧菜单 → 系统设置 → 基本设置

逐项操作:

  1. 站点名称:填写博客名称,例如"小明的技术笔记"
  2. 站点副标题:一句话介绍,例如"记录学习与生活"
  3. Logo 上传:点击上传区域选择本地图片(建议正方形 PNG,512×512 以上),上传后即时预览
  4. 站点地址(URL):暂时保持默认,第五章绑定域名后务必回来改为正式域名,否则附件链接可能仍指向 IP
  5. 点击右上角保存

4.4 选择并安装主题

Halo 的主题(决定博客前台长相)可以直接在后台市场安装,无需手动下载上传:左侧菜单 → 外观 → 主题,切换到主题市场标签页浏览并点击安装

适合新手的热门推荐:

主题特点
Theme EarthHalo 2.x 官方默认主题,稳定、文档齐全,建议先用它熟悉流程
Dream现代简约风,深色模式支持良好
Joe功能丰富、可定制项多,社区活跃

安装完成后点击启用,再访问 http://IP:8090 即可看到新外观。主题的细节(首页布局、配色、侧边栏)通常在 外观 → 主题设置 中调整。

4.5 写下第一篇文章

路径:左侧菜单 → 文章 → 新建文章。编辑器支持标准 Markdown 语法,写完点击发布,前台首页即可看到。

4.6 建议顺手安装的插件

插件市场 中,以下插件对新手非常实用:评论组件、站点统计、RSS 订阅、友情链接管理。安装与启用方式和主题一样,一键完成。

💡 完成本章配置后,建议现在就先把 ~/.halo2 目录备份一次(Jar 包方式为运行用户的家目录),养成备份习惯。


五、域名绑定与 HTTPS

用 IP 访问虽然可行,但既难记又无法启用 HTTPS 加密。本章带你完成域名绑定与证书配置。

名词解释

  • DNS 解析:把人类可读的域名(如 blog.example.com)翻译成服务器 IP 地址的系统,相当于互联网的"电话簿"。
  • 反向代理:由一台服务器(这里用 Nginx/Caddy)统一接收外部请求,再转发给内网中真正提供服务的程序(Halo 的 8090 端口),对外隐藏真实端口与架构。
  • SSL 证书:让网站启用 HTTPS 加密传输的数字证书,浏览器会显示安全锁标志;Let's Encrypt 是最常用的免费证书颁发机构。

5.1 DNS 解析

  1. 登录你的域名服务商控制台(阿里云 DNS、腾讯云 DNSPod、Cloudflare 等)
  2. 添加一条 A 记录(把域名直接指向某个 IP 地址的 DNS 记录类型):
    • 主机记录blog(或直接 @ 表示主域名)
    • 记录类型A
    • 记录值:你的服务器公网 IP
    • TTL:默认 600 即可
  3. 等待解析生效(通常几分钟内),可用以下命令验证:
ping blog.example.com      # 返回的 IP 应为你的服务器 IP

若你的博客托管在对象存储或负载均衡等场景(个人博客一般用不到),也可使用 CNAME 记录指向服务商提供的域名;个人服务器场景一律使用 A 记录最简单。

解析生效后,访问 http://blog.example.com:8090 应能看到博客。接下来的目标是去掉 :8090 并启用 HTTPS。

5.2 安装 Nginx

# Ubuntu / Debian
sudo apt update && sudo apt install -y nginx

# CentOS / Rocky
sudo dnf install -y nginx

sudo systemctl enable --now nginx

5.3 Nginx 反向代理配置(HTTP 阶段)

创建站点配置文件 /etc/nginx/conf.d/halo.conf

server {
    listen 80;                            # 监听 HTTP 80 端口(证书申请阶段需要)
    server_name blog.example.com;         # 替换为你的域名(关键配置)

    # 证书验证路径:供 5.4 节 acme.sh webroot 方式使用,务必放在 location / 之前(关键配置)
    location ^~ /.well-known/acme-challenge/ {
        root /var/www/html;
    }

    location / {
        proxy_pass http://127.0.0.1:8090;          # 转发到本机 Halo 服务(关键配置)
        proxy_set_header Host $host;               # 传递原始域名,Halo 依赖它生成正确链接
        proxy_set_header X-Real-IP $remote_addr;   # 传递真实客户端 IP
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme; # 传递协议类型 http/https(关键配置)
        proxy_http_version 1.1;
    }
}

关键配置项说明

  • proxy_pass http://127.0.0.1:8090:所有请求转发给本机 Halo;若 Halo 部署在其他机器或改了端口,在此调整。
  • X-Forwarded-Proto:让 Halo 知道原始请求是 HTTP 还是 HTTPS,否则启用 HTTPS 后可能出现链接与跳转异常。

测试并重载配置:

sudo nginx -t                 # 语法检查,显示 syntax is ok 再继续
sudo systemctl reload nginx   # 平滑重载,不中断现有连接

此时访问 http://blog.example.com(无需端口)即可看到博客。

5.4 使用 acme.sh 申请 Let's Encrypt 证书

推荐使用 acme.sh(纯 Shell 脚本,自动续期)。

⚠️ 请使用 root(或 sudo -i 切换到 root)身份安装与使用 acme.sh:后续 --reloadcmd "systemctl reload nginx" 需要 root 权限,否则证书自动续期时会因无权重载 Nginx 而静默失败,新证书不生效。

以下演示 webroot 方式(验证目录已在上一步 5.3 节配置好 /.well-known/acme-challenge/ 路径,无需停止 Nginx):

# 安装 acme.sh(将邮箱替换为你自己的,用于续期通知)
# 该方式会执行远程官方脚本,请确认来源为 get.acme.sh 官方域名后再运行
curl https://get.acme.sh | sh -s email=you@example.com

# 签发证书:-w 指定验证目录,与 5.3 节配置的 root 路径保持一致
~/.acme.sh/acme.sh --issue -d blog.example.com -w /var/www/html

如果站点根目录不便使用,也可改用 standalone 方式(acme.sh 临时占用 80 端口完成验证,需先停止 Nginx):

sudo systemctl stop nginx
sudo ~/.acme.sh/acme.sh --issue -d blog.example.com --standalone
sudo systemctl start nginx

💡 无论签发成功与否,请务必确认已重新执行 sudo systemctl start nginx 恢复站点访问。

证书签发成功后,将证书安装(部署)到 Nginx 使用的路径,并配置自动重载:

sudo ~/.acme.sh/acme.sh --install-cert -d blog.example.com \
  --key-file       /etc/nginx/ssl/blog.example.com.key \
  --fullchain-file /etc/nginx/ssl/blog.example.com.pem \
  --reloadcmd      "systemctl reload nginx"

# 收紧私钥权限,仅允许 root 读取
sudo chmod 600 /etc/nginx/ssl/blog.example.com.key

🔒 私钥(.key 文件)等同于站点的身份凭证,一旦泄露他人可冒充你的站点,禁止上传、分享或提交到代码仓库

最后验证一次续期链路是否可用(模拟定时任务触发,无报错即正常):

sudo ~/.acme.sh/acme.sh --cron

证书有效期 90 天,acme.sh 安装时已自动写入 crontab(Linux 的定时任务调度器)定时续期,--reloadcmd 会在续期后自动执行 systemctl reload nginx,无需人工干预。

也可以使用 certbot 作为替代:sudo apt install -y certbot python3-certbot-nginx && sudo certbot --nginx -d blog.example.com,certbot 会自动改写 Nginx 配置。

5.5 Nginx 启用 HTTPS

将 5.3 节的配置升级为完整的 HTTP 跳转 + HTTPS 站点:

# HTTP → HTTPS 强制跳转
server {
    listen 80;
    server_name blog.example.com;
    return 301 https://$host$request_uri;
}

# HTTPS 主站点
server {
    listen 443 ssl;
    server_name blog.example.com;                          # 替换为你的域名

    ssl_certificate     /etc/nginx/ssl/blog.example.com.pem;   # 证书文件(acme.sh 部署的)
    ssl_certificate_key /etc/nginx/ssl/blog.example.com.key;   # 私钥文件
    ssl_protocols TLSv1.2 TLSv1.3;

    location / {
        proxy_pass http://127.0.0.1:8090;
        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_http_version 1.1;
    }
}
sudo nginx -t && sudo systemctl reload nginx

最后回到 Halo 后台:系统设置 → 基本设置,将站点地址改为 https://blog.example.com,保存即可。浏览器地址栏出现安全锁,说明 HTTPS 配置成功。

5.6 替代方案:Caddy 极简自动 HTTPS

如果你不想手动管理证书,Caddy 是更省心的选择——它默认自动申请并续期 Let's Encrypt 证书,配置只需两行。

# Ubuntu / Debian 安装 Caddy(官方 apt 源)
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install -y caddy

编辑 /etc/caddy/Caddyfile

blog.example.com {
    reverse_proxy 127.0.0.1:8090
}
sudo systemctl reload caddy

首次访问域名时,Caddy 会自动完成证书申请(确保域名解析已生效且 80/443 端口已在安全组放行)。Nginx 与 Caddy 二选一即可,不要同时监听 80/443 端口。


六、常见问题排查 FAQ

6.1 端口 8090 被占用

现象:启动时报 Port 8090 was already in use,或容器端口映射失败。

排查思路:先确认谁占用了端口。

sudo ss -lntp | grep 8090        # 查看占用进程名与 PID
sudo netstat -tunlp | grep 8090  # 备选命令

解决方案

  1. 若占用进程可停止(如残留的旧 Halo 实例),sudo kill <PID> 后重启即可
  2. 若必须共存,则更换端口
    • Docker:-p 9090:8090(仅改冒号左侧),Compose 中改 ports: - "9090:8090"
    • Jar 包:java -jar halo.jar --server.port=9090,并同步修改 systemd 文件中的 ExecStart 后执行 sudo systemctl daemon-reload && sudo systemctl restart halo
    • 使用 Nginx/Caddy 反代时,同步把 proxy_pass / reverse_proxy 指向新端口

6.2 数据库连接失败(PostgreSQL / MySQL)

现象:Halo 启动日志报 Failed to obtain database connection 等连接异常。

排查思路:按"配置 → 网络 → 密码"顺序逐一核对。

  1. 核对环境变量SPRING_R2DBC_URL 中的主机名、端口、库名必须与数据库服务一致;Compose 内必须使用服务名(如 halo-db)而非 localhost
  2. 核对账号密码SPRING_R2DBC_USERNAME/PASSWORD 必须与 POSTGRES_USER/POSTGRES_PASSWORD 完全一致,注意特殊字符(如 $#)在 Compose 中需要转义
  3. 验证网络连通:进入 Halo 容器测试数据库端口
# 在数据库容器侧验证连通性(Halo 官方镜像未预装 netcat,故从数据库侧检查)
docker compose exec halo-db pg_isready -U halo -d halo   # 显示 accepting connections 即就绪
docker compose logs halo-db                              # 查看数据库侧日志与报错

解决方案:修正配置后执行 docker compose down && docker compose up -d 重建;若数据库已初始化过账号,修改密码需要同时处理数据库内已有用户(或清空 db-data 重新初始化,注意会丢失数据)。

6.3 目录权限问题(挂载卷 / 非 root 运行)

现象:容器启动即退出,日志出现 Permission denied、无法写入 /root/.halo2 等错误;或 Jar 包方式下 systemd 服务启动失败。

排查思路:挂载目录的属主/权限与容器内运行用户不匹配是最常见原因。

ls -ld ~/.halo2        # 查看目录属主与权限
docker logs halo       # 确认具体报错路径

解决方案

  1. 调整挂载目录权限(简单场景):
sudo chown -R 1000:1000 ~/.halo2   # 或将属主改为当前运行用户:chown -R $USER ~/.halo2
sudo chmod -R u+rwX ~/.halo2
  1. systemd 方式下,确保 halo.service 中的 UserWorkingDirectory 与其家目录下的 .halo2 有读写权限;生产环境强烈建议以非 root 用户运行,降低安全风险
  2. 修改后重启容器或服务:docker compose restart halo / sudo systemctl restart halo

6.4 云服务器安全组未放行端口

现象:服务器本地 curl http://127.0.0.1:8090 正常,但外网浏览器始终无法访问(连接超时)。

排查思路:请求根本没到达服务器,通常是云厂商安全组(云平台层面的虚拟防火墙)或系统防火墙未放行端口。注意:绑定域名并使用 Nginx/Caddy 后,对外暴露的是 80/443,而不是 8090。

解决方案

  1. 登录云厂商控制台(阿里云/腾讯云/华为云等),在实例的安全组中添加入方向规则:
    • 仅用 IP 访问 Halo:放行 TCP 8090
    • 已配置 Nginx/Caddy:强烈建议在安全组中删除或限制 8090 的放行规则,仅保留 TCP 80 与 443;若使用 docker run 部署,可进一步将端口映射改为仅绑定本机回环地址 -p 127.0.0.1:8090:8090,这样 8090 完全不对公网暴露,只能由本机 Nginx 反代访问
  2. 检查系统防火墙:
sudo ufw status                  # Ubuntu:如已启用,执行 sudo ufw allow 8090/tcp
sudo firewall-cmd --list-ports   # CentOS:如已启用,执行 sudo firewall-cmd --add-port=8090/tcp --permanent && sudo firewall-cmd --reload
  1. 规则生效后等待约 10 秒再次访问验证

6.5 内存不足导致容器被杀(OOM)

现象:博客运行一段时间后突然无法访问,docker ps 显示容器反复重启或已退出;docker inspect halo 中可见 OOMKilled: true;系统日志出现 Out of memory: Killed process ... java

排查思路:1GB 内存的服务器同时运行 Halo + PostgreSQL 时容易触发内核 OOM Killer。

free -h                          # 查看可用内存与 Swap
docker inspect halo | grep -i oom    # 确认是否 OOMKilled
dmesg | grep -i "killed process" | tail   # 查看系统 OOM 记录

解决方案

  1. 限制并降低 JVM 堆内存:Compose 中设置 JVM_OPTS=-Xmx256m(或 512m),避免 Java 无限申请内存
  2. 开启 Swap(1GB 服务器的救命稻草)。执行前先用 swapon --show 确认是否已有 Swap,若已配置请跳过本步:
swapon --show                    # 先检查:有输出则说明已存在 Swap,跳过以下步骤
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile && sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
  1. 如预算允许,升级至 2GB+ 内存是最根本的解法;也可考虑只使用内置 H2 数据库以减少一个常驻进程

6.6 容器陷入重启循环

现象docker ps 中 STATUS 显示 Restarting (1) x seconds ago,反复重启无法稳定运行。

排查思路:容器是"启动 → 崩溃 → 被 restart 策略拉起 → 再崩溃"的循环,关键是看崩溃前的日志

docker logs --tail 200 halo      # 查看最近 200 行日志
docker logs -f halo              # 实时跟踪一次完整的启动崩溃过程
docker inspect halo | grep -A3 State   # 查看退出码 ExitCode

解决方案:按日志报错对症处理——

  • 端口冲突 → 参考 6.1
  • 数据库连接失败 → 参考 6.2
  • 权限拒绝 → 参考 6.3
  • 镜像版本损坏或标签异常 → 删除本地镜像重新拉取:docker rmi halohub/halo:2.20 && docker compose pull
  • 配置实在无法定位时,可临时移除 --restart 策略让容器"停住",便于从容排查

💡 通用排查三件套:docker ps(看状态)、docker logs(看日志)、docker inspect(看配置与退出码)。绝大多数容器问题都能靠这三条命令定位。


七、结语

恭喜你走完了全程!回顾一下我们完成的事情:

  • 环境准备:确认了系统、硬件与依赖——Docker 部署只需 Docker,Jar 包部署需要 JDK 17+
  • 安装部署:通过 docker run / Docker Compose(Halo + PostgreSQL)或 Jar 包 + systemd 启动了 Halo,数据统一持久化在 ~/.halo2
  • 基础配置:在 http://IP:8090 完成初始化,在 /console 后台设置了站点信息并安装了主题
  • 域名与 HTTPS:通过 DNS 解析 + Nginx 反向代理 + Let's Encrypt 证书(或 Caddy 自动 HTTPS)让博客拥有了安全锁

接下来最值得做的三件事:

  1. 备份:把 ~/.halo2(使用数据库时还有数据目录)纳入定期备份计划,数据才是博客最值钱的资产
  2. 写作:技术博客的生命力在于持续输出,先写三篇再谈优化
  3. 探索生态:在后台的插件市场与主题市场逛逛,按需增强你的站点

工具只是起点,内容才是灵魂。现在就打开 http://你的域名/console,写下第一篇文章吧!


📌 本文基于 Halo 2.x 撰写,镜像版本、环境变量等细节可能随官方迭代变化,实际操作请以 Halo 官方文档GitHub Releases 为准。

把笔记搬回自己服务器——Memos 部署手记 2026-08-13

评论区