📝 1. 背景与目标#
在云服务器(VPS)上部署项目时,直接暴露端口既不安全也不优雅。本次实战的目标是:
- 将在 Spaceship 购买的域名 (
gyqblog.top) 解析到 VPS 公网 IP。 - 停用并清理早期的测试项目(如 JSP 容器),释放资源。
- 利用 Docker 部署一个全局 Nginx 作为统一网关(反向代理)。
- 使用
acme.sh自动申请并部署 Let’s Encrypt 免费 SSL 证书,实现全站 HTTPS 加密。
🌐 2. DNS 解析:让域名找到服务器#
在域名注册商(如 Spaceship)的后台,找到 Advanced DNS(高级 DNS) 设置。我们需要添加两条 A 记录(A 记录的作用是将域名直接指向一个 IPv4 地址):
- 记录 1:主机记录 (Host) 填
@,记录值 (Value) 填 VPS 公网 IP。代表访问主域名。 - 记录 2:主机记录 (Host) 填
www,记录值 (Value) 填 VPS 公网 IP。代表访问 www 子域名。
验证方法:在本地终端执行 ping gyqblog.top,如果返回服务器 IP,说明解析已生效。
🔐 3. SSL 证书申请与“端口保卫战”#
3.1 安装 acme.sh 脚本#
这是一个轻量级的自动证书申请工具:
curl https://get.acme.sh | sh -s email=你的邮箱@example.com
source ~/.bashrc3.2 ⚠️ 避坑指南:80 端口占用问题#
在使用 --standalone(独立模式)申请证书时,acme.sh 会临时创建一个 Web 服务来验证域名所有权,这要求服务器的 80 端口必须处于空闲状态。
如果遇到报错:tcp port 80 is already used by (("docker-proxy"...)),说明之前的容器没有彻底关闭。 排查与解决:
# 1. 揪出占用端口的“内鬼”容器
docker ps
# 2. 找到占用 80 端口的 CONTAINER ID,强行停止它
docker stop <容器ID>
# 3. 再次确认端口已空出
lsof -i:803.3 申请并提取证书#
端口清空后,执行申请:
acme.sh --issue -d gyqblog.top -d www.gyqblog.top --standalone申请成功后,将证书安装到宿主机的指定目录(此目录稍后会挂载给 Nginx):
mkdir -p /opt/nginx/ssl
acme.sh --install-cert -d gyqblog.top \
--key-file /opt/nginx/ssl/gyqblog.top.key \
--fullchain-file /opt/nginx/ssl/gyqblog.top.pem \
--reloadcmd "cd /root/nginx-proxy && docker compose restart nginx"(注:--reloadcmd 保证了未来证书自动续期后,Nginx 能自动重启加载新证书。
而cd /root/nginx-proxy是存放地点,必须写明)
🏗️ 4. 核心网关搭建(Docker + Nginx)#
为博客和 API 接口做准备,建立一个专属的网关目录:
mkdir -p /root/nginx-proxy
cd /root/nginx-proxy4.1 写入 Nginx 配置文件 (解决终端粘贴换行错乱)#
在某些 SSH 终端中使用 nano 右键粘贴代码时,会导致换行符丢失、格式错乱。
解法:使用 cat EOF 直接注入代码,绝对不会乱码。
在终端直接执行以下整段命令:
cat > nginx.conf << 'EOF'
# HTTP 强制跳转 HTTPS
server {
listen 80;
server_name gyqblog.top www.gyqblog.top;
return 301 https://$host$request_uri;
}
# HTTPS 核心网关
server {
listen 443 ssl;
server_name gyqblog.top www.gyqblog.top;
# 容器内的证书路径(稍后在 compose 中映射)
ssl_certificate /etc/nginx/ssl/gyqblog.top.pem;
ssl_certificate_key /etc/nginx/ssl/gyqblog.top.key;
# 临时欢迎页面
location / {
default_type text/html;
return 200 '<!DOCTYPE html><html><head><meta charset="UTF-8"><title>网关成功</title></head><body><h1>网关配置成功!博客即将上线...</h1></body></html>';
}
}
EOF4.2 编写 docker-compose.yml#
同样使用 cat EOF 写入容器编排配置。注意我们需要挂载配置文件和刚刚提取的 SSL 证书目录:
cat > docker-compose.yml << 'EOF'
services:
nginx:
image: nginx:latest
container_name: nginx-gateway
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- /opt/nginx/ssl:/etc/nginx/ssl
EOF4.3 启动网关#
docker compose up -d启动后,在浏览器访问 http://gyqblog.top,网页会自动跳转到带有安全锁的 HTTPS 页面,并显示“网关配置成功!”。
💡 5. 总结与架构演进#
完成这一步后,服务器架构发生了质的改变: 过去是单体直连(用户直接通过 IP:端口 访问项目),现在演变成了网关代理(Nginx 独占 80 和 443 端口,处理完安全加密后,在内网安全地将请求转发给后端的真实项目)。
🔄 6. 进阶:SSL 证书全自动续签与 Hooks 解法#
6.1 隐患:端口冲突导致的“静默失败”#
虽然在任务 3.2 中通过手动执行 docker stop 解决了首次申请的 80 端口占用问题,但这埋下了一个隐患 :acme.sh 默认会在 60 天后触发后台定时任务自动续签。
当定时任务在半夜静默运行时,依然会因为 80 端口被 nginx-gateway 占死而报错(tcp port 80 is already used),最终导致证书过期、网站断联。
6.2 解法:引入自动化 Hooks#
为了实现真正的“无人值守”,我们需要在 acme.sh 的配置中永久写入自动化脚本(Hooks),让它在续签前自己关门(停用 Nginx),续签后自己开门(启动 Nginx 并重启相关服务)。同时,我们将颁发机构强制绑定为最稳定的 Let’s Encrypt。
场景一:个人博客主站 (gyqblog.top)#
执行以下命令,将起停动作彻底“刻录”到配置中:
~/.acme.sh/acme.sh --issue -d gyqblog.top -d www.gyqblog.top --standalone \
--server letsencrypt \
--pre-hook "docker stop nginx-gateway" \
--reloadcmd "cd /root/nginx-proxy && docker compose restart nginx" \
--force工作流:自动停止 Nginx ➔ 强制抢占 80 端口完成 Let’s Encrypt 续签 ➔ 执行 docker compose 重启网关加载新证书。
场景二:高阶网关(包含 3X-UI 与 Hysteria2 独立节点)#
对于挂载了多个代理协议的面板域名(如 panel.gyqblog.top),续签后不仅要恢复 Nginx,还需要将证书复制到公共目录,下发权限给非 root 用户(如 Hysteria2),并重启相关代理核心:
~/.acme.sh/acme.sh --issue -d panel.gyqblog.top --standalone \
--server letsencrypt \
--pre-hook "docker stop nginx-gateway" \
--post-hook "docker start nginx-gateway" \
--reloadcmd "x-ui restart && cp /root/cert/panel.gyqblog.top/fullchain.pem /etc/hysteria/fullchain.pem && cp /root/cert/panel.gyqblog.top/privkey.pem /etc/hysteria/privkey.pem && chown hysteria:hysteria /etc/hysteria/fullchain.pem && chown hysteria:hysteria /etc/hysteria/privkey.pem && systemctl restart hysteria-server.service" \
--force工作流:自动停止 Nginx ➔ 续签证书 ➔ 启动 Nginx ➔ 重启 Xray 核心 ➔ 复制证书至 /etc/hysteria ➔ 赋予 hysteria 用户组权限 ➔ 重启 Hysteria2 极速服务。
至此,整套服务器架构的 SSL 证书更新实现了全自动闭环,无需人工干预。









