尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

SSL证书与Nginx配置全指南:从自签、免费证书到浏览器信任

SSL证书与Nginx配置全指南:从自签、免费证书到浏览器信任 先说一件我今年已经碰到好几次的事同事搭了个内部系统Nginx、域名、端口全配好了结果浏览器一打开就是红色警告。检查了半天问题要么出在证书链没配全要么出在自签证书压根没被系统信任。SSL证书这个东西说难不难说简单也不简单真正卡人的地方往往不在Nginx配置而在证书本身怎么来、浏览器到底认不认。这篇文章就把整条链路讲透证书怎么生成、怎么签出来、怎么配置进Nginx以及最后一步——怎么让浏览器从拒绝访问变成小锁头。无论你是要给公网网站上HTTPS还是内网测试环境想消掉那个烦人的警告都能从这里找到能直接抄走的方案。1. SSL证书到底是什么Nginx为什么需要它1.1 证书解决的两个核心问题网络传输本质上是一封封信件在驿站间传递任何中间节点都可以拆开偷看。HTTP协议传的就是明文信件密码、Cookie、订单数据全都赤裸裸地暴露在链路上。SSL/TLS协议解决的就是信件加密和身份核验两个问题而SSL证书是这两个问题共同的基础设施。身份核验浏览器需要确认当前连接的服务器确实是example.com而不是某个仿冒站点。证书里写着域名、颁发者、有效期、公钥等信息数字签名保证了这些内容无法被篡改。加密通信证书里携带的公钥让客户端和服务器能安全地协商出一把对称加密密钥后续所有数据都用这把密钥加密传输。没有证书就算你在Nginx里强行开了SSL浏览器也会因为无法确认服务器身份而拒绝信任。这也是为什么配置SSL证书这个动作本质上不是写几行配置而是解决信任的问题。1.2 证书类型与适用场景按颁发方和用途常见的证书分三类证书类型颁发方浏览器是否默认信任适用场景公网CA证书Lets Encrypt、云厂商等商业CA是线上正式环境、对外网站自签名证书自己用openssl签否需手动导入测试环境、临时验证内网自建CA证书自己搭CA再做下级签名需把CA根证书导入客户端内网多台服务器、长期使用的内部系统这里有个常见误区公司内网系统用自签证书浏览器报错很多人第一反应是绕过警告继续访问。短时间可以但一旦有同事换了电脑、清了缓存警告又会回来。正确做法是搭一个内网CA把根证书分发到所有客户端一劳永逸。后面的章节我会分别讲这三种方案的落地方式。2. 三种获取SSL证书的方式按需选择2.1 自签名证书openssl一步到位测试环境最快的方式是直接用openssl生成自签名证书。一条命令搞定私钥和证书mkdir -p /etc/nginx/ssl cd /etc/nginx/ssl openssl req -x509 -nodes -newkey rsa:2048 \ -keyout example.key -out example.crt \ -days 365 \ -subj /CCN/STBeijing/LBeijing/OExample Inc/OUIT/CNexample.com简单解释下几个参数-x509直接输出自签名证书而不是生成证书签名请求CSR。-nodes私钥不加密这样Nginx启动时不用输入密码。生产环境如果要更高安全等级可以去掉这个参数但Nginx每次reload都要手动输密码实操中很少这么用。-days 365有效期一年测试环境够了。-subj证书主体信息其中CNexample.com是最关键的字段浏览器会拿它和你访问的域名比对。这里填IP也可以但要注意访问时用IP访问且证书的CN或SAN里必须包含这个IP。注意现在主流浏览器已经不怎么认CN字段了更认SANSubject Alternative Name。上面的命令只写了CNChrome新版可能还是会报错。稳妥的做法是额外加一个配置文件或在命令里通过-addext添加SAN扩展openssl req -x509 -nodes -newkey rsa:2048 \ -keyout example.key -out example.crt \ -days 365 \ -subj /CCN/STBeijing/LBeijing/OExample Inc/OUIT/CNexample.com \ -addext subjectAltNameDNS:example.com,DNS:www.example.com,IP:192.168.1.10这个坑我踩过不止一次早些年生成的证书连SAN都没有结果Chrome升级后直接报NET::ERR_CERT_COMMON_NAME_INVALID排查了半天才想起来是证书缺扩展字段。2.2 免费公网证书ACME协议与Lets Encrypt如果你的域名解析在公网且网站对外提供服务最省心的方式是申请Lets Encrypt免费证书。有效期90天但配置自动续期后基本可以做到装上之后再也不管。目前主流工具是certbot或者acme.sh。我更喜欢acme.sh因为它是纯shell脚本不依赖Python环境在各类Linux发行版上都能跑。以acme.sh为例安装并签发证书curl https://get.acme.sh | sh alias acme.sh~/.acme.sh/acme.sh acme.sh --issue -d example.com -d www.example.com --webroot /var/www/html签发成功后把证书安装到Nginx目录acme.sh --install-cert -d example.com \ --key-file /etc/nginx/ssl/example.key \ --fullchain-file /etc/nginx/ssl/example.crt \ --reloadcmd nginx -s reload--fullchain-file是关键它会把服务器证书和CA中间证书拼成一个文件避免后面出现证书链不完整的问题。--reloadcmd会在证书自动续期成功后主动reload Nginx让新证书生效。2.3 云厂商提供的免费证书阿里云、腾讯云等主流云平台都提供一年期的免费DV证书在控制台搜索SSL证书就能找到。申请流程一般是填写域名、选择验证方式DNS验证或文件验证、等待签发、下载证书文件。这类证书的优势是证书链完整签发后直接拿到crt和key两个文件。有的一年期到期后可以免费续期操作路径和申请时一样。支持泛域名*.example.com一张证书覆盖所有子域名。不足之处是要登录控制台手动操作不像ACME协议那样全自动。如果你有精力折腾一次自动续期我建议优先上Lets Encrypt如果想省心、不想维护脚本云厂商的免费证书也不差。我个人是混合使用对外核心域名用云厂商证书内部系统和测试域名用自建CA或Lets Encrypt。3. Nginx配置HTTPS的完整流程3.1 最基础的HTTPS server块拿到证书文件后Nginx配置其实很简单。核心只需要三个指令server { listen 443 ssl; server_name example.com; ssl_certificate /etc/nginx/ssl/example.crt; ssl_certificate_key /etc/nginx/ssl/example.key; root /var/www/html; index index.html; }关键点listen 443 ssl让Nginx在443端口启用SSL。新版Nginx也可以写成listen 443 ssl default_server;。ssl_certificate证书文件路径如果用acme.sh安装是fullchain文件。ssl_certificate_key私钥文件路径。写完配置后先校验再生效nginx -t nginx -s reloadnginx -t这一步务必养成习惯。很多人改了配置不校验就直接reload语法错了Nginx根本起不来反而把线上服务搞挂了。3.2 HTTP自动跳转HTTPS配好443后还要处理80端口的流量。最常用的做法是加一个server块把所有HTTP请求301到HTTPSserver { listen 80; server_name example.com; return 301 https://$host$request_uri; }这招的细节在于用$host而不是$server_name这样用户用IP访问时也能正确跳转不会因为server_name不匹配而跳到一个错误的域名上。如果你是反向代理场景比如Nginx在外面后面挂了Tomcat或其他后端服务443端口的server块就要加上proxy_passserver { listen 443 ssl; server_name example.com; ssl_certificate /etc/nginx/ssl/example.crt; ssl_certificate_key /etc/nginx/ssl/example.key; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $remote_addr; } }X-Forwarded-Proto要特别留意。后端的Tomcat或其他应用只有拿到这个头才知道你用的是HTTPS否则它会以为自己是纯HTTP环境生成出来的重定向和回调地址全是http://然后整个链路就乱了。3.3 安全加固参数一次配到位证书能用之后建议顺手把安全参数也加上。下面是经过实战验证的一组配置兼容性和安全性比较平衡server { listen 443 ssl; server_name example.com; ssl_certificate /etc/nginx/ssl/example.crt; ssl_certificate_key /etc/nginx/ssl/example.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; add_header Strict-Transport-Security max-age31536000; includeSubDomains always; add_header X-Content-Type-Options nosniff always; add_header X-Frame-Options SAMEORIGIN always; }几个参数的解释ssl_protocols TLSv1.2 TLSv1.3禁掉老旧的TLSv1.0和TLSv1.1这两个协议有多处已知漏洞还在用的话安全检查肯定过不了。ssl_session_cache shared:SSL:10m启用session cacheHTTPS握手太频繁时会大幅降低CPU开销。10m大约能存8万个session。Strict-Transport-SecurityHSTS头告浏览器接下来一段时间只允许用HTTPS访问防止中间人把HTTPS降级成HTTP。注意这个头一旦加上浏览器会严格执行如果证书配错了想临时切回HTTP用户端会一直跳转失败。第一次配置建议先不加这个头或者把max-age设小一点验证没问题再调大。4. 让浏览器信任证书的三种方案4.1 公网CA证书默认信任无需任何操作如果你用的是Lets Encrypt或云厂商签发的公网证书浏览器默认就会信任不需要做任何额外工作。原因在于你的证书是由一个根CA逐级签名下来的而各大操作系统内置了一系列根CA的公钥列表浏览器会顺着证书链一路验证上去最终发现签名链的根在可信列表里就判定为可信。这里唯一可能翻车的情况是证书链不完整。服务器只发了自己的证书没发中间CA证书浏览器找不到中间环节就会报证书链不完整NET::ERR_CERT_AUTHORITY_INVALID。这也是为什么我一直强调要用fullchain文件而不是只拿证书文件本身。4.2 自签证书手动导入客户端信任库自签名证书的信任问题是天生的——它没有上级CA浏览器只认自身签名而你的证书又不在系统信任列表里。解决思路只有一个把你的自签证书或者自建CA的根证书导入到客户端的系统信任库。以Windows为例导入流程双击.crt文件点安装证书。存储位置选择本地计算机。选择将所有的证书都放入下列存储点浏览选择受信任的根证书颁发机构。完成安装重启浏览器。macOS上操作类似双击证书文件在钥匙串访问里找到它右键选择显示简介把信任选项改为始终信任。Linux桌面环境下不同发行版路径不同。Ubuntu可以用sudo cp example.crt /usr/local/share/ca-certificates/ sudo update-ca-certificates这种方式适合一台两台机器。如果你的内网环境有几十台电脑、手机、平板再一台台手动导入就不现实了得用下面的方案。4.3 内网自建CA一劳永逸的信任方案内网多服务器场景最佳实践是自己搭一个内部CA用这个CA给所有内网服务器签证书然后把CA根证书分发到所有客户端。这样每次新增服务器只需要用CA签一张新证书客户端的信任列表完全不用动。自建CA的核心步骤分三步。第一步创建CA根证书mkdir -p /etc/pki/CA cd /etc/pki/CA openssl genrsa -out ca.key 2048 openssl req -x509 -new -key ca.key -days 3650 \ -subj /OInternal CA/CNInternal Root CA \ -out ca.crt第二步为服务器签发证书。这一步要先给服务器生成私钥和CSR然后在CSR里带上SANopenssl genrsa -out server.key 2048 openssl req -new -key server.key \ -subj /CNgitlab.example.internal \ -out server.csr openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key \ -CAcreateserial -out server.crt -days 825 \ -extfile (printf subjectAltNameDNS:gitlab.example.internal,IP:192.168.1.100)这里有效期写825天而不是365天是因为Apple和Google对超过825天的证书不予信任虽然内网CA不受公网CA规则约束但养成这个习惯没坏处。第三步把ca.crt分发到所有客户端导入方式见4.2。之后无论哪台Nginx服务器用这个CA签证书客户端都能正常信任。提示手机内网访问是自签/自建CA方案最容易遗漏的环节。iOS需要在设置-通用-关于本机-证书信任设置里额外打开完全信任Android也要手动安装CA证书。别等同事手机访问系统报错才想起来这一层。5. 实战排错常见问题速查表5.1 证书链不完整导致的信任失败浏览器报错NET::ERR_CERT_AUTHORITY_INVALID或证书颁发者错误十有八九是证书链问题。处理思路很简单检查服务器返回的证书链是否包含中间证书。用这条命令可以快速查看openssl s_client -connect example.com:443 -showcerts输出的证书列表中应该能看到至少两段BEGIN CERTIFICATE。如果只有一段说明你配置的证书文件里只有服务器证书缺了中间CA。解决办法是把中间证书和服务器证书拼接成一个文件cat server.crt intermediate.crt fullchain.crt然后ssl_certificate指向fullchain.crtreload即可。这也是Lets Encrypt的--fullchain-file帮你做的事情。5.2 nginx -t报错的几种典型情况nginx -t报错主要集中在证书路径和私钥匹配上我整理成一张速查表报错现象常见原因解决办法cannot load certificate证书文件路径写错或文件不存在检查ssl_certificate路径确认文件权限可读PEM_read_bio_PrivateKey私钥不是PEM格式或私钥与证书不匹配确认ssl_certificate_key指向正确的私钥文件the certificate ... does not match the private key证书和私钥不是同一对用自签或签发时配套的私钥重新核对[emerg] bind() to 0.0.0.0:443 failed80/443端口被占用查ss -lntp看是哪个进程占用了端口常见的是其他Nginx实例或Apache证书和私钥是否匹配也可以用一条命令验证openssl x509 -in server.crt -noout -pubkey | openssl md5 openssl rsa -in server.key -pubout 2/dev/null | openssl md5两次输出的MD5一致说明是同一对否则肯定报错。5.3 证书续期与自动更新Lets Encrypt证书有效期90天手动续期的话会经常忘。acme.sh默认装了cron任务证书到期前会自动续期并把新证书通过--reloadcmd触发Nginx reload整体不需要人工干预。验证自动续期是否正常acme.sh --list crontab -l | grep acme如果用的云厂商证书需要在到期前在控制台重新申请并下载替换Nginx里的证书文件后reload。这里我有个血泪教训云厂商证书的有效期是按自然年计算的到期那天正好是节假日网站HTTPS挂了半天才发现。建议在手机上设个提前30天的提醒或者干脆转Lets Encrypt让它全自动。还有一个细节容易被忽略证书续期后如果Nginx进程长时间没有reload会导致它一直握着旧证书直到重启才换新。所以无论用哪种方式续期都要记得reload一次Nginx。这篇文章从证书原理讲到自签/免费/云厂商证书的获取再到Nginx配置和浏览器信任最后给了排查思路。最后再分享一个我个人的小建议生产环境的证书文件权限一定要收紧。私钥文件设成600或640目录设成700别让Nginx worker进程之外的账号能读。证书这种东西表面上只是几行文本但一旦私钥泄露等于把加密的大门钥匙交了出去这才是真正要命的隐患。能用自动化续期的就别手动能用内网CA的别用一堆自签证书把信任关系一次性理清楚后面能省下大把时间。
返回列表