Halo 是一款开源的现代化博客系统(CMS),基于 Java 开发,主打轻量、易用和高扩展性,适合个人博客、自媒体或小型内容站点搭建。
核心特点
开源免费:基于 MIT 协议,源代码公开,无商业授权限制。
简洁易用:提供直观的后台管理界面,支持文章编辑、媒体管理、评论互动等核心功能,新手易上手。
主题与插件生态:支持自定义主题(通过模板文件修改样式)和插件扩展(如 SEO、社交分享等功能),灵活适配不同需求。
多平台部署:可通过 JAR 包、Docker、K8s 等方式部署,兼容 Linux、Windows、macOS 系统。
数据安全:支持多种数据库(MySQL、H2 等),内置数据备份功能,保障内容安全。
Halo 强调「以内容为中心」,兼顾技术灵活性和用户体验,是个人开发者和内容创作者搭建独立站点的热门选择。
事前准备
专属用户
## useradd 命令选项
## -r 创建系统用户 / 组
## -g 用户初始登录组的组名或编号。组名必须存在。组号必须引用已经存在的组
## -s /sbin/nologin 禁止用户登录 Shell
[root@localhost ~]# groupadd test001
[root@localhost ~]# useradd -r -g test001-s /sbin/nologin test001创建站点目录
[root@localhost ~]# mkdir -p /var/www/html/halo/app
[root@localhost ~]# mkdir -p /var/www/html/halo/.halo2安装 Nginx
安装依赖包
## 安装依赖包
[root@localhost ~]# yum groupinstall -y "Development tools"
[root@localhost ~]# yum install -y gcc gcc-c++ autoconf automake
[root@localhost ~]# yum install -y openssl-devel expat-devel
[root@localhost ~]# yum install -y libev libev-devel zlib-devel
## 编译安装pcre
[root@localhost ~]# wget https://zenlayer.dl.sourceforge.net/project/pcre/pcre/8.45/pcre-8.45.tar.gz?viasf=1
[root@localhost ~]# tar zxvf pcre-8.44.tar.gz -C /usr/src/
[root@localhost ~]# cd /usr/src/pcre-8.44/
[root@localhost pcre-8.44]# ./configure --prefix=/usr/local/pcre
[root@localhost pcre-8.44]# make -j 20 && make install
## 编译安装apr
[root@localhost ~]# wget https://dlcdn.apache.org/apr/apr-1.7.6.tar.gz
[root@localhost ~]# tar zxvf apr-1.7.4.tar.gz -C /usr/src/
[root@localhost ~]# cd /usr/src/apr-1.7.4/
#> 【 报错 】rm: cannot remove ‘libtoolT’: No such file or directory
#> 修改 configure 文件
[root@localhost apr-1.7.4]# sed -i 's|$RM "$cfgfile"|# $RM "$cfgfile"|g' ./configure
#> 继续编译
[root@localhost apr-1.7.4]# ./configure --prefix=/usr/local/apr
[root@localhost apr-1.7.4]# make -j 20 && make install
## 编译安装apr-util
[root@localhost ~]# wget https://dlcdn.apache.org/apr/apr-util-1.6.3.tar.gz
[root@localhost ~]# tar -zxvf apr-util-1.6.3.tar.gz -C /usr/src/
[root@localhost ~]# cd /usr/src/apr-util-1.6.3/
[root@localhost apr-util-1.6.3]# ./configure --prefix=/usr/local/apr-util --with-apr=/usr/local/apr
[root@localhost apr-util-1.6.3]# make -j 20 && make install
## 编译安装nghttp2
#> 安装依赖包
[root@localhost ~]# dnf install -y jemalloc jemalloc-devel c-ares-devel
#> 继续安装
[root@localhost ~]# wget https://github.com/nghttp2/nghttp2/releases/download/v1.65.0/nghttp2-1.65.0.tar.gz
[root@localhost ~]# tar -zxvf nghttp2-1.65.0.tar.gz -C /usr/src/
[root@localhost ~]# cd /usr/src/nghttp2-1.65.0/
[root@localhost nghttp2-1.65.0]# ./configure --prefix=/usr/local/nghttp2
[root@localhost nghttp2-1.65.0]# make -j 20 && make install
#> 配置环境变量
[root@localhost nghttp2-1.65.0]# cat >> /etc/profile << EOF
# Nghttp2 Start
export PKG_CONFIG_PATH=/usr/local/nghttp2/lib/pkgconfig:$PKG_CONFIG_PATH
# Nghttp2 End
EOF
#> 加载全局环境变量
[root@localhost nghttp2-1.65.0]# source /etc/profile
## 安装gd-devel
[root@localhost ~]# yum install -y gd-devel下载解压
## Nginx 官方下载:https://nginx.org/en/download.html
## 下载 nginx-1.27.3.tar.gz 安装包,但下载的时候很慢
[root@localhost ~]# wget http://172.16.28.200/Source/nginx/nginx-1.28.0.tar.gz
## 解压 nginx 到指定目录,,假设 nginx 包在当前目录
[root@localhost ~]# tar -zxvf nginx-1.28.0.tar.gz -C /usr/src/配置选项
## 配置 nginx
## 参数详解:https://blog.csdn.net/lizhengyu891231/article/details/136477249
## --prefix=<path> Nginx 安装根目录
## --sbin-path=<path> Nginx 二进制可执行文件目录
## --conf-path=<path> Nginx 主配置文件目录
## --pid-path=<path> 存储Nginx主进程进程号,可随时改变文件名
## --error-log-path=<path> 主错误、警告、诊断文件名称,可随时改变文件名
## --http-log-path=<path> Nginx 服务日志文件,可随时改变文件名
## --lock-path=<path> 共享存储器互斥锁文件所在目录
## --user=<name> Nginx 进程运行用户
## --group=<groupname> Nginx 进程运行用户组
## --with-http_ssl_module 启用HTTPS支持,能够处理SSL/TLS加密连接
## --with-http_rewrite_module 会报错,这个不要
## 可以通过 nginx -V 查看历史编译选项
[root@localhost ~]# cd /usr/src/nginx-1.28.0/
[root@localhost nginx-1.28.0]# ./configure --prefix=/usr/local/nginx --sbin-path=/usr/sbin/nginx --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx/nginx.pid --lock-path=/var/lock/nginx.lock --user=test001--group=test001--with-http_ssl_module --with-pcre --with-http_stub_status_module --with-http_v2_module --with-http_image_filter_module --with-http_gzip_static_module编译安装
## 编译 && 安装
[root@localhost nginx-1.28.0]# make -j $(nproc) && make install
## 编译异常,清理编译
[root@localhost nginx-1.28.0]# make clean all配置文件
nginx.conf
[root@localhost ~]# vim /etc/nginx/nginx.confuser test001 test001; # 运行服务的用户名和用户组
worker_processes auto; # 自动根据CPU核心数设置工作进程
worker_cpu_affinity auto; # 进程与CPU核心绑定,减少切换
# 并发连接限制:避免超CPU/内存承载能力
events {
worker_connections 1024; # 每个工作进程的最大连接数(2核建议1024 - 2048)
use epoll; # 高效事件模型(Linux推荐)
}
http {
# 引入外部配置文件
include mime.types;
include /etc/nginx/conf.d/halo.conf;
# 默认文件类型
default_type application/octet-stream;
# http2 可以放在 http 进行全局配置,也可以放在 server 针对特定服务器进行局部配置;
# 新版本的 http2 如以下配置,不再会有报警信息;
# large_client_header_buffers 替换原有的 http2_max_field_size 和 http2_max_header_size
http2 on; # 启用 HTTP/2 协议,优化并发请求处理,降低移动端延迟;
large_client_header_buffers 4 16k; # 4 个缓冲区,单个缓冲区16k(建议值,可根据业务调整);
# 添加这一行来为.mjs文件设置正确的MIME类型
types {
text/javascript mjs;
}
# 日志格式:精简日志,只记录关键信息(减少 I/O 开销)
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
# 高效传输设置
sendfile on; # 启用零拷贝传输(加速静态文件发送)
tcp_nopush on; # 与 sendfile 配合,减少网络包数量
tcp_nodelay on; # 小数据块立即发送(适合动态内容)
keepalive_timeout 65; # 长连接超时时间(平衡连接数和延迟)
keepalive_requests 100; # 单个长连接最多处理100个请求后关闭
types_hash_max_size 2048; # 提高 MIME 类型哈希表效率
# Gzip 压缩:减少传输数据量(只压缩文本类内容)
gzip on;
gzip_vary on; # 告诉客户端有压缩版本
gzip_proxied any; # 对代理请求也压缩
gzip_comp_level 5; # 压缩级别(1-9,5 是平衡值)
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
}halo.conf
upstream halo {
server 127.0.0.1:8090;
}
server {
listen 80;
listen [::]:80;
server_name myblog.example.com;
# 禁止检测 Nginx HTTP 服务器
server_tokens off;
# 强制将 HTTP 转向 HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
# http2 on; # 放在 http 进行全局配置
server_name myblog.example.com;
# 根目录,指向 halo 安装位置,需根据实际情况修改;
root /var/www/html/halo;
# SSL 配置
ssl_certificate /etc/letsencrypt/live/myblog.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/myblog.example.com/privkey.pem;
# 禁止检测 Nginx HTTP 服务器
server_tokens off;
client_max_body_size 1024m;
location / {
proxy_pass http://halo;
proxy_set_header HOST $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}配置服务
服务文件
[root@localhost ~]# cat >> /etc/systemd/system/nginx.service << EOF
[Unit]
Description=Nginx Server
After=network.target
[Service]
Type=forking
RuntimeDirectory=nginx
PIDFile=/var/run/nginx/nginx.pid
ExecStartPre=/usr/sbin/nginx -t -q
ExecStart=/usr/sbin/nginx
ExecReload=/usr/sbin/nginx -s reload
ExecStop=-/sbin/start-stop-daemon --quiet --stop --retry QUIT/5 --pidfile /var/run/nginx/nginx.pid
TimeoutStopSec=5
KillMode=mixed
[Install]
WantedBy=multi-user.target
EOF配置服务
## 随系统启动
[root@localhost ~]# systemctl enable nginx
## 启动服务
[root@localhost ~]# systemctl start nginx站点文件
[root@localhost ~]# cat >> /var/www/html/halo/h.html << EOF
<html>
<head>
<title>Welcome to TestWeb</title>
</head>
<body>
<h1>Hello from mytest.example.com!</h1>
</body>
</html>
EOF防火墙配置
## 开放 80 端口,如果非常规 80 端口,除了防火墙要配置外,还需要通过 semange 配置 selinux
[root@localhost ~]# firewall-cmd --add-port=80/tcp --permanent
[root@localhost ~]# firewall-cmd --add-port=443/tcp --permanent
[root@localhost ~]# firewall-cmd --add-port=8090/tcp --permanent
[root@localhost ~]# firewall-cmd --reload内网穿透
下载解压
## 内网穿透
## 官方下载:https://gofrp.org/zh-cn/
[root@localhost ~]# wget http://172.16.28.200/Source/frp/frp_0.64.0_linux_amd64.tar.gz
[root@localhost ~]# tar -zxvf frp_0.64.0_linux_amd64.tar.gz -C /usr/local
[root@localhost ~]# mv /usr/local/frp_0.64.0_linux_amd64 /usr/local/frpc
## 删除 frp 服务器端文件
[root@localhost ~]# rm -rf /usr/local/frpc/frps*配置文件
[root@localhost ~]# vim /usr/local/frpc/frpc.toml# frp 0.64 服务端配置文件(TOML格式)
# 官方文档参考:https://gofrp.org/zh-cn/docs/concepts/
# frps 服务器域名或IP地址
serverAddr = "1.1.1.1"
# frps 监听端口
serverPort = 1234
# 客户端与服务端通信的认证令牌(强烈建议设置,客户端必须相同)
auth.method = "token" # 认证方法,可选 "token" 或 "oidc"
auth.token = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# 传输层配置
transport.tcpMux = true # 启用 TCP 多路复用(推荐,提高性能)
[[proxies]]
# 服务名称
name = "myblog-http"
# 服务类型
type = "http"
# 本地服务的ip
localIP = "127.0.0.1"
# 本地web服务端口
localPort = 80
# 自定义访问的域名
customDomains = ["myblog.example.com"]
[[proxies]]
# 服务名称
name = "myblog-https"
# 服务类型
type = "https"
# 本地服务的ip
localIP = "127.0.0.1"
# 本地web服务端口
localPort = 443
# 自定义访问的域名
customDomains = ["myblog.example.com"]服务文件
[root@localhost ~]# cat >> /etc/systemd/system/frpc.service << EOF
[Unit]
Description=frpc
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/frpc/frpc -c /usr/local/frpc/frpc.toml
Restart=always
TimeoutStopSec=5
User=test001
Group=test001
[Install]
WantedBy=multi-user.target
EOF配置服务
## 启动服务
[root@localhost ~]# systemctl start frpc.service
## 随系统启动
[root@localhost ~]# systemctl enable frpc.service申请 SSL 证书
安装 certbot
[root@localhost ~]# yum install -y certbot python3-certbot-nginx申请证书
## 申请证书时,需要通过 frpc 将80端口映射到公网,服务器同时不能占用80端口(关闭 Nginx 服务)
## 证书申请完毕后,在启动 Nginx 服务
[root@localhost ~]# certbot certonly --standalone -d myblog.example.com目录赋权
## 需要 /etc/letsencrypt/ 目录赋权给 Nginx 服务专用用户 test001
[root@localhost ~]# chown -R test001:test001 /etc/letsencrypt/自动延期
计划任务示例
* * * * * 要执行的命令/脚本路径
分 时 日 月 周
分:0-59(每分钟用 *,指定分钟用具体数字,如 30 表示 30 分)
时:0-23(0 表示凌晨,12 表示中午,如 2 表示凌晨 2 点)
日:1-31(* 表示每天)
月:1-12(* 表示每月)
周:0-7(0 和 7 都表示周日,* 表示每周)创建计划任务
[root@localhost ~]# sudo -u test001 crontab -e# 每天凌晨 3 点检查并续期 Certbot 证书,续期成功后重启 Nginx/Apache
0 3 * * * /usr/bin/certbot renew --quiet --renew-hook "systemctl restart nginx"#> 0 3 * * * 每天凌晨 3 点执行;
#> --quiet 静默模式,只输出错误信息,避免冗余日志;
#> --renew-hook "systemctl restart nginx" 证书续期成功后,重启 nginx 服务器;连接 MySQL
连接数据库
## 参考资料:https://blog.csdn.net/leyou921/article/details/135238772
## 参考资料:https://blog.csdn.net/2401_88677290/article/details/143642829
[root@localhost ~]# mysql -h 192.168.2.101 -u root -p
Enter password: # 这里需要输入mysql数据库 root 用户密码创建数据库
## 创建新数据库
mysql> create database halodb;
## 验证新数据库 halodb是否创建成功
mysql> show databases;创建数据库用户
## 创建用户 halodbuser ,并指定该用户只能从本地机器连接,如果能从任意终端登录,使用 % 替换 localhost
## 同时需要注意密码强度和密码长度(大于20位)
mysql> CREATE USER 'halodbuser'@'%' IDENTIFIED BY 'A789**@a123**';
## 授予新用户 halodbuser 对数据库 halodb 具有管理员权限 ;
## myhalodb123.* 释义: *号前表示数据库名称,*后表示数据库表;如果是*.*表示所有数据库的所有表 ;
mysql> GRANT ALL PRIVILEGES ON halodb.* TO 'halodbuser'@'%' WITH GRANT OPTION;
## 刷新权限使更改生效, 非必须,但强烈建议执行一次
mysql> FLUSH PRIVILEGES;
## 验证是否创建新用户成功
mysql> SHOW GRANTS FOR 'halodbuser'@'%';部署 Halo
依赖环境
安装jdk
## halo 技术文档 https://docs.halo.run/getting-started/install/jar-file
## 要求 halo-2.21.9.jar ,需要安装 jdk21 以上版本
[root@localhost ~]# dnf install -y java-21-openjdk验证安装
[root@localhost ~]# java -version
openjdk version "21.0.1" 2023-10-17 LTS
OpenJDK Runtime Environment Temurin-21.0.1+12 (build 21.0.1+12-LTS)
OpenJDK 64-Bit Server VM Temurin-21.0.1+12 (build 21.0.1+12-LTS, mixed mode)
## 如果不是安装的最新版,可通过以下步骤完善
[root@localhost ~]# vim /etc/profile.d/java21.sh
export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-21.0.8.0.9-1.el9.x86_64
export PATH=$JAVA_HOME/bin:$PATH
[root@localhost ~]# source /etc/profile.d/java21.sh
[root@localhost ~]# echo $JAVA_HOME
/usr/lib/jvm/java-21-openjdk-21.0.8.0.9-1.el9.x86_64
[root@localhost ~]# java -version
openjdk version "21.0.8" 2025-07-15 LTS
OpenJDK Runtime Environment (Red_Hat-21.0.8.0.9-1) (build 21.0.8+9-LTS)
OpenJDK 64-Bit Server VM (Red_Hat-21.0.8.0.9-1) (build 21.0.8+9-LTS, mixed mode, sharing)下载部署
下载 jar 文件
[root@localhost ~]# wget https://dl.halo.run/release/halo-2.21.9.jar -O /var/www/html/halo/app/halo.jar创建 文件
[root@localhost ~]# cat >> /var/www/html/halo/.halo2/application.yaml << EOF
server:
# 运行端口
port: 8090
spring:
# 数据库配置,支持 MySQL、MariaDB、PostgreSQL、H2 Database,具体配置方式可以参考下面的数据库配置
r2dbc:
url: r2dbc:pool:mysql://192.168.2.101:3306/halodb
username: halodbuser
password: A789**@a123**
sql:
init:
mode: always
# 需要配合 r2dbc 的配置进行改动
platform: h2
halo:
# 工作目录位置
work-dir: ${user.home}/.halo2
# 外部访问地址
external-url: http://localhost:8090
# 附件映射配置,通常用于迁移场景
attachment:
resource-mappings:
- pathPattern: /upload/**
locations:
- migrate-from-1.x
EOF创建服务文件
[root@localhost ~]# cat >> /etc/systemd/system/halo.service << EOF
[Unit]
Description=Halo Service
Documentation=https://docs.halo.run
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=test001
ExecStart=/usr/lib/jvm/java-21-openjdk-21.0.8.0.9-1.el9.x86_64/bin/java -Dfile.encoding=UTF-8 -server -Xms256m -Xmx256m -jar /var/www/html/halo/app/halo.jar --spring.config.additional-location=optional:file:/var/www/html/halo/.halo2/
ExecStop=/bin/kill -s QUIT $MAINPID
Restart=always
StandOutput=syslog
StandError=inherit
[Install]
WantedBy=multi-user.target
EOF配置服务
[root@localhost ~]# systemctl daemon-reload
[root@localhost ~]# systemctl enable halo
[root@localhost ~]# systemctl start halo到这里,CentOS-Stream-9 通过JAR部署Halo开源博客系统就完成了。