这一阵子连着帮朋友处理了不少Nginx配置SSL证书的事,发现大家最容易卡住的点反而不在证书生成,而在“让浏览器信任证书”这一步。证书文件不就两个文件嘛,配上不就行了?实际搞起来你会发现,自签名证书、免费证书、证书链、中间证书、SAN扩展,每一环都可能让你在浏览器里看到一串看不懂的报错。这篇文章就把我在Nginx上生成SSL证书、配置HTTPS、最终让浏览器信任证书的完整流程讲清楚。从自签名到Let's Encrypt免费证书,从配置文件到信任库导入,从续期到排错,个人博客、小程序后端、企业测试环境都能直接照着做。
1. 先搞懂Nginx和SSL证书的配合逻辑
1.1 没有SSL证书时,请求到底在裸奔什么
很多人觉得HTTP和HTTPS的区别就是“多了个S”,但实际差距非常大。HTTP请求是明文传输,你的账号、密码、Cookie、表单提交内容,在传输链路上任何一个能抓到包的人都能直接看到。尤其现在WiFi环境复杂,公共网络里抓包门槛很低,开着Wireshark就能看到隔壁请求里的明文内容。HTTPS就是在HTTP外面包了一层TLS加密,让数据在传输过程中变成密文,即使被截获也无法直接解读。
Nginx在这套体系里的角色,是TLS握手时的“服务端主角”。当浏览器访问https://example.com时,Nginx需要把自己的证书和公钥发给浏览器,双方协商出一个对称加密密钥,之后的HTTP数据都使用这个密钥加密传输。没有证书,Nginx就没办法完成这套握手,浏览器自然也不会继续发请求。所以配置SSL证书并不是“为了合规”或“为了绿锁”,而是让数据从客户端到服务器这段路真正安全下来的前提。
1.2 TLS握手流程:Nginx只需要做好两件事
TLS握手完整流程比较长,但站在Nginx配置的角度,你只需要理解两个关键动作。
第一个动作,是Nginx向浏览器出示证书。这个证书里包含了域名信息、公钥、有效期、签发者等信息。第二个动作,是浏览器验证证书是否可信。浏览器收到证书后,会检查证书是不是由自己信任的根证书颁发机构签发的,域名是否匹配,证书是否过期,是否被吊销。如果都通过,浏览器才会继续后续的密钥协商。
所以“让浏览器信任证书”的本质,不是Nginx能控制的,而是取决于“证书链是否到达浏览器信任的根”。Nginx的职责只是把正确的证书文件(一般是一份fullchain.pem)完整地传给浏览器。如果你给它配置的证书链不完整,或者用了一个自签名证书但没导入信任库,那无论Nginx配置多完美,浏览器照样给你大红色警告。理解这一点之后,很多配置问题其实都能自己推出来了。
1.3 证书类型怎么选:自签名、免费DV、商业OV/EV
做技术选型前,先把三类证书的差异搞清楚。下面这个表格是我自己整理的,基本覆盖了常见场景。
| 证书类型 | 颁发对象 | 浏览器默认信任 | 成本 | 申请耗时 | 适合场景 |
|---|---|---|---|---|---|
| 自签名证书 | 自己给自己签发 | 不信任,需要手动导入 | 0 | 几分钟 | 本地开发、内网测试、临时环境 |
| 免费DV证书 | CA机构验证域名所有权 | 信任 | 0 | 几分钟到几小时 | 个人博客、中小站点、API服务 |
| 商业OV/EV证书 | CA机构验证企业身份 | 信任 | 每年几百到几千元 | 1-5个工作日 | 企业官网、电商、金融类站点 |
自签名证书不是不能用,但请务必把它限制在“你自己可控客户端”的环境里。我在内网测试环境用自签名证书用了很久,只要把所有客户端系统信任库导入一次,访问效果和正式证书几乎没有区别。生产环境我强烈建议直接用Let's Encrypt这类免费DV证书,自动续期做好之后,比商业证书省心得多。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 生成SSL证书:自签名与ACME免费证书的完整实操
2.1 用OpenSSL生成自签名证书,注意SAN扩展
自签名证书的生成命令看起来很简单,但有个大坑:现代浏览器早就忽略了证书里的CN(Common Name)字段,不再把它作为域名校验依据,而是必须看subjectAltName(SAN)扩展。如果你只用一个简单的openssl req -x509 -nodes -days 365 -keyout key.pem -out cert.pem -subj "/CN=example.com"命令生成证书,Chrome和Firefox大概率会报“证书名称不匹配”或者“NET::ERR_CERT_COMMON_NAME_INVALID”。
正确的做法是准备一个带SAN扩展的配置文件。下面是我常用的方式,先创建一个目录保存证书文件:
bash复制mkdir -p /etc/nginx/ssl && cd /etc/nginx/ssl
然后生成私钥和CSR(证书签名请求):
bash复制openssl genrsa -out example.com.key 2048
cat > san.cnf <<'EOF'
[req]
distinguished_name = dn
req_extensions = v3_req
prompt = no
[dn]
C = CN
ST = Beijing
L = Beijing
O = Example Inc
CN = example.com
[v3_req]
keyUsage = keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = example.com
DNS.2 = www.example.com
IP.1 = 192.168.1.10
EOF
openssl req -new -key example.com.key -out example.com.csr -config san.cnf
这里关键在[alt_names]段,你打算用哪个域名访问,就必须把哪个域名或IP加进去。IP地址要用IP.1这种格式,域名用DNS.1。然后基于CSR生
