背景
偶然看见halo博客框架,自带管理后台,搭建也比较方便(绝对不是因为自己菜),就试着搭建一下,感觉效果还不错,写一篇随笔记录一下。
自己云服务器还有其他服务,于是想实现用不同的子域名访问对应的服务,这时就想到了nginx的反向代理,再加上服务器经常换,有迁移需求,于是采用容器化的方案。除此之外还有SSL证书问题也想一并解决
于是目标便定下来了:
容器化部署halo博客框架和nginx,nginx负责反向代理,用子域名访问blog与其他服务,Let’s Encrypt申请证书,certbot实现证书续签。
搭建环境
1.一台东京云服务器 2C2G50G 峰值30Mbps Ubuntu Server 24.04 LTS 64bit(配有1penal方便管理)
2.一条闲置域名 (服务器不在国内,不需要备案)
大体思路
1.Nginx 与 halo框架 部署与测试
1.1 Nginx
我的配置文件保存路径将设置成: /opt/1panel/docker/compose/nginx
打算直接在 1panel 完成 Nginx 的部署,
在部署之前需要在<配置文件保存路径>目录下创建nginx.conf这个文件,不然可能编排失败(会被识别成目录,但是我们要挂载是这个文件)
# nginx.conf 参考文件
# 运行用户,默认是 nginx(容器内已创建该用户)
user nginx;
# 工作进程数,建议设为 CPU 核心数,auto 表示自动匹配
worker_processes auto;
# 错误日志路径与级别:debug > info > notice > warn > error > crit
error_log /var/log/nginx/error.log notice;
# 进程 PID 文件路径
pid /var/run/nginx.pid;
# 事件模块配置
events {
# 单个工作进程的最大并发连接数
worker_connections 1024;
# 启用多路复用 I/O 模型(epoll 是 Linux 最优选择)
use epoll;
}
# HTTP 核心模块配置
http {
# 引入 MIME 类型映射文件
include /etc/nginx/mime.types;
# 默认 MIME 类型(二进制流)
default_type application/octet-stream;
# 日志格式定义,main 是格式名称
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
# 访问日志路径,使用上面定义的 main 格式
access_log /var/log/nginx/access.log main;
# 启用高效文件传输模式
sendfile on;
# 减少网络报文段数量(需配合 sendfile on 使用)
tcp_nopush on;
# 优化 TCP 连接的关闭流程
tcp_nodelay on;
# 长连接超时时间
keepalive_timeout 65;
# 开启 gzip 压缩(基础场景可关闭,高并发建议开启)
# gzip on;
# 引入 conf.d 目录下的所有 .conf 文件(虚拟主机配置)
include /etc/nginx/conf.d/*.conf;
}
在1pnael创建编排(docker compose)
配置文件保存路径: /opt/1panel/docker/compose/nginx
services:
nginx:
image: nginx:latest
container_name: nginx
restart: always
volumes:
- ./conf.d:/etc/nginx/conf.d
- ./nginx.conf:/etc/nginx/nginx.conf
- /etc/letsencrypt:/etc/letsencrypt # ssl证书 这个路径方便Let’s Encrypt申请完证书与续签完直接使用
- ./logs:/var/log/nginx # 日志
environment:
TZ: Asia/Shanghai
ports:
- "80:80"
- "443:443"
networks:
- public-network # nginx与其他需要开放的服务共用的网络,便于容器之间通讯
networks:
public-network:
# 核心配置:声明为外部网络,存在则用,不存在则创建
external: false
name: public-network # 固定全局唯一网络名
driver: bridge # 可选,默认就是bridge,可省略
1.2 halo框架
参考指南:Halo文档 - 使用 Docker Compose 部署
version: "3"
services:
halo:
image: registry.fit2cloud.com/halo/halo:2.22 # 这里使用的是Halo 社区版镜像,可以检测一下版本有没有更新
restart: on-failure:3
depends_on:
halodb:
condition: service_healthy
networks:
- halo_network
- public-network # 如果更改了网络名这里记得改
volumes:
- ./halo2:/root/.halo2
ports:
- "8090:8090"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8090/actuator/health/readiness"]
interval: 30s
timeout: 5s
retries: 5
start_period: 30s
environment:
# JVM 参数,默认为 -Xmx256m -Xms256m,可以根据实际情况做调整,置空表示不添加 JVM 参数
- JVM_OPTS=-Xmx256m -Xms256m
command:
- --spring.r2dbc.url=r2dbc:pool:postgresql://halodb/halo
- --spring.r2dbc.username=halo
# PostgreSQL 的密码,请保证与下方 POSTGRES_PASSWORD 的变量值一致。
- --spring.r2dbc.password=<密码>
- --spring.sql.init.platform=postgresql
# 外部访问地址,请根据实际需要修改
- --halo.external-url=http://localhost:8090/
halodb:
image: postgres:15.4
restart: on-failure:3
networks:
- halo_network
- public-network # 如果更改了网络名这里记得改
volumes:
- ./db:/var/lib/postgresql/data
healthcheck:
test: [ "CMD", "pg_isready" ]
interval: 10s
timeout: 5s
retries: 5
environment:
- POSTGRES_PASSWORD=<密码>
- POSTGRES_USER=halo
- POSTGRES_DB=halo
- PGUSER=halo
networks:
halo_network:
public-network: # 如果更改了网络名这里记得改
external: true # 外部网络
2.Nginx反向代理配置
当前目录: ./conf.d
重命名default.conf -> default.conf.bak 消除此配置文件的影响
新建文件subdomain.conf或<...>.conf
# subdomain.conf
# 1. 匹配 pan.web.com 子域名,转发到网盘服务
server {
listen 80;
# 精准匹配子域名
server_name pan.web.com;
# 反向代理配置
location / {
proxy_pass http://<网盘容器名>:5212/;
# 传递客户端真实信息,必填
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;
}
}
# 2. 匹配 blog.web.com 子域名,转发到博客服务
server {
listen 80 default_server;
server_name blog.web.com;
location / {
proxy_pass http://<halo容器名>: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_buffer_size 16k;
proxy_buffers 4 64k;
proxy_busy_buffers_size 128k;
# 延长超时时间,避免后端处理慢导致的 500
proxy_connect_timeout 60s;
proxy_read_timeout 120s;
}
}
# 3. 匹配主域名 web.com,转发到默认主站服务
server {
listen 80;
server_name web.com;
location / {
proxy_pass http://<容器名,比如halo容器>:8090;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
启动两个编排并检查防火墙端口是否开放,测试是否能访问。
3.SSL证书与HTTPS访问
参考教程:使用 Let’s Encrypt 免费申请泛域名 SSL 证书,并实现自动续期
这里申请的是泛域名SSL证书,域名托管在Cloudflare,使用DNS-01验证,
不同的域名服务商可能需要不同操作,这里只测试了Cloudflare
3.1 Let’s Encrypt申请SSL证书
安装 Certbot DNS 插件
apt install -y certbot python3-certbot-dns-cloudflare
获取域名服务商 API 密钥
在Cloudflare获取Token,此处略过
新建 Cloudflare 密钥配置文件
~/.secrets/certbot/cloudflare.ini:
# cloudflare.ini
# Cloudflare API 配置
dns_cloudflare_api_token = 你的 Cloudflare API Token
设置文件权限(避免其他用户读取):
chmod 600 ~/.secrets/certbot/cloudflare.ini
申请泛域名证书
执行以下命令,通过 DNS-01 验证申请泛域名证书:
certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials ~/.secrets/certbot/cloudflare.ini \
--dns-cloudflare-propagation-seconds 60 \ # 等待 DNS 记录生效的时间
-d <域名> \ # 主域名
-d *.<域名> \ # 泛域名,匹配所有子域名(pan/blog 等)
--email 你的邮箱@xxx.com \
--agree-tos \
--no-eff-email
申请成功后,证书路径应该为 /etc/letsencrypt/live/<域名>/
3.2 泛域名证书的自动续期
测试续期(不会真的执行续约仅测试):
certbot renew --dry-run
添加定时任务
切换root
编辑 crontab 配置
crontab -e
在新行添加:
0 3 * * * certbot renew --quiet && docker exec <nginx容器名> nginx -s reload
每天凌晨 3 点自动检查证书是否需要续期,续期成功后重载 Nginx 配置,让新证书生效
3.3 nginx 配置HTTPS
如果使用HTTPS访问,需要修改nginx配置,可以新建一个配置https-subdomain.conf或https-<...>.conf,并重命名subdomain.conf->subdomain.conf.bak,避免影响
# ========== 全局规则:所有 HTTP 请求强制跳转到 HTTPS ==========
server {
listen 80;
server_name <域名> pan.<域名> blog.<域名>;
return 301 https://$host$request_uri;
}
# ========== 1. HTTPS 配置:pan.<域名> 转发到 <网盘容器名> 容器 ==========
server {
listen 443 ssl;
server_name pan.<域名>;
# SSL 证书配置(路径和你 Certbot 申请的一致)
ssl_certificate /etc/letsencrypt/live/<域名>/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/<域名>/privkey.pem;
# SSL 安全优化配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers HIGH:!aNULL:!MD5:!3DES;
ssl_session_timeout 10m;
ssl_session_cache shared:SSL:10m;
# 反向代理配置(复用原有逻辑)
location / {
proxy_pass http://<网盘容器名>:5212/;
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; # 传递 HTTPS 协议标识
}
}
# ========== 2. HTTPS 配置:blog.<域名> 转发到 <halo容器名> 容器 ==========
server {
listen 443 ssl;
server_name blog.<域名>;
# 复用同一套 SSL 证书
ssl_certificate /etc/letsencrypt/live/<域名>/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/<域名>/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers HIGH:!aNULL:!MD5:!3DES;
# 反向代理配置(保留原有缓冲区+超时优化)
location / {
proxy_pass http://<halo容器名>: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_buffer_size 16k;
proxy_buffers 4 64k;
proxy_busy_buffers_size 128k;
proxy_connect_timeout 60s;
proxy_read_timeout 120s;
}
}
# ========== 3. HTTPS 配置:主域名 <域名> 转发到 <halo容器名> 容器 ==========
server {
listen 443 ssl;
server_name <域名>;
# 复用 SSL 证书
ssl_certificate /etc/letsencrypt/live/<域名>/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/<域名>/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
# 反向代理配置
location / {
proxy_pass http://<halo容器名>: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;
}
}
重载nginx后测试是否能以https访问
4.主题推荐
总体评价
实用性 4.5 / 5
美观性 4.5 / 5
折腾完感觉好麻烦,不如回到博客园写()
参考文献:
1.Halo文档 - 使用 Docker Compose 部署
2.使用 Let’s Encrypt 免费申请泛域名 SSL 证书,并实现自动续期