☰
SSL证书与Nginx配置全指南:从自签、免费证书到浏览器信任
2026/9/26 11:29:29 网站建设 项目流程

先说一件我今年已经碰到好几次的事:同事搭了个内部系统,Nginx、域名、端口全配好了,结果浏览器一打开就是红色警告。检查了半天,问题要么出在证书链没配全,要么出在自签证书压根没被系统信任。SSL证书这个东西,说难不难,说简单也不简单,真正卡人的地方往往不在Nginx配置,而在证书本身怎么来、浏览器到底认不认。

这篇文章就把整条链路讲透:证书怎么生成、怎么签出来、怎么配置进Nginx,以及最后一步——怎么让浏览器从"拒绝访问"变成"小锁头"。无论你是要给公网网站上HTTPS,还是内网测试环境想消掉那个烦人的警告,都能从这里找到能直接抄走的方案。

1. SSL证书到底是什么,Nginx为什么需要它

1.1 证书解决的两个核心问题

网络传输本质上是一封封信件在驿站间传递,任何中间节点都可以拆开偷看。HTTP协议传的就是明文信件,密码、Cookie、订单数据全都赤裸裸地暴露在链路上。SSL/TLS协议解决的就是"信件加密"和"身份核验"两个问题,而SSL证书是这两个问题共同的基础设施。

  • 身份核验:浏览器需要确认,当前连接的服务器确实是"example.com",而不是某个仿冒站点。证书里写着域名、颁发者、有效期、公钥等信息,数字签名保证了这些内容无法被篡改。
  • 加密通信:证书里携带的公钥,让客户端和服务器能安全地协商出一把对称加密密钥,后续所有数据都用这把密钥加密传输。

没有证书,就算你在Nginx里强行开了SSL,浏览器也会因为"无法确认服务器身份"而拒绝信任。这也是为什么"配置SSL证书"这个动作,本质上不是写几行配置,而是解决"信任"的问题。

1.2 证书类型与适用场景

按颁发方和用途,常见的证书分三类:

证书类型颁发方浏览器是否默认信任适用场景
公网CA证书Let's 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 "/C=CN/ST=Beijing/L=Beijing/O=Example Inc/OU=IT/CN=example.com"

简单解释下几个参数:

  • -x509:直接输出自签名证书,而不是生成证书签名请求(CSR)。
  • -nodes:私钥不加密,这样Nginx启动时不用输入密码。生产环境如果要更高安全等级,可以去掉这个参数,但Nginx每次reload都要手动输密码,实操中很少这么用。
  • -days 365:有效期一年,测试环境够了。
  • -subj:证书主体信息,其中CN=example.com是最关键的字段,浏览器会拿它和你访问的域名比对。这里填IP也可以,但要注意访问时用IP访问,且证书的CN或SAN里必须包含这个IP。

注意:现在主流浏览器已经不怎么认CN字段了,更认SAN(Subject Alternative Name)。上面的命令只写了CN,Chrome新版可能还是会报错。稳妥的做法是额外加一个配置文件,或在命令里通过-addext添加SAN扩展:

openssl req -x509 -nodes -newkey rsa:2048 \ -keyout example.key -out example.crt \ -days 365 \ -subj "/C=CN/ST=Beijing/L=Beijing/O=Example Inc/OU=IT/CN=example.com" \ -addext "subjectAltName=DNS:example.com,DNS:www.example.com,IP:192.168.1.10"

这个坑我踩过不止一次,早些年生成的证书连SAN都没有,结果Chrome升级后直接报"NET::ERR_CERT_COMMON_NAME_INVALID",排查了半天才想起来是证书缺扩展字段。

2.2 免费公网证书:ACME协议与Let's Encrypt

如果你的域名解析在公网,且网站对外提供服务,最省心的方式是申请Let's 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协议那样全自动。如果你有精力折腾一次自动续期,我建议优先上Let's Encrypt;如果想省心、不想维护脚本,云厂商的免费证书也不差。我个人是混合使用:对外核心域名用云厂商证书,内部系统和测试域名用自建CA或Let's 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 reload

nginx -t这一步务必养成习惯。很多人改了配置不校验就直接reload,语法错了Nginx根本起不来,反而把线上服务搞挂了。

