☰
Nginx配置SSL证书完整指南:HTTPS跳转与安全加固实战
2026/10/14 1:52:59 网站建设 项目流程

前阵子有位做独立站的开发者找我,说他的站一直被人提醒“不安全”,点进去之后浏览器地址栏直接标红,用户还没看到页面先被吓退了一半。他问我能不能帮他配上SSL证书,让站点走HTTPS。这事情我自己前后经手过很多服务器,正好把整个流程从头到尾捋一遍。

这篇不是那种只贴一段配置就完事的速食教程。我会把证书选型、文件核对、nginx配置细节、HTTP强制跳转、上线后的自检手段,以及容易踩的坑全部讲清楚。无论你是只维护一台个人博客,还是管理几台生产环境服务器,照着这篇文章的思路做下去,都能稳稳妥妥地把HTTPS跑起来。

1. 上HTTPS之前:先搞清楚证书和HTTP/HTTPS的关系

1.1 为什么说现在的HTTP服务约等于裸奔

很多人觉得,我这站又没什么敏感信息,HTTP用得好好的,何必折腾。这是对HTTPS最大的误解。HTTP传输是明文的,数据从用户浏览器到服务器之间的每一跳,只要有人做了链路截获,就能看到完整的请求内容,包括登录密码、Cookie、聊天内容,甚至页面里被篡改成什么都能被改。

我印象很深的一回,在某个公共网络条件下测试一台没上证书的测试机,打开页面时发现HTML里被塞了一段莫名其妙的脚本,明显是中间设备干的。那台机没有证书,所有流量都裸奔,等于把自己家的门敞开了,路人谁都能伸手进去翻一圈。至于HTTPS本身,相当于把数据装进了一个只有双方有钥匙的密封箱,中间人看到的只会是一串乱码。

还有一个重要的点:HTTPS已经不是加分项了。现代浏览器对HTTP页面的态度越来越苛刻,有些会直接标记为“不安全”,部分产品内置的浏览器策略也会默认不允许调用HTTP资源。可以说,今天新上线一个对外服务的站点,如果还带着纯HTTP出去,从第一步就落后了。

TLS握手的过程用生活类比来说很简单。浏览器和对端服务器先互相亮出家底,协商加密套件,确认完身份之后,再生成一把临时的“会话钥匙”,之后所有数据都用这把钥匙加密传输。这个“确认身份”的环节靠的就是SSL证书。

1.2 证书选型:免费、付费、自签名,哪种才是真的适合你

证书的来源决定了浏览器认不认你的服务器。目前常见的有三类:免费公共CA签发的证书、商业CA签发的证书、自签名证书。

证书类型获取成本浏览器信任适合场景主要注意点
免费公共CA证书免费受主流浏览器信任个人站、中小业务、测试环境有效期短,通常90天,需要自动续期
商业CA证书按年付费受主流浏览器信任企业官网、金融业务、电商等审核流程长一些,价格差异大,可买多年
自签名证书免费不受浏览器信任内网系统、开发联调、离线环境首次使用要手动导入信任链,浏览器会有安全提示

对绝大多数普通站点来说,免费公共CA证书已经完全够用了。它和商业证书在浏览器里的信任表现几乎没有区别,都是同一个根信任体系验下来的。我在生产环境里带HTTPS的服务,很多就是用免费证书跑着的,上线两年多没出过信任问题。

真正要劝阻的是另一种情况:有人为了省事,在内网环境生成一个自签名证书,然后直接配到线上对外域名去用。结果用户打开页面就该弹红色警告,再解释也解释不清。自签名证书不是不能用,而是要清楚它的适用边界:内部测试、开发环境没问题,对外服务就不要拿它糊弄用户了。

2. 证书文件拿到手之后,先确认三件事再动手

2.1 私钥和证书是否匹配,别等到浏览器报错才发现

证书签发机构返回给你的,通常是一组文件,常见的是fullchain.pem(完整证书链)和yourdomain.key(私钥)。这里我见过最多的低级错误,就是下载文件没错,但私钥和证书对不上。证书和私钥是成对生成的,你拿着A证书配B私钥,nginx启动根本不会报明晃晃的错,但握手的时候客户端的校验永远过不去。

所以我的习惯是先校验再动配置文件。用几个命令就能确认。

# 提取证书里的公钥摘要 openssl x509 -in /etc/nginx/ssl/yourdomain.com/fullchain.pem -pubkey -noout | sha256sum # 提取私钥对应的公钥摘要 openssl pkey -in /etc/nginx/ssl/yourdomain.com/yourdomain.key -pubout -outform pem | sha256sum

两条命令打出来的哈希值如果一模一样,说明证书和私钥匹配,可以进入下一步。如果不一致,回到证书商那里把正确的私钥重新下载下来,别抱着侥幸心理继续往下配。

