IP证书和域名证书在Nginx冲突问题详解:5种解决方案

a
admin
作者
2026-07-18
发布日期
约 17 分钟
阅读时间

一台服务器既部署了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块。

本文到此结束