内网 HTTPS 部署实战:私有 CA + Nginx 反向代理全流程复盘

19 阅读6分钟

内网 HTTPS 部署实战:私有 CA + Nginx 反向代理全流程复盘

前言

在内网环境中,很多服务只提供 HTTP 访问,存在明文传输、无法使用浏览器安全特性等问题。本文将完整复盘一次内网 HTTPS 改造过程:自建私有 CA 签发证书 → Nginx 容器加载证书 → 反向代理后端服务 → 客户端信任根证书。

全程使用 Docker Compose 部署,配置可复用,适合内网测试环境、企业内部系统、IoT 管理平台等场景。

注:文中出现的 IP、域名均为示例,请替换为你自己的实际地址。

一、为什么不用自签名证书,而要用私有 CA

很多人第一次配 HTTPS 都是直接 openssl req -x509 生成一张自签名证书,但这样会有一个致命问题:每台服务器都要单独导入一次证书,客户端无法统一信任。

正确的做法是:建一个根 CA,用它签出服务器证书。客户端只需要导入一次根 CA 证书,之后这个 CA 签发的所有服务器证书都会被自动信任。

方案客户端操作扩展性
直接自签名服务器证书每台服务器都要导入一次差
私有 CA 签发只导入一次根 CA好

二、整体架构

text

客户端浏览器 --HTTPS--> Nginx 容器 --HTTP--> 后端服务(app.internal:8000)

Nginx 容器挂载 server.crt / server.key

三、环境信息

项目示例值
Nginx 宿主机192.168.1.100
项目目录~/nginx-ssl/
配置目录~/nginx-ssl/conf.d/
证书目录~/nginx-ssl/ssl/
访问域名myapp.local
访问 IP192.168.1.100
后端服务app.internal:8000/

四、目录结构

text

nginx-ssl/
├── docker-compose.yml
├── conf.d/
│   └── default.conf
└── ssl/
    ├── ca.key
    ├── ca.crt
    ├── ca.srl
    ├── server.key
    ├── server.csr
    ├── server.crt
    └── san.ext

五、核心步骤复盘

1. 创建根 CA

bash

openssl genrsa -out ca.key 4096
openssl req -x509 -new -key ca.key -days 3650 -out ca.crt -subj "/CN=MyLAN-CA"

ca.key 是信任链的根基,一旦泄露,所有签发的证书全部失效,务必离线保管。

2. 生成服务器私钥和 CSR

bash

openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr -subj "/CN=myapp.local"

3. 编写 SAN 扩展文件(最容易踩坑的一步)

现代浏览器(尤其 Chrome)已经不再识别 CN 字段,只认 SAN(Subject Alternative Name)。如果 SAN 没写对,即使证书被信任,浏览器依然会报 ERR_CERT_COMMON_NAME_INVALID。

san.ext 内容:

text

authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = myapp.local
IP.1  = 192.168.1.100

经验总结:IP.1 必须是你实际访问用的 IP,而不是随便填。如果你既用域名又用 IP 访问,两个都要列上。

4. 用 CA 签发服务器证书

bash

openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key \
  -CAcreateserial -days 825 -sha256 -extfile san.ext -out server.crt

-days 825 是 iOS/macOS 对服务器证书有效期的上限,超过这个天数在这些设备上会直接报错。

5. 验证证书

bash

openssl x509 -in server.crt -noout -text | grep -A1 "Subject Alternative Name"
openssl verify -CAfile ca.crt server.crt

预期输出:server.crt: OK

六、Nginx 容器化部署

1. docker-compose.yml

yaml

services:
  nginx:
    image: nginx:latest
    container_name: nginx-ssl
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./ssl:/etc/nginx/ssl:ro
      - ./conf.d:/etc/nginx/conf.d:ro
    networks:
      - webnet

networks:
  webnet:
    driver: bridge

2. conf.d/default.conf

HTTP 自动跳转 HTTPS:

nginx

server {
    listen 80;
    server_name myapp.local 192.168.1.100;
    return 301 https://$host$request_uri;
}

HTTPS 主服务:

nginx

server {
    listen 443 ssl;
    server_name myapp.local 192.168.1.100;

    ssl_certificate     /etc/nginx/ssl/server.crt;
    ssl_certificate_key /etc/nginx/ssl/server.key;

    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 10m;

    location / {
        proxy_pass http://app.internal:8000/;

        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_connect_timeout 30s;
        proxy_read_timeout    60s;
        proxy_send_timeout    60s;
    }
}