顺带看一眼证书的内容信息,包括域名、签发机构、有效期,也很有用。

openssl x509 -in /etc/nginx/ssl/yourdomain.com/fullchain.pem -noout -subject -issuer -dates

看到Not After后面那个日期,就是你证书的到期时间。我会在博文后半部分讲续期,但提前把到期时间刻在脑里,能省很多事。

2.2 证书链文件的作用:为什么不能只放一张证书

还有一个文件需要理解,就是ca_bundle或chain.pem这类中间证书文件。很多第一次配置的人会问,我把证书文件拆开看内容,里面好像就是一张证书按Base64编码的文本,为什么还要这个中间的“链”文件?

这个要讲清楚。浏览器内置的是最顶层的根证书,而服务器提交的通常是站点证书。站点证书不是由根CA直接签发的,中间还站着一个或几个“中间CA”。如果服务器只抛站点证书,不把中间证书也发过去,浏览器顺着证书链找上去,发现找不到能连到根证书的那一环,就会判定证书不可信。

fullchain.pem这个文件之所以推荐使用,就是因为它把“站点证书”和“中间证书”串在了一个文件里。服务器回给浏览器时,正好补齐了整条信任路径。你在其他教程里看到有人只填了站点证书那一段,回头看浏览器信任报错,很多都是这一环漏了。

3. nginx的SSL配置逐行拆解

3.1 最简可用配置:一个server块搞定

证书文件确认无误后,就可以开始改nginx配置了。以静态站点为例,最简可用的server块长这样。

server { listen 443 ssl; server_name yourdomain.com www.yourdomain.com; ssl_certificate /etc/nginx/ssl/yourdomain.com/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/yourdomain.com/yourdomain.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 1d; ssl_stapling on; ssl_stapling_verify on; location / { root /var/www/yourdomain.com; index index.html; } }

如果你是给后端接口做反向代理,location块长这样。