3.2 HTTP自动跳转HTTPS

配好443后,还要处理80端口的流量。最常用的做法是加一个server块,把所有HTTP请求301到HTTPS:

server { listen 80; server_name example.com; return 301 https://$host$request_uri; }

这招的细节在于用$host而不是$server_name,这样用户用IP访问时也能正确跳转,不会因为server_name不匹配而跳到一个错误的域名上。

如果你是反向代理场景,比如Nginx在外面,后面挂了Tomcat或其他后端服务,443端口的server块就要加上proxy_pass:

server { 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-age=31536000; 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 cache,HTTPS握手太频繁时会大幅降低CPU开销。10m大约能存8万个session。
  • Strict-Transport-Security:HSTS头,告浏览器"接下来一段时间只允许用HTTPS访问",防止中间人把HTTPS降级成HTTP。注意这个头一旦加上,浏览器会严格执行,如果证书配错了想临时切回HTTP,用户端会一直跳转失败。第一次配置建议先不加这个头,或者把max-age设小一点验证没问题再调大。

4. 让浏览器信任证书的三种方案

4.1 公网CA证书:默认信任,无需任何操作

如果你用的是Let's Encrypt或云厂商签发的公网证书,浏览器默认就会信任,不需要做任何额外工作。原因在于,你的证书是由一个"根CA"逐级签名下来的,而各大操作系统内置了一系列根CA的公钥列表,浏览器会顺着证书链一路验证上去,最终发现签名链的根在可信列表里,就判定为可信。

这里唯一可能翻车的情况是证书链不完整。服务器只发了自己的证书,没发中间CA证书,浏览器找不到中间环节,就会报"证书链不完整"(NET::ERR_CERT_AUTHORITY_INVALID)。这也是为什么我一直强调要用fullchain文件,而不是只拿证书文件本身。

4.2 自签证书:手动导入客户端信任库

自签名证书的信任问题是天生的——它没有上级CA,浏览器只认自身签名,而你的证书又不在系统信任列表里。解决思路只有一个:把你的自签证书(或者自建CA的根证书)导入到客户端的系统信任库。

以Windows为例,导入流程:

  1. 双击.crt文件,点"安装证书"。
  2. 存储位置选择"本地计算机"。
  3. 选择"将所有的证书都放入下列存储",点"浏览",选择"受信任的根证书颁发机构"。
  4. 完成安装,重启浏览器。

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 "/O=Internal CA/CN=Internal Root CA" \ -out ca.crt

第二步,为服务器签发证书。这一步要先给服务器生成私钥和CSR,然后在CSR里带上SAN:

openssl genrsa -out server.key 2048 openssl req -new -key server.key \ -subj "/CN=gitlab.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 "subjectAltName=DNS: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.crt,reload即可。这也是Let's 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 证书续期与自动更新

Let's Encrypt证书有效期90天,手动续期的话会经常忘。acme.sh默认装了cron任务,证书到期前会自动续期,并把新证书通过--reloadcmd触发Nginx reload,整体不需要人工干预。

验证自动续期是否正常:

acme.sh --list crontab -l | grep acme

如果用的云厂商证书,需要在到期前在控制台重新申请并下载,替换Nginx里的证书文件后reload。这里我有个血泪教训:云厂商证书的有效期是按自然年计算的,到期那天正好是节假日,网站HTTPS挂了半天才发现。建议在手机上设个提前30天的提醒,或者干脆转Let's Encrypt让它全自动。

还有一个细节容易被忽略:证书续期后,如果Nginx进程长时间没有reload,会导致它一直握着旧证书,直到重启才换新。所以无论用哪种方式续期,都要记得reload一次Nginx。


这篇文章从证书原理讲到自签/免费/云厂商证书的获取,再到Nginx配置和浏览器信任,最后给了排查思路。最后再分享一个我个人的小建议:生产环境的证书文件,权限一定要收紧。私钥文件设成600或640,目录设成700,别让Nginx worker进程之外的账号能读。证书这种东西,表面上只是几行文本,但一旦私钥泄露,等于把加密的大门钥匙交了出去,这才是真正要命的隐患。能用自动化续期的就别手动,能用内网CA的别用一堆自签证书,把信任关系一次性理清楚,后面能省下大把时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询