多域名跳转必须用多个server块,不能用if+rewrite混搭,否则会因优先级混乱、重写循环和https兼容性问题导致500错误或err_too_many_redirects。

多域名跳转必须用多个 server 块,不能靠 if + rewrite 混搭处理——否则会踩到优先级、重写循环、HTTPS 兼容性三连坑。
为什么不用 if 做多域名跳转
很多人抄网上片段直接写:if ($host != 'www.example.com') { rewrite ... permanent; }。这在单域名跳转时看似能跑,但一旦加了第二个跳转规则,就出问题:
-
if是 nginx 的“非标准上下文”,不支持嵌套,也不保证执行顺序 - 当多个
if判断共存(比如 www → non-www + 旧域名 → 新域名),容易触发重写循环,返回500或浏览器报ERR_TOO_MANY_REDIRECTS -
if不感知$scheme,HTTP 和 HTTPS 请求混用时,rewrite可能硬编码http://,导致 HTTPS 跳转降级 - nginx 官方文档明确标注:
if在server上下文中仅用于简单变量判断,不推荐用于重定向逻辑
server_name 匹配失败的常见表现
配置完多个 server 块后,访问某个域名却进了默认站点(或另一个站点),不是 DNS 问题,大概率是 server_name 没匹配上:
-
server_name必须完全匹配或符合通配符规则,比如example.com不匹配www.example.com,得单独写或用*.example.com - 空格、大小写、末尾点号(如
example.com.)都会导致匹配失败 - 如果只配了一个
server块,nginx 会把它当作“默认 server”,无视server_name—— 所以多域名跳转前,至少要有两个server块 - 用
nginx -t检查语法后,再用nginx -T | grep "server_name"确认实际加载的值
HTTP 和 HTTPS 跳转必须分开写 server 块
一个域名要同时支持 HTTP → HTTPS + 域名规范化(比如 example.com → www.example.com),不能挤在一个 server 里用条件判断,而应拆成两个独立块:
server {
listen 80;
server_name example.com www.example.com;
return 301 https://www.php.cn/link/59386272857f013b167191da57f9eb91;
}
<p>server {
listen 443 ssl;
server_name example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
return 301 <a href="https://www.php.cn/link/59386272857f013b167191da57f9eb91">https://www.php.cn/link/59386272857f013b167191da57f9eb91</a>;
}</p><p>server {
listen 443 ssl;
server_name www.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;</p><h1>正常业务配置放这里</h1><pre class="brush:php;toolbar:false;">root /var/www/html;
index index.html;}
- HTTP 块只管协议升级和域名归一,不处理证书
- HTTPS 块按
server_name精确分流:非www的 HTTPS 请求先跳转,www的才进业务逻辑 - 证书路径必须在每个启用 SSL 的
server块里显式声明,不能继承 - 别漏掉
ssl_prefer_server_ciphers on;等基础安全项,否则某些老客户端握手失败
泛域名跳转主域名时,server_name 不能只写 *
想让 a.example.com、b.example.com 全部跳到 www.example.com,不能这么写:
server {
listen 80;
server_name *.example.com; # ❌ 错误:* 只匹配一级子域,不匹配 example.com 自身
return 301 https://www.php.cn/link/59386272857f013b167191da57f9eb91;
}
正确做法是显式列出所有需跳转的域名模式:
server {
listen 80;
server_name ~^([a-zA-Z0-9\-]+)\.example\.com$;
return 301 https://www.php.cn/link/59386272857f013b167191da57f9eb91;
}
<p>server {
listen 80;
server_name example.com;
return 301 <a href="https://www.php.cn/link/59386272857f013b167191da57f9eb91">https://www.php.cn/link/59386272857f013b167191da57f9eb91</a>;
}</p>
- 正则
server_name必须以~开头,且注意转义点号(\.) - 泛匹配不覆盖主域名本身,
example.com得单独再写一个server块 - 正则匹配性能略低于字面量,但对几十个子域来说无感;若子域超百,建议用
map指令预处理 - 测试正则是否生效,可临时加
add_header X-Matched-Host $host;,用 curl 看响应头
真正容易被忽略的是:跳转链不能跨协议又跨域名还带路径重写——比如从 http://old.com/path?a=1 跳到 https://new.net/other,$request_uri 就不该直接拼接,得手动截断或重构造,否则参数错位或路径污染。