server { listen 443 ssl; server_name api.yourdomain.com; ssl_certificate /etc/nginx/ssl/api.yourdomain.com/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/api.yourdomain.com/yourdomain.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; location / { proxy_pass http://127.0.0.1:8080; 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; } }

关于ssl_protocols,这里只开启了TLSv1.2和TLSv1.3,有意把TLSv1.0和TLSv1.1关掉了。原因很简单,这两个老版本协议已经暴露过多个严重漏洞,主流浏览器早就不再使用。至于要不要担心老设备访问不了,请参见我后面写的第6节。

ssl_ciphers那串值,对应的是现代浏览器普遍支持的加密套件。不需要背下来,直接复制这段配置一般没问题,它已经把优先ECDHE密钥交换、使用GCM模式的策略落实了。关于ssl_prefer_server_ciphers on这一行是否还重要,在新版nginx里作用比以前弱了,但保留下来对老客户的兼容性没坏处。

3.2 配置文件该放哪,软链接怎么建,验证和重载别省略

配置文件的具体路径,各家系统镜像略有差异。

操作系统或发行版常用目录说明
Debian / Ubuntu/etc/nginx/sites-available/+/etc/nginx/sites-enabled/sites-available放原始文件,sites-enabled里用软链接启用
CentOS / RHEL/etc/nginx/conf.d/目录下所有以.conf结尾的文件会被自动加载
源码编译安装/usr/local/nginx/conf/取决于编译时的prefix参数

在Debian系里,我常这样操作:配置文件写到sites-available,然后用软链接把它“点亮”。

ln -s /etc/nginx/sites-available/yourdomain.conf /etc/nginx/sites-enabled/yourdomain.conf

改了配置不验证就重载,是在生产服务器上最危险的动作。好在nginx提供了非常方便的检查机制。

nginx -t

看到nginx: configuration file /etc/nginx/nginx.conf test is successful,才允许执行平滑重载。

nginx -s reload

reload是平滑重载,不会中断正在处理的请求,对生产业务几乎没有影响。如果nginx -t报错,它会明确告诉你在哪个文件第几行出了问题。这时候老老实实回去改配置,改完再验证,再重载。

3.3 顺手做起来的小优化:会话复用、OCSP Stapling

有几个不算必须但强烈建议配上的选项,对性能和首屏体验有实在的帮助。

ssl_session_cache和ssl_session_timeout负责TLS会话的复用。TLS握手的开销不小,尤其对频繁发起新连接的应用来说。配置了session cache之后,同一条客户端在一段时间内复用之前的会话参数,可以省掉一次完整的握手流程。

ssl_stapling on则是把OCSP查询结果主动“带”给浏览器。没有开staple时,许多浏览器在拿到证书后,还会自己再去OCSP服务端查一次证书吊销状态,等于多了一段网络往返时间。开了stapling并配上ssl_stapling_verify后,这个查询结果由服务器缓冲区直接交付,页面建立连接的时间能明显降下来。

这里提醒一句:开OCSP Stapling时,如果服务器访问OCSP服务端比较慢或不通,有些版本的nginx会在错误日志里持续报警告。不影响页面正常打开,但日志排查时会有点干扰。在检测站点里确认stapling状态为“Good”即可。

4. 把HTTP流量安全引导到HTTPS

4.1 return 301 vs rewrite,别用错方式跳转

配好了443端口的HTTPS服务,原来的HTTP服务还停在那里。如果不做任何处理,用户输域名时浏览器默认走80端口,看到的还是老页面。正确做法是把80端口的请求全部引导到HTTPS上来。

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

这里为什么要用return 301而不是rewrite,我明确讲一下。用rewrite写跳转时,经常出现写法不当导致路径拼接错乱的情况,而且rewrite本质上还是在nginx内部重新处理请求,性能和逻辑上都不如直接返回301清晰。return 301 https://$host$request_uri;把完整的URL构造交给nginx的变量系统,原路径和查询参数都能原样带过去,是最稳的做法。

如果你本来就不需要80端口对外服务,也可以直接在防火墙上把80端口关掉。不过从用户体验角度考虑,我现在仍然会保留这一个80端口的server块,做一次清爽的301跳转。

4.2 重定向循环是怎么发生的,以及怎么预防

配好后最常见的故障现象是:访问域名,浏览器一直提示“将您重定向的次数过多”,也就是重定向循环。我排查过的好几个案例,原因都出在多个server块相互干扰。

最常见的循环场景是:原本有一个监听443的server块配置了跳转逻辑,比如在里面写了return 301 https://$host$request_uri;,然后又额外写了一个监听80的server块,两边互相指。结果浏览器访问HTTPS时,服务器让你再跳一次HTTPS,永远跳不完。

另一个容易出问题的点是,如果你在配置反代时把proxy_set_header X-Forwarded-Proto $scheme写错了值,而后端应用又基于这个头判断“当前协议不是HTTPS就强制跳转”,同样会形成循环。检查时先注释掉反向代理里的跳转逻辑,确认到底是nginx层循环还是后端应用循环。

预防核心就一句话:清理掉任何“443端口server块里的协议跳转语句”。443端口只需要服务HTTPS内容,把跳转职责全部交给80端口的server块,职责单一,就不容易出现互相打架的场面。

5. 配置完成后,按这套流程自检一遍

5.1 本地验证命令与curl输出解读

配置重载后不要急着到处宣传,先在本地命令行做一个快速验证。我最常用的命令是:

curl -I https://yourdomain.com

响应里如果能看到HTTP/2 200,说明HTTPS服务已经正常响应了。再做一次带握手细节的检查:

curl -v https://yourdomain.com/ 2>&1 | grep -i "SSL connection"

能看到类似SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384这样的输出,说明TLS协商成功。如果你是临时服务器没配DNS,也可以用--resolve参数把域名解析指向本机IP来测试。

curl -I --resolve yourdomain.com:443:127.0.0.1 https://yourdomain.com

还有一条命令,能直接检查证书链是否完整:

echo | openssl s_client -connect yourdomain.com:443 -servername yourdomain.com 2>/dev/null | openssl x509 -noout -subject -issuer -dates

看到证书的subject、issuer和有效期都正常,再往下走。

5.2 上线后务必跑一遍的检测项

命令行验证完,我会建议再到第三方在线检测平台里做一次完整体检。这类平台能自动模拟全球多个节点的访问,重点输出几类关键信息。

  • 证书信任链:提示“证书信链完整”才算合格。如果显示“缺少中间证书”,回到第2节把fullchain.pem替换上去。
  • 协议支持:确认开启了TLSv1.2和TLSv1.3,检测结果里通常还会顺带提示哪些老版本协议不建议启用。如果网站只面向国内用户,也可以关闭其余杂项,保持清晰。
  • OCSP Stapling:前面配置了这个选项,检测结果里“OCSP stapling”状态应为正常。
  • HSTS状态:HSTS是让浏览器强制走HTTPS的安全头。可以等HTTPS稳定运行一段时间后再开启,详见下一段。

HSTS的配置方式是给HTTPS响应增加一组响应头:

add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

注意一点:HSTS生效后,浏览器会把该域名的HTTP请求直接改写成HTTPS请求,且持续时间内无法轻易取消。所以我会先确认HTTPS整体稳定,再上这个头。测试环境不要开,避免开发调试时被本地缓存拦住。

5.3 浏览器常见的证书报错对照表

在线检测没问题,不代表所有用户环境都顺利。浏览器端的报错信息,对应的问题基本都能在这个表格里找到答案。

报错信息常见原因处理方法
ERR_CERT_DATE_INVALID服务器时间不对或证书过期检查系统时间,或重新签发证书并替换
ERR_CERT_AUTHORITY_INVALID证书链缺失或安装的是自签名证书补上中间证书/更换为受信任CA签发的证书
ERR_SSL_PROTOCOL_ERRORTLS版本不兼容或加密套件不匹配检查ssl_protocols与ssl_ciphers配置
ERR_CERT_COMMON_NAME_INVALID证书域名和访问域名不一致确认证书签发时是否正确写入访问域名
ERR_SSL_VERSION_OR_CIPHER_MISMATCH客户端与服务器没有共通加密算法谨慎调整ciphers,保留现代通用加密套件

6. 上线后还会遇到的几个坑:续期、兼容性、排障思路

6.1 证书到期前,续期流程怎么做到自动化

免费证书的一个特点就是有效期短,动不动三个月就过期。很多人配置完成后就忘了这茬,直到某天发现浏览器红了才想起来。所以自动续期这件事必须在配置完成当天就安排好,不能靠记忆力。

思路是:部署一个定时任务,每隔几天检查证书剩余天数,小于阈值时执行续期命令,完成后reload nginx。下面是一个基于手动脚本的简化示例,你只需把续期命令换成自己实际使用的工具。

#!/bin/bash CERT_PATH="/etc/nginx/ssl/yourdomain.com/fullchain.pem" REMAIN_DAYS=1000 if [ -f "$CERT_PATH" ]; then EXP_DATE=$(openssl x509 -in "$CERT_PATH" -noout -enddate 2>/dev/null | cut -d= -f2) REMAIN_DAYS=$(($(date -d "$EXP_DATE" +%s) - $(date +%s))) REMAIN_DAYS=$((REMAIN_DAYS / 86400)) fi if [ "$REMAIN_DAYS" -lt 30 ]; then # 此处替换为你的证书续期命令 /usr/bin/certbot renew --quiet # 如果有新证书了,再重载 nginx nginx -s reload fi

配上crontab每两天跑一次:

0 3 */2 * * /root/renew_cert.sh

这里有一个容易忽略的细节:续期工具生成的证书文件名可能带时间戳或随机后缀,每次续期都会生成新文件。如果你的nginx配置指向的路径是固定的软链接,那没问题;如果路径指向实际文件名,则续期后必须把文件名替换或者更新nginx.conf。所以我会把配置指向目录里的固定路径,续期脚本里再统一清理旧文件,避免配置路径失效。

6.2 旧设备和旧客户端的TLS兼容问题

禁用TLSv1.0和TLSv1.1之后,确实会有一些老设备访问不了。哪些设备受影响?典型的是几年前的老年机内置浏览器、某些老版本安卓WebView、还有少量旧款收银机和门禁设备。

我自己的处理原则是:公开站点坚决向TLSv1.2看齐,不为极少数老设备降低安全线。因为这些旧协议本身已经不可信,明文传输的漏洞在那个体系里无解。如果你确实需要照顾非常老的客户端,又不想大幅降级,可以单开一个端口或一个独立的server块,只给那部分客户端使用老协议,把这个口子缩到最小。大多数情况下,统计日志里老设备占比低得可以忽略。

6.3 从外到内逐步排除的思路:遇到打不开该查什么

HTTPS服务上线后如果遇到用户反馈打不开,我的排查顺序固定是从外向里,一层一层缩小范围。

  1. 先看网络层。在本地执行curl -v https://yourdomain.com,如果提示超时或连接被拒绝,用telnet yourdomain.com 443看看443端口通不通。网络不通先查防火墙、云安全组、监听地址。
  2. 再看服务层。ps aux | grep nginx确认nginx进程在跑。ss -lntp | grep 443确认nginx在监听443端口。如果监听在0.0.0.0:443,外网才能访问到。
  3. 再看配置层。nginx -t确认配置语法没有问题。然后打开error日志:
tail -f /var/log/nginx/error.log

如果是证书文件读取失败,日志里会明确报错路径和权限问题。证书文件的读取权限要注意,私钥通常需要设置成只有root可读,nginx通过master进程读取没问题。 4. 最后看证书层。用echo | openssl s_client -connect yourdomain.com:443 -servername yourdomain.com查看是否握手成功,结合前面第2节的比对方法判断证书是否被正确放置在服务器上。

这套排查链路我实践过很多次,大多数HTTPS故障都能被这几步定位到具体环节。从外到内,每一层验证通过就向下推进,不容易漏掉问题。

说到底,nginx配SSL证书这件事本身不难,难的是你对文件、协议、浏览器信任链、续期机制这些细节有没有完整的理解。按照这篇文章的顺序走一遍,配置完再做一轮系统自检,基本就能把HTTPS这个事彻底搞定。我也建议你在自己另外一台服务器上多练几遍,直到不用看文档也能默写出整段配置为止——后面真正接手业务时,你会感谢这个很笨但管用的习惯。

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

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

立即咨询