简单来说,HTTPS 现在就是互联网的门面,没它浏览器直接给你挂个大红叉,用户看着就心里发毛。作为全栈工程师,咱们在部署服务的时候,Nginx 肯定是绕不开的。现在 Nginx 的主线版本都到 1.27.3(2024年10月发布)了,这货对 TLS 的支持已经非常完善了。
很多新手一看到 SSL 配置就头大,觉得参数巨多。其实咱们搞个最基础的 HTTPS 服务,真没那么复杂。面试的时候也经常问:Nginx 实现 HTTPS 的最小配置包含哪几个指令? 答案就三个:ssl_certificate(指定证书)、ssl_certificate_key(指定私钥)、还有 listen 443 ssl(监听端口并开启 SSL)。
咱们直接上代码,看一个最精简、能跑起来的配置。假设你已经有了证书文件 example.com.crt 和私钥 example.com.key,放在了 /etc/nginx/ssl/ 目录下。
值得留意的是,光有上面的配置,用户如果输入 http://example.com 是访问不到的,因为 HTTP 走的是 80 端口。咱们还得加一个 80 端口的 server 块,专门用来做跳转。这也是面试常客:Nginx 如何配置 HTTP 到 HTTPS 的强制跳转?
这里有个🔧 实战技巧:千万别在 return 301 后面写死域名,比如 return 301 https://example.com$request_uri;,万一你以后改域名或者加个二级域名,还得回来改配置。用 $host 变量是最稳妥的,它会自动匹配请求头里的 Host 字段。
还有个坑得提醒一下,很多老教程里会在 listen 443 后面加 http2,像这样 listen 443 ssl http2;。在 Nginx 1.25.1 之后的版本,其实不需要显式指定了,只要 Nginx 编译时带了 HTTP/2 模块,它会自动在 SSL 连接上协商 ALPN 来启用 HTTP/2。不过为了兼容性,写上也没啥坏处。
打个比方,证书就是个文本文件,但格式得对。通常是 PEM 格式。如果你拿到的证书是 .pem 或者 .crt 都行,只要内容是 -----BEGIN CERTIFICATE----- 开头的就没问题。私钥同理,得是 -----BEGIN PRIVATE KEY----- 或者 -----BEGIN RSA PRIVATE KEY-----。
另外,私钥文件的权限一定要控好!Nginx 启动用户(通常是 www-data 或 nginx)需要能读取这个文件,但其他用户不能乱看。建议把私钥权限设为 600:
不然重启 Nginx 的时候,日志里可能会报权限拒绝的错误,这种低级错误咱们得避免。
搞 HTTPS 最烦的是啥?买证书!以前一张通配符证书动不动几千块,对小项目来说肉疼。现在咱们都用 Let's Encrypt 的免费证书,配合 Certbot 这个神器,简直是开发者的福音。打个比方,Certbot 就是个自动化脚本,帮你申请、配置、续期一条龙服务。
现在的趋势就是 ACME v2 深度集成,虽然 Nginx 还没做到完全原生内置(还得靠 Certbot 这种客户端),但自动化程度已经很高了。咱们来实操一下,假设你的服务器是 Ubuntu/Debian 系统,跑的是最新的 Nginx。
第一步,先装 Certbot 和 Nginx 插件。别用 apt-get install certbot 这种太老的源,有时候版本跟不上。
装完之后,直接一条命令搞定:
运行后它会问你几个问题:
2(Redirect),它会自动帮你改 Nginx 配置,把刚才咱们写的那个 80 端口跳转给配好。执行完之后,你再去看看你的 Nginx 配置文件,会发现 Certbot 已经很“贴心”地把证书路径和密钥路径都给你填进去了,甚至帮你加了一些基础的 SSL 参数。
Let's Encrypt 的证书有效期只有 90 天,这是为了防止私钥泄露后长期有效。别担心,Certbot 装完后会自动在系统的定时任务(crontab)里加一个脚本,每天检查两次,快过期了就自动续。
不过,咱们做开发的,不能把宝全压在自动化上,得自己验证一下续期能不能跑通。运行这个命令(加 --dry-run 是模拟运行,不会真的申请证书,很安全):
如果看到 "Congratulations, all renewals succeeded" 这种字样,说明你的续期机制是通的。
💡 经验总结:虽然 Certbot 很智能,但它有时候会“手贱”改乱你的 Nginx 配置。如果你对配置文件的格式有严格要求,建议使用 certonly 模式,只让 Certbot 下载证书,不修改 Nginx 配置。
然后你自己手动去 Nginx 配置里指定路径。Let's Encrypt 的证书默认会存在 /etc/letsencrypt/live/example.com/ 目录下。你的配置得这么写:
社区里讨论最多的坑就是权限问题。Certbot 运行时需要读取 /etc/letsencrypt 目录,而且 Nginx 也需要读取里面的文件。如果你是用 root 跑的 Certbot,但 Nginx 用的是普通用户,可能会遇到权限问题。
还有一个坑,Certbot 在做验证的时候,会临时启动一个服务占用 80 端口,或者让 Nginx 临时响应一个请求。如果你的 80 端口被其他程序(比如 Docker 容器、或者别的 Web 服务)占用了,就会失败。所以运行 Certbot 前,确保 80 端口是空闲的,或者 Nginx 是正常运行的。
另外,如果你在 Kubernetes 里用 Nginx Ingress,那就别用 Certbot 了,直接用 cert-manager 这个组件,它是 K8s 生态里专门干这个的,配合 Nginx Ingress 控制器,那是相当丝滑。
基础配置跑通了,证书也自动续期了,这就结束了吗?作为一个有追求的全栈工程师,咱们得追求极致。现在的版本(比如 Nginx 1.27.3)默认都支持 TLS 1.3 了,这玩意儿比 TLS 1.2 握手快多了,而且更安全。咱们得把那些老旧的、不安全的协议(比如 SSLv3, TLSv1.0, TLSv1.1)都给禁了。
还有,面试常问的:什么是 TLS 握手?如何优化 HTTPS 的访问速度? 答案里肯定包含开启 TLS 1.3、会话复用、OCSP Stapling 这些。咱们一个个来配置。
先上一份比较“硬核”的配置,我直接把优化项都加进去了:
简单来说,OCSP Stapling 就是为了解决“证书吊销检查”这个痛点。以前客户端拿到证书,还得去 CA 问问“这证是不是废了?”,这一问一答就增加了延迟,而且还泄露了用户隐私(CA 知道你访问了啥)。
开启 ssl_stapling on; 后,Nginx 会定期去 CA 那里把证书状态取回来,缓存到服务器上。当客户端握手时,Nginx 直接把这个状态(Stapling)发给客户端。客户端一看,哦,这证没问题,就省去了那次额外的查询。配置里那个 ssl_trusted_certificate 很关键,很多新手就是漏了这个导致 Stapling 没生效。
现在社区里聊得最火的就是 QUIC/HTTP3。Nginx 从 1.25 版本开始,通过 ngx_http_v3_module 实验性地支持了 HTTP/3。虽然现在(2024年)生产环境大规模部署的还不多,但趋势在那。如果你用的是最新的 1.27.3,想尝鲜的话,可以编译时加上 --with-http_v3_module。
不过,HTTP/3 走的是 UDP 协议,监听方式跟 TCP 不一样,配置起来稍微麻烦点,还得处理 Alt-Svc 头。对于大多数业务,现在的 HTTP/2 配合 TLS 1.3 已经足够快了。
⚡ 效率提示:配置完这些优化后,千万别凭感觉觉得“应该没问题了”。去用工具测一下。推荐直接用 SSL Labs 的在线测试(搜索 SSL Test),输入你的域名,它会给你打个分。如果你按照上面的配置搞,基本能拿 A+。如果没拿到,看看是不是证书链没配全(要用 fullchain.pem 而不是 cert.pem),或者 HSTS 没开对。
另外,关于 ECC vs RSA 的选择,现在 Let's Encrypt 默认给的就是 ECC 证书(椭圆曲线),它比 RSA 密钥短但安全性更高,性能也更好,特别是在移动端。除非你的用户还在用那种老掉牙的 Android 4.x 系统,否则放心用 ECC。
可以这么理解,现在给 Nginx 配上 HTTPS 只是个起步价,真正考验你功力的地方在于:用户访问你的网站快不快?尤其是在移动端网络不稳定的情况下。咱们现在用的是 Nginx 1.27.3(2024年10月刚发布的 Mainline 版本),这个版本对 QUIC 的支持已经相当成熟了,别再守着 HTTP/1.1 不放了。
很多同学一听 HTTP/3 就觉得是噱头,其实不是。传统的 HTTPS 跑在 TCP 上,一旦丢包,整个连接都得等着(队头阻塞)。而 HTTP/3 是基于 UDP 的 QUIC 协议,抗丢包能力极强。在弱网环境下,那种秒开的感觉是 HTTP/2 给不了的。
要在 Nginx 1.27.3 上开启它,你得确认编译的时候带了 --with-http_v3_module。如果没带,那就得重新编译一下。配置起来其实没那么玄乎,核心就是监听 UDP 的 443 端口,并声明你是支持 h3 的。
看看这个配置,直接拿去用:
💡 经验总结:开启 QUIC 后,别急着上线。先用 Chrome 访问,打开 chrome://net-internals/#http3,看看你的域名是不是出现在了列表里。或者直接在 DevTools 的 Network 面板里看 Protocol 列,是不是显示了 h3。如果没显示,多半是防火墙把 UDP 443 给拦了,云服务器的话记得去安全组放行 UDP。
除了换协议,还有一个老生常谈但极其有效的优化点:SSL 会话复用。
换个角度看,TLS 握手是个很耗 CPU 的过程。如果每次用户刷新一下页面或者开个新标签都要重新握一次手,服务器压力山大。会话复用就是让客户端和服务器“记仇”(记住之前的密钥),下次连接直接复用,省去复杂的密钥交换过程。
Nginx 里主要用两种机制:ssl_session_cache 和 ssl_session_ticket。
这里有个坑,很多新手以为配了 ssl_session_cache 就万事大吉,结果发现在负载均衡环境下不生效。那是因为 shared 类型的缓存只在同一台机器的同一个 Nginx 进程间共享。如果你有多台服务器,得用 ssl_session_ticket_key 来同步密钥,不然用户跳转到另一台机器还是得重新握手。
🔧 实战技巧:别盲目追求 TLS 1.3 的 0-RTT。虽然 0-RTT 很快,但它有重放攻击的风险。对于金融或者涉及写操作的 API,建议把 ssl_early_data off;(默认就是关的),别为了那几十毫秒的延迟把安全给丢了。
---
搞 HTTPS 最让人头秃的不是配置,而是配完了浏览器上挂个“不安全”的小锁,或者明明证书没过期,用户访问却报错。这一章咱们就来聊聊那些年我踩过的坑,以及怎么填。
这是最常见的问题。你兴冲冲地把 Nginx 配好了,访问 https://example.com 也没问题,结果浏览器地址栏的小锁是灰色的,或者干脆有个黄色警告。F12 打开控制台,一堆红色的错误:Mixed Content: The page at 'https://...' was loaded over HTTPS, but requested an insecure resource...
换个角度看,就是你网页里的图片、CSS 或者 JS 还在用 http:// 开头的地址。浏览器现在很严格,HTTPS 页面里不允许加载 HTTP 资源。
解决办法:
http:// 改成 https:// 或者干脆写成相对协议 //(不过现在更推荐直接写 https://)。这是另一个重灾区。很多同学用 Certbot 或者 acme.sh 申请证书,往往只把 cert.pem 或者 fullchain.pem 里的一部分内容丢给 ssl_certificate。结果就是,Chrome 浏览器访问正常(因为它会自动补全证书链),但是安卓手机、老旧的 Java 客户端或者 Python 脚本访问就直接报错,提示证书不可信。
关键点:Nginx 的 ssl_certificate 指令,一定要指向 fullchain.pem(完整链),而不是单纯的 cert.pem。完整链包含了你的域名证书 + 中间证书。
看看这个标准的证书配置结构:
📌 要点提醒:配完之后,别光用浏览器看。去用 openssl 命令或者在线工具(比如 SSLLabs 的测试)跑一下。如果你看到提示 "Chain issues: Incomplete" 或者 "Extra download",那就说明你的证书链配错了。一定要确保服务器直接发送了完整的证书链,而不是让客户端去猜。
既然提到了 Certbot,就不得不说自动化续期的问题。很多新手配了 Certbot 的 cron 任务,结果证书过期了还没续上。
通常是因为 Nginx 正在运行,Certbot 的 standalone 模式抢不到 80 端口,或者 webroot 模式路径不对。现在推荐用 --nginx 插件,它能自动修改配置并重载。
关键点:--deploy-hook 非常重要!证书续期后,Nginx 必须 reload 才能加载新证书。如果没有这个 hook,哪怕证书续期成功了,Nginx 还在用旧的(快过期的)证书,到时候照样报警。
还有一个坑,就是权限问题。Certbot 默认用 root 跑,生成的证书文件权限很严格。如果你的 Nginx 是用非 root 用户跑的(虽然很少见),或者你手动把证书拷到了别的地方,一定要检查读权限,不然 Nginx 启动会报 SSL_CTX_use_PrivateKey_file failed 的错误。