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

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

前言

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

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

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

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

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

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

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

二、整体架构

text

css 复制代码
客户端浏览器 --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
访问 IP 192.168.1.100
后端服务 app.internal:8000/

四、目录结构

text

vbscript 复制代码
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

vbnet 复制代码
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

vbnet 复制代码
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

ini 复制代码
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

objectivec 复制代码
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

vbscript 复制代码
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

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

perl 复制代码
server {
    listen 80;
    server_name myapp.local 192.168.1.100;
    return 301 https://$host$request_uri;
}

HTTPS 主服务:

nginx

ini 复制代码
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

bash 复制代码
location /vna/ {
    proxy_pass http://app.internal:8000/vna/;
}

七、启动与验证

bash

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

javascript 复制代码
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_INVALID SAN 缺少访问用的域名/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

vbnet 复制代码
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

bash 复制代码
cd ~/nginx-ssl
docker compose up -d
docker compose exec nginx nginx -t
docker compose exec nginx nginx -s reload

验证:

bash

javascript 复制代码
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

相关推荐
AllFiles1 小时前
K8s Node Exporter 异常 Write 超时排查实录
运维·后端·kubernetes
虎头金猫5 天前
4K 视频总卡在公网带宽?用 N1 + OpenList 把网盘播放链路重新理顺
运维·服务器·网络·python·容器·beautifulsoup·pandas
还是大剑师兰特5 天前
解决nginx错误:http://localhost:7000正常,http://localhost:7000/map 报错404
nginx·大剑师
AI职业加油站6 天前
AI智能体应用工程师证书:政策红利下的职业新风口
大数据·运维·人工智能·学习·职场发展
此冬歌咏6 天前
K8s 节点故障实战:优雅驱逐 31 秒,硬故障 331 秒,以及那个永远 Pending 的 Pod
运维·k8s
xing-xing6 天前
Docker容器中Nginx站点根目录网页配置访问
nginx·docker
-梅6 天前
linux(8) 软硬链接
linux·运维·服务器
张洛闻Eren6 天前
k8s云原生【第十课】:水平 Pod 自动扩缩容
运维·数据库·云原生·kubernetes·github
其实防守也摸鱼6 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透