IP证书和域名证书在Nginx冲突问题详解:5种解决方案
一台服务器既部署了IP证书,又部署了域名证书,访问时经常出现证书不匹配的警告。这是Nginx配置里常见的冲突问题。
这个问题的根源是Nginx的SNI机制和IP访问的特殊性。 本文详细分析冲突原因,给出5种解决方案。
一、冲突现象
现象一:用IP访问显示域名证书
浏览器访问 https://1.2.3.4,证书警告显示证书是 example.com 的,不是IP的。
现象二:用域名访问显示IP证书
浏览器访问 https://example.com,证书警告显示证书是 1.2.3.4 的。
现象三:部分浏览器正常,部分警告
电脑浏览器正常,手机浏览器警告;或Chrome正常,Firefox警告。
现象四:访问不同路径证书不同
根路径正常,子路径证书不匹配。
二、冲突原因分析
原因一:Nginx默认服务器机制
Nginx的 default_server 配置决定了没有匹配到其他server块时,用哪个server块响应。
问题场景:
# server 1:IP证书
server {
listen 443 ssl default_server;
server_name 1.2.3.4;
ssl_certificate ip.crt;
}
# server 2:域名证书
server {
listen 443 ssl;
server_name example.com;
ssl_certificate domain.crt;
}
用IP访问时:
- •浏览器不发送SNI(Server Name Indication)
- •Nginx匹配不到server_name,使用default_server
- •返回IP证书 ✓
用域名访问时:
- •浏览器发送SNI:
example.com - •Nginx匹配到server 2
- •返回域名证书 ✓
但实际情况经常出错:
- •旧浏览器不支持SNI
- •某些客户端不发送SNI
- •Nginx配置顺序导致default_server识别错误
原因二:SNI不匹配
SNI是TLS握手时客户端告诉服务器”我要访问哪个域名”的机制。服务器根据SNI选择对应证书。
IP访问的问题:
- •浏览器访问IP时不发送SNI(或发送IP作为SNI)
- •Nginx找不到匹配的server_name,用default_server
- •如果default_server是域名证书,就返回域名证书
域名访问的问题:
- •如果域名解析到IP,但DNS没配置好
- •或Nginx没配置对应的server_name
- •就会用default_server的证书
原因三:证书的SAN不匹配
现代证书用SAN(Subject Alternative Name)字段标识覆盖的域名/IP。
问题场景:
域名证书的SAN是 example.com, www.example.com,IP证书的SAN是 1.2.3.4。
- •用IP访问:Nginx返回的证书SAN里没有IP → 警告
- •用域名访问:Nginx返回的证书SAN里没有域名 → 警告
原因四:HTTP跳转HTTPS丢失信息
server {
listen 80;
server_name _;
return 301 https://$host$request_uri;
}
问题:用IP访问 http://1.2.3.4,跳转到 https://1.2.3.4。如果HTTPS的default_server是域名证书,就警告。
三、解决方案
方案一:正确配置default_server
核心思路:把IP证书的server设为default_server,处理所有未匹配的请求。
# IP证书的server作为default_server
server {
listen 443 ssl default_server;
server_name 1.2.3.4; # IP地址
ssl_certificate /path/to/ip.crt;
ssl_certificate_key /path/to/ip.key;
ssl_protocols TLSv1.2 TLSv1.3;
location / {
# IP访问的内容
root /var/www/ip;
index index.html;
}
}
# 域名证书的server
server {
listen 443 ssl;
server_name example.com www.example.com;
ssl_certificate /path/to/domain.crt;
ssl_certificate_key /path/to/domain.key;
ssl_protocols TLSv1.2 TLSv1.3;
location / {
root /var/www/domain;
index index.html;
}
}
关键点:
- •IP证书的server必须有
default_server - •IP证书的server_name必须是IP地址
- •域名证书的server_name必须是域名
方案二:用通配符server_name捕获IP访问
# 捕获所有非域名的访问(包括IP)
server {
listen 443 ssl default_server;
server_name _; # 匹配所有
ssl_certificate /path/to/ip.crt;
ssl_certificate_key /path/to/ip.key;
location / {
root /var/www/ip;
index index.html;
}
}
server {
listen 443 ssl;
server_name example.com www.example.com;
ssl_certificate /path/to/domain.crt;
ssl_certificate_key /path/to/domain.key;
location / {
root /var/www/domain;
index index.html;
}
}
原理:server_name _ 是无效域名,匹配所有未被其他server匹配的请求。default_server处理IP访问,域名访问走第二个server。
方案三:用正则匹配IP
# 用正则匹配IP访问
server {
listen 443 ssl;
server_name ~^(\d{1,3}\.){3}\d{1,3}$;
ssl_certificate /path/to/ip.crt;
ssl_certificate_key /path/to/ip.key;
location / {
root /var/www/ip;
}
}
server {
listen 443 ssl default_server;
server_name example.com www.example.com;
ssl_certificate /path/to/domain.crt;
ssl_certificate_key /path/to/domain.key;
location / {
root /var/www/domain;
}
}
原理:用正则匹配IP格式,IP访问走IP证书server,其他访问走域名证书server。
注意:正则server_name前必须加 ~。
方案四:HTTP和HTTPS分开处理
# HTTP server:根据Host判断跳转到哪个HTTPS
server {
listen 80;
server_name _;
set $https_url "https://$host$request_uri";
# 如果是IP访问,跳转到IP的HTTPS
if ($host ~ ^(\d{1,3}\.){3}\d{1,3}$) {
return 301 https://$host$request_uri;
}
# 域名访问,跳转到域名的HTTPS
return 301 https://$host$request_uri;
}
# HTTPS server:IP证书
server {
listen 443 ssl default_server;
server_name 1.2.3.4;
ssl_certificate /path/to/ip.crt;
ssl_certificate_key /path/to/ip.key;
location / {
root /var/www/ip;
}
}
# HTTPS server:域名证书
server {
listen 443 ssl;
server_name example.com www.example.com;
ssl_certificate /path/to/domain.crt;
ssl_certificate_key /path/to/domain.key;
location / {
root /var/www/domain;
}
}
方案五:用一张证书覆盖IP和域名
如果CA支持,可以申请一张同时包含IP和域名的证书:
申请时:在SAN里同时添加IP和域名。
CN = example.com
SAN = example.com, www.example.com, 1.2.3.4
Nginx配置:
server {
listen 443 ssl default_server;
server_name example.com www.example.com 1.2.3.4;
ssl_certificate /path/to/combined.crt;
ssl_certificate_key /path/to/combined.key;
location / {
root /var/www/html;
}
}
优势:一张证书覆盖所有访问方式,不存在冲突。
劣势:证书里同时暴露域名和IP,有信息泄露风险。ZeroSSL等CA支持这种多SAN证书。
四、验证配置是否正确
用openssl验证
# 用域名访问
openssl s_client -connect 1.2.3.4:443 -servername example.com
# 用IP访问
openssl s_client -connect 1.2.3.4:443
检查返回的证书:
- •域名访问应该返回域名证书
- •IP访问应该返回IP证书
用curl验证
# 用域名访问(不验证证书,只看证书信息)
curl -vI https://example.com 2>&1 | grep "subject:"
# 用IP访问
curl -vI https://1.2.3.4 2>&1 | grep "subject:"
用浏览器验证
- •浏览器访问
https://example.com,点小锁查看证书,应该是域名证书 - •浏览器访问
https://1.2.3.4,点小锁查看证书,应该是IP证书
五、常见问题
问题一:手机浏览器仍警告
原因:部分旧手机浏览器不支持SNI。
解决:
- •升级浏览器
- •用方案五(一张证书覆盖所有)
- •接受这部分用户的警告
问题二:curl访问IP返回域名证书
原因:curl默认发送SNI,可能发送了域名。
解决:用 --resolve 强制不发送SNI:
curl -vI https://1.2.3.4 --resolve 1.2.3.4:443:1.2.3.4
问题三:Nginx配置语法错误
检查:
nginx -t
常见错误:
- •
default_server重复定义 - •
server_name重复 - •证书路径错误
- •缺少
ssl关键字
问题四:证书链不完整
现象:电脑正常,手机警告;或Chrome正常,Firefox警告。
解决:用fullchain.crt而不是单独的certificate.crt。
问题五:续签后证书不更新
原因:Nginx没有reload。
解决:续签命令加 --reloadcmd "systemctl reload nginx"。
六、最佳实践
1. IP证书和域名证书分server配置
不要试图在同一个server块里处理两种证书,分开配置更清晰。
2. IP证书的server设为default_server
处理所有未匹配的请求,包括IP访问和不支持SNI的客户端。
3. 用正则匹配IP
server_name ~^(\d{1,3}\.){3}\d{1,3}$ 精准匹配IP访问。
4. 配置HTTP跳转
确保HTTP访问正确跳转到HTTPS,跳转时保留Host信息。
5. 用SSL Labs检测
ssllabs.com/ssltest 检测配置是否正确,目标A级以上。
6. 监控证书有效期
IP证书90天有效期,配置自动续签。
结语
IP证书和域名证书在Nginx上的冲突,根源是SNI机制和default_server配置不当。通过正确配置default_server、用正则匹配IP、或用一张证书覆盖所有,都能解决冲突。关键是理解Nginx的server_name匹配逻辑,分开配置不同证书的server块。