3. 关于 proxy_pass 末尾斜杠的坑

配置访问 /foo 实际请求
proxy_pass http://app.internal:8000/;/foo(原样转发,推荐)
proxy_pass http://app.internal:8000;/foo(行为相同但语义差)

如果后端服务挂在子路径(如 /vna),则要写成:

nginx

location /vna/ {
    proxy_pass http://app.internal:8000/vna/;
}

七、启动与验证

bash

cd ~/nginx-ssl
docker compose up -d
docker compose exec nginx nginx -t
docker compose exec nginx nginx -s reload
docker compose logs -f nginx

验证命令:

bash

curl -k https://192.168.1.100/
curl --cacert ~/nginx-ssl/ssl/ca.crt https://192.168.1.100/
curl http://app.internal:8000/

八、客户端信任根 CA

关键点:客户端只需导入 ca.crt(根 CA 证书),不需要导入 server.crt。

Windows(Chrome / Edge)

  1. 下载 ca.crt 到本地。
  2. 双击,选择“安装证书”。
  3. 存储位置选“本地计算机”(管理员)或“当前用户”。
  4. 选择“将所有的证书都放入下列存储”,浏览,选择“受信任的根证书颁发机构”。
  5. 完成导入,彻底重启浏览器。

macOS(Safari / Chrome)

  1. 双击 ca.crt,加入“系统”钥匙串。
  2. 钥匙串访问,找到 MyLAN-CA,右键“显示简介”。
  3. “信任”里“使用此证书时”选“始终信任”。
  4. 输入密码确认。

Firefox(独立证书库)

  1. about:preferences#privacy → 证书 → “查看证书”。
  2. “证书颁发机构” → “导入” → 选 ca.crt。
  3. 勾选“信任由此证书颁发机构来标识网站”。

九、踩坑记录

现象原因解决
ERR_CERT_AUTHORITY_INVALID根 CA 未导入或导入位置错导入到“受信任的根证书颁发机构”
ERR_CERT_COMMON_NAME_INVALIDSAN 缺少访问用的域名/IP重新生成 san.ext,重签 server.crt
用 IP 访问报错但域名正常SAN 只写了 DNS补充 IP.1
502 Bad Gateway容器访问不到后端宿主机先 curl 后端确认可达
nginx -t 报证书路径错误挂载路径不对确认容器内路径为 /etc/nginx/ssl/server.crt
页面样式/JS 404后端返回绝对路径资源为对应前缀补充 location 代理
Firefox 仍报错Firefox 未导入 CA单独导入到 Firefox 证书库

十、经验总结

  1. 私有 CA 比单张自签名证书更优雅,客户端只导一次根证书,后续所有服务器证书自动被信任。
  2. SAN 是必须的,CN 已过时。Chrome 从某个版本开始完全忽略 CN,只看 SAN。
  3. ca.key 要离线保管,不要和 Nginx 部署在同一台机器上。
  4. 有效期不要超过 825 天,否则 iOS/macOS 会拒绝。
  5. 证书续期无需重新导入 CA,只需重签服务器证书并 nginx -s reload。
  6. 生产环境建议用 Let's Encrypt,Nginx 配置方式完全一致,仅替换证书文件即可。

十一、速查清单

服务端:

bash

cd ~/nginx-ssl/ssl
openssl genrsa -out ca.key 4096
openssl req -x509 -new -key ca.key -days 3650 -out ca.crt -subj "/CN=MyLAN-CA"
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr -subj "/CN=myapp.local"
# 写 san.ext(含 DNS.1 和 IP.1)
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key \
  -CAcreateserial -days 825 -sha256 -extfile san.ext -out server.crt

部署:

bash

cd ~/nginx-ssl
docker compose up -d
docker compose exec nginx nginx -t
docker compose exec nginx nginx -s reload

验证:

bash

curl --cacert ~/nginx-ssl/ssl/ca.crt https://192.168.1.100/
openssl verify -CAfile ca.crt server.crt

客户端:导入 ca.crt 到“受信任的根证书颁发机构”,重启浏览器。

结语

本文完整复盘了内网 HTTPS 改造的全流程,从私有 CA 建设到 Nginx 容器化部署,再到客户端信任配置。相比直接使用自签名证书,私有 CA 方案在可维护性和扩展性上都有明显优势。

关键词:私有 CA、Nginx、HTTPS、反向代理、Docker、自签名证书、SAN