Nginx同域名HTTPS首次请求失败求助(含证书验证问题)
兄弟,你遇到的unable to verify the first certificate问题,核心是SSL证书链不完整,或者NextJS在SSR场景下用isomorphic-unfetch发起请求时,服务器端的证书信任逻辑和浏览器不一样。结合你的配置和场景,我整理了几个靠谱的排查和解决步骤:
1. 先把Nginx的HTTPS证书配置补全
很多人部署HTTPS时只放了域名证书,漏掉了中间证书,导致客户端(包括服务器端的fetch请求)没法完成信任链验证。这是最常见的原因!
你的Nginx HTTPS server块得这么配才对:
server { listen 443 ssl; server_name my-domain.com www.my-domain.com; # 重点:要用包含中间证书的fullchain.pem,别只用单独的域名证书 ssl_certificate /path/to/fullchain.pem; ssl_certificate_key /path/to/privkey.pem; # 可选但推荐的SSL安全配置 ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; # 前端NextJS SSR服务转发 location / { proxy_pass http://customer-ssr; 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; } # 后端API转发,注意末尾的斜杠,避免路径重复 location /api/ { proxy_pass http://your-backend-server:port/; 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; } } # HTTP自动跳转到HTTPS server { listen 80; server_name my-domain.com www.my-domain.com; return 301 https://$server_name$request_uri; }
比如用Let's Encrypt的话,它会自动生成
fullchain.pem和privkey.pem,直接用这两个就行,别用单独的cert.pem。
2. 搞定NextJS SSR里的fetch证书验证问题
在SSR场景下,isomorphic-unfetch在服务器端跑的时候,用的是Node.js的证书信任库,和浏览器的不是一套。如果你的证书是自签名的,或者CA不被Node默认信任,就会报错。
方案A:让Node信任你的证书(正规CA证书优先用这个)
- 如果你用的是正规CA颁发的证书,先升级Node.js到14+版本,新版的根证书库更全,大概率能解决问题。
- 如果是自签名证书,测试环境可以临时禁用验证(生产环境绝对不能这么干!):
import fetch from 'isomorphic-unfetch'; import https from 'https'; // 仅测试环境使用,生产环境禁止! const agent = new https.Agent({ rejectUnauthorized: false }); const res = await fetch('https://my-domain.com/api/your-endpoint', { agent });
方案B:SSR直接请求后端内网地址(最推荐)
既然前后端都在同一服务器或者内网里,SSR时完全没必要绕公网HTTPS请求my-domain.com/api/,直接请求后端的内网地址就行,既避免了证书问题,还更快。
你可以在NextJS里通过环境变量区分客户端和服务器端的API地址:
// 根据运行环境切换API地址 const API_BASE = typeof window !== 'undefined' ? '/api' // 客户端走Nginx转发 : 'http://127.0.0.1:你的后端端口'; // SSR时直接打内网地址 const res = await fetch(`${API_BASE}/your-endpoint`);
3. 验证证书链是否正常
用openssl命令检查下你的证书链有没有问题:
openssl s_client -connect my-domain.com:443 -servername my-domain.com
看输出里的Verify return code,如果是0 (ok)就没问题;如果是20 (unable to get local issuer certificate),那就是缺中间证书,赶紧把中间证书加到fullchain.pem里。
内容的提问来源于stack exchange,提问作者Vo Manh Kien

