平台工程手记Nginx 部署上线方案

HTTPS FOR PUBLIC. PLAIN FOR INTERNAL.

Nginx 全站 HTTPS 上线用 Certbot 签发证书,一次切换对外域名到 443

对外公开域名统一启用 HTTPS,内部管理域名保持 80 不变;
证书由 Certbot(Let's Encrypt)自动签发与续期,
配置完全由仓库文件控制。

SPARROW 部署实践2026.09.27含域名清单、配置改动与上线步骤
PUBLIC HTTPS · INTERNAL PLAIN
对外公开域名 × 9443 ssl 配置块 + 80 → 301 跳转
Certbot 签发并自动续期
证书 / Let's EncryptHTTP-01 校验 · 90 天续期
nginx 443 ssl 块静态 root / 反向代理

内部管理域名 × 3 保持 80 不变,不上 HTTPS。
先签证书,再覆盖配置,顺序不能反。

01 /

结论先行

对外公开域名统一走 443 SSL,并做 80 → 301 跳转;内部管理域名保持 80 不变。证书用 certbot certonly 只签发、不改配置,配置完全由仓库文件控制。上线顺序固定:先签发证书,再覆盖新配置。

01 · PUBLIC HTTPS

对外域名上 443

9 个对外域名新增 443 ssl 块,80 端口统一 301 跳转到 HTTPS。

02 · INTERNAL PLAIN

内部域名保持 80

nacos、monitor、ossrh 等内部管理域名不暴露公网 HTTPS,维持 80 不变。

03 · CERTONLY ONLY

certbot 只签不改

用 certonly 只签发证书,不触碰 nginx 配置,避免证书与配置相互干扰。

顺序不能反:先签证书,再覆盖配置

443 块引用了证书路径,证书不存在时 nginx -t 会直接失败、nginx 无法启动。因此必须先完成证书签发,再把新配置覆盖到服务器,最后 nginx -t 通过后再 reload。

依据:Nginx · Configuring HTTPS Servers / Certbot · Documentation
02 /

问题背景

现网 nginx.conf 中,除 www.sparrowzoo.com 已有证书外,其余域名都只配置了 80 端口。所有对外公开域名——静态站点、反向代理、甚至带 WebSocket 的 API——都走明文 HTTP,请求内容可被中间人窃听或篡改。

HTTPS 通过 TLS 加密传输,保证机密性与完整性,是浏览器安全基线(安全上下文、Service Worker、剪贴板等能力)的前置条件。本次改动的目标是:为所有对外公开域名启用 HTTPS,内部管理域名保持 80 不变,证书通过 Certbot(Let's Encrypt)自动签发与续期。

443 SSL · 加密端口
HTTPS 默认端口,listen 443 ssl 表示该 server 块启用 TLS。
301 跳转 · 永久重定向
浏览器与搜索引擎都会记住跳转,80 端口访问被永久转到 HTTPS。
Certbot · 证书客户端
Let's Encrypt 官方工具,自动完成域名校验、签发与续期。
X-Forwarded-Proto
反向代理向后端透传原始协议,后端据此生成 https 链接与 secure cookie。
HTTP-01 校验依赖 80 端口

Certbot 的 HTTP-01 校验会临时在 80 端口放一个文件让 Let's Encrypt 访问。因此所有待签发域名的 A 记录必须指向本服务器,且 80 端口对外可达。这也是「先签证书、再覆盖配置」的原因之一——签发时旧配置里的 80 块仍在正常工作。

03 /

详细内容

STEP 01域名清单:对外与内部的边界

先把域名按「是否对外公开」分成两类。只有对外公开的域名才需要证书与 443 块;内部管理域名继续走 80,避免把管理面板暴露到公网 HTTPS 的证书与端口管理里。

对外公开(启用 HTTPS,80 → 301 跳转 443)

域名类型后端 / root备注
r.sparrowzoo.net静态/path/to/sourceexpires 1h
passport.sparrowzoo.com静态/path/to/passporttry_files
admin.sparrowzoo.com静态/path/to/admintry_files
im.sparrowzoo.com静态/path/to/imtry_files
www.sparrowzoo.com静态/path/to/www已有证书
img.im.sparrowzoo.net静态/path/to/imagesexpires 0d
u.r.sparrowzoo.net静态/path/to/uploadexpires 0d
lottery.sparrowzoo.com反代http://lottery (127.0.0.1:<PORT>)no-store
api.sparrowzoo.com反代http://api (127.0.0.1:<PORT>) + /websocket → http://websocket (127.0.0.1:<PORT>)含 WebSocket

内部管理(保持 80,不上 HTTPS)

域名类型后端 / root
nacos.example.com反代http://nacos (127.0.0.1:<PORT>)
monitor.example.com反代http://monitor (127.0.0.1:<PORT>)
ossrh.example.com静态/path/to/ossrh

分类原则:能公开访问的域名才需要加密与证书;内部运维面板走内网或由 IP 白名单保护,不必占用 Let's Encrypt 的证书名额。

STEP 02本次配置改动

改动集中在五点:对外域名加 443 块、反代透传协议、修复语法 bug、清理无效写法、移除废弃的 juejin 配置。

  1. 新增 443 ssl 块 + 80→301:每个对外域名新增 listen 443 ssl 块,原 80 块改为 return 301 https://$host$request_uri;。
  2. 透传协议头:反向代理新增 proxy_set_header X-Forwarded-Proto $scheme;,后端据此判断协议,用于生成 https 链接与 secure cookie。
  3. 修复语法 bug:原配置 server_name api.sparrowzoo.com 缺少分号,会让 nginx -t 直接失败。
  4. 清理无效写法:index /; → index index.html;、charset utf_8; → charset utf-8;、移除对 proxy_pass 无意义的 fastcgi_intercept_errors on;。
  5. 移除 juejin 配置:删除 juejin 相关的 upstream 与 server 块。

静态站点:80→301 + 443 ssl

passport.sparrowzoo.com · 静态站点示例
# 80:统一重定向到 HTTPS
server {
    listen 80;
    server_name passport.sparrowzoo.com;
    return 301 https://$host$request_uri;
}

# 443:静态站点
server {
    listen 443 ssl;
    server_name passport.sparrowzoo.com;

    ssl_certificate     /etc/letsencrypt/live/passport.sparrowzoo.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/passport.sparrowzoo.com/privkey.pem;

    root /path/to/passport;
    index index.html;
    charset utf-8;

    location / {
        try_files $uri $uri/ /index.html;
    }
}

反向代理 + WebSocket:透传协议头

api.sparrowzoo.com · 反代 + WebSocket 示例
server {
    listen 80;
    server_name api.sparrowzoo.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name api.sparrowzoo.com;

    ssl_certificate     /etc/letsencrypt/live/api.sparrowzoo.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.sparrowzoo.com/privkey.pem;

    location / {
        proxy_pass http://api;
        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;
    }

    # WebSocket 升级
    location /websocket {
        proxy_pass http://websocket;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

证书路径由 Certbot 统一放在 /etc/letsencrypt/live/<域名>/ 下,fullchain.pem 是证书链、privkey.pem 是私钥。覆盖配置前要确认这些路径与实际签发目录一致。

STEP 03上线步骤:先签证书,再覆盖配置

整个过程七步,核心原则是证书在前、配置在后,每一步都有明确的验证点。

01 / BACKUP备份现网配置
02 / DNS确认解析与 80
03 / ISSUEcertbot 签发证书
04 / DEPLOY覆盖新配置
05 / RELOAD校验并平滑重载
06 / VERIFY验证 HTTPS
07 / RENEW确认自动续期

第 1 步 · 备份现网配置

bash
sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak-$(date +%F)

备份带日期后缀,出问题可以一键还原。

第 2 步 · 确认 DNS 与 80 端口

所有待签发域名的 A 记录需指向本服务器,且 80 端口对外可达(Certbot 的 HTTP-01 校验依赖)。这一步不通过,后面签发会失败。

第 3 步 · 先用 certbot 签发证书

此时服务器仍是旧配置,80 块正常。需新签发的 8 个域名:r.sparrowzoo.net、img.im.sparrowzoo.net、u.r.sparrowzoo.net、passport/admin/im/lottery/api.sparrowzoo.com。

bash
for d in r.sparrowzoo.net img.im.sparrowzoo.net u.r.sparrowzoo.net \
         passport.sparrowzoo.com admin.sparrowzoo.com im.sparrowzoo.com \
         lottery.sparrowzoo.com api.sparrowzoo.com; do
  sudo certbot certonly --nginx -d "$d"
done
为什么用 certonly 而不是 --nginx

certonly 只签发证书、不修改 nginx 配置,配置完全由仓库文件控制;--nginx(不带 certonly)则会直接改写配置,容易和仓库文件冲突。

第 4 步 · 覆盖新配置到服务器

将仓库中的 nginx.conf 覆盖到服务器 /etc/nginx/nginx.conf,并核对 pid 路径、各 root 路径与服务器实际目录一致。

第 5 步 · 校验并平滑重载

bash
sudo nginx -t          # 必须先通过
sudo nginx -s reload

nginx -t 是必须通过的前置校验;失败说明配置有语法错误或证书路径缺失,此时不要 reload。

第 6 步 · 验证

bash
curl -I https://api.sparrowzoo.com        # 应 200
curl -I http://passport.sparrowzoo.com    # 应 301 跳到 https

并人工确认 im 聊天(wss)、图片加载、文件上传等正常——HTTPS 下这些走 wss:// 与加密上传,要实测链路。

第 7 步 · 确认自动续期

bash
sudo certbot renew --dry-run

Let's Encrypt 证书有效期 90 天,Certbot 会自动续期。--dry-run 做一次模拟续期,确认定时任务与校验链路都正常。

STEP 04注意事项

  • 顺序不能反:443 块引用了证书路径,证书不存在时 nginx -t 会失败、nginx 无法启动。必须先签发证书,再覆盖新配置。
  • 证书与配置解耦:用 certonly 只签证书,配置完全由仓库文件控制,避免 Certbot 自动改写引入差异。
  • 内部域名不上 HTTPS:nacos、monitor 等管理面板保持 80,不暴露到公网 HTTPS。
  • WebSocket 要单独验证:HTTPS 下客户端用 wss://,升级头要正确透传,im 聊天才能正常工作。

DEPLOYMENT DECISION

对外上 443,内部保 80,证书先于配置。

对外公开域名统一 443 SSL + 80→301 跳转,内部管理域名保持 80;证书用 certbot certonly 只签不改,配置由仓库文件控制。

上线七步可归为一句:备份、确认 DNS、签发证书、覆盖配置、nginx -t 通过后 reload、验证、模拟续期。真正的风险点只有一个——证书没签好就覆盖了引用证书路径的配置,nginx 起不来,所以顺序永远不能反。

04 /

常见问题

按文档上线后 HTTPS 依然不生效?大多数情况不是 nginx 配置写错,而是服务器之外的网络入口没放行 443 端口——最常见的就是云服务器安全组只开了 22(SSH),没开 80 / 443。下面给出「本机自测三步法」,先把问题隔离到正确的层次。

Q1HTTPS 没生效,浏览器报「无法访问此网站 / ERR_CONNECTION_CLOSED」

现象

访问 https://www.sparrowzoo.com 时,浏览器提示「无法访问此网站…ERR_CONNECTION_CLOSED」或长时间无响应。

排查:本机自测三步法

依次执行下面三条命令。每条「通过」都代表 nginx 的某一层是好的,通过后往下走,直到找出断在哪一层。

第 1 步 · 校验配置与证书

bash
sudo nginx -t

输出 syntax is ok / test is successful 说明配置语法正确、证书路径都能加载。若失败,多半是证书没签好或路径写错。

第 2 步 · 确认监听端口

bash
sudo ss -tlnp | grep -E ':(80|443)\b'

同时出现 0.0.0.0:80 和 0.0.0.0:443 说明 reload 已生效、nginx 正在监听两个端口。只有 80 没有 443,说明还在跑旧配置,先执行 sudo nginx -s reload。

第 3 步 · 本机直连 443

bash
curl -kv https://127.0.0.1 -H "Host: www.sparrowzoo.com"

本机能完成 TLS 握手并返回内容,说明 nginx 的 HTTPS 本身完全正常。此时问题一定出在服务器之外:云安全组、防火墙或 DNS。

三步全通过,问题就在安全组

如果第 1、2、3 步都通过,但外网浏览器仍连不上 HTTPS,基本可以锁定是云服务器安全组没有放行 443(或 80)端口。阿里云 ECS 默认安全组往往只开了 22,80 / 443 需要手动添加。

解决:在阿里云安全组放行 80 / 443

  1. 进入安全组:登录 阿里云 ECS 控制台 → 网络与安全 → 安全组 ↗,找到实例绑定的安全组(如 sg-2zea8r0aht2wpup5yc90)。
  2. 添加入方向规则:点「手动添加」,分别加两条——端口 80(TCP)、授权对象 0.0.0.0/0;端口 443(TCP)、授权对象 0.0.0.0/0。
  3. 保存后重试:安全组规则即时生效、无需重启,直接刷新 https://www.sparrowzoo.com 验证。
为什么本机通、外网不通

安全组是云服务器最外层的「网络防火墙」,独立于系统内的 iptables / ufw。它默认丢弃未放行端口的入站流量,因此即使 nginx 在系统内正常监听 443,外网流量也会在到达实例前被安全组拦下。

05 /

官方依据与阅读

下列文档支撑 HTTPS 与 Certbot 的机制说明;域名分类与上线顺序是结合本项目背景给出的部署方案。文档核对日期:2026-09-27。

  1. [01]
    Nginx · Configuring HTTPS Servers ↗

    listen 443 ssl、ssl_certificate 与 ssl_certificate_key 的标准写法。

  2. [02]
    Nginx · ngx_http_core_module: return ↗

    return 301 重定向的语义与 $host、$request_uri 变量。

  3. [03]
    Nginx · ngx_http_proxy_module: proxy_set_header ↗

    X-Forwarded-Proto 等代理头如何向后端透传原始协议。

  4. [04]
    Certbot · Documentation ↗

    certonly 只签不改的用法、HTTP-01 校验与 renew 自动续期。

  5. [05]
    Let's Encrypt ↗

    免费证书机构与 90 天证书有效期说明。