当位于 Nginx 反向代理后面时,来自 Nginx 的意外 301 重定向

Aks*_*lén 5 nginx reverse-proxy redirect proxypass

类似的问题之前在这里这里被问过,但没有一个匹配或解决我遇到的问题。经过几个小时的拼命解决问题后,我找到了一个出乎意料但简单的解决方案,我想以问答的方式分享它。

背景

一家公司有两台服务器:反向代理(Ubuntu 20.04 + Nginx 1.17)和上游应用程序服务器(也是 Ubuntu 20.04 + Nginx 1.17)。反向代理处理各种子域,例如通过使用proxy_passhttp://demo.example.com指令将它们映射到上游服务器的目录。http://12.12.12.12:8000/demo/

然后,客户端发出 HTTP 请求,其中路径指向目录,但没有尾部斜杠,例如http://demo.example.com/items。默认的 Nginx 行为是进行 301 重定向到相同的 URL,并添加尾部斜杠,例如位置文档http://demo.example.com/items/中所述。

如果 301 重定向发生在上游应用程序服务器,则正常行为是重定向位置路径变为http://12.12.12.12:8000/demo/items/. 这不是问题,因为默认情况下,反向代理将12.12.12.12:8000/demo/用原始主机替换部分内容,因此客户端会收到正确的 301 重定向到demo.example.com/items/. 这正是我所期望发生的事情。

问题

但是,通过以下配置,客户端收到的重定向位置将变为http://demo.example.com:8000/demo/items/。反向代理似乎只部分重写了重定向 URL。

反向代理的站点配置:

server {
  listen 80;
  listen [::]:80;

  index index.html index.htm;

  server_name demo.example.com;

  location / {
    proxy_pass http://12.12.12.12:8000/demo/;
    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;
  }
}
Run Code Online (Sandbox Code Playgroud)

应用服务器上的站点配置:

server {
  listen 8000;
  listen [::]:8000;

  root /var/www/example.com;
  index index.html index.htm;

  location / {
    try_files $uri $uri/ =404;
  }
}
Run Code Online (Sandbox Code Playgroud)

我尝试过的

为了解决这个问题但没有成功,我尝试了proxy_redirectAbsolute_redirectserver_name_in_redirectport_in_redirect指令的各种组合和值,如类似问题中所建议的。这些指令要么没有效果,要么只部分解决了隐藏端口等问题。

某些组合部分禁用了重定向,并允许客户端访问项目页面(位于 items/index.html),而无需尾随斜杠http://demo.example.com/items/product.jpg然而,这破坏了图像的相对链接,因为浏览器试图在而不是在 处找到它们/items/product.jpg。重定向确实是必要的。

我尝试了以下重写指令:rewrite ^/(.*) /demo/$1 break;with proxy_pass http://12.12.12.12:8000/. 它按预期工作,但不影响重定向。

我还尝试按照此处的try_files $uri $uri/index.html $uri/ =404建议替换 try_files 行,但几乎显然没有成功。来自的重定向顽固地将其位置保留在。http://demo.example.com/itemshttp://demo.example.com:8000/demo/items/

我觉得不应该这么复杂。我缺少什么?

Aks*_*lén 4

经过多个小时的努力尝试理解 Nginx 重定向机制的问题和确切行为后,出现了一个解决方案,而且可能是解决方案。解决方案实际上相当简单:

proxy_set_header Host $host从反向代理配置中删除:

server {
  ...

  location / {
    proxy_pass http://12.12.12.12:8000/demo/;
    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;
  }
}
Run Code Online (Sandbox Code Playgroud)

或者,使用适当的proxy_redirect值:

server {
  ...
  location / {
    proxy_pass http://12.12.12.12:8000/demo/;
    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;
    proxy_redirect http://demo.example.com:8000/demo/ http://demo.example.com/;
  }
}
Run Code Online (Sandbox Code Playgroud)

其背后的原因是proxy_set_header Host $host将原始主机名委托给上游服务器。上游服务器的 nginx 将此主机应用于重定向。因此,重定向位置在http://demo.example.com:8000/demo/items/由上游服务器分派时并且在反向代理以任何方式修改它之前就已经存在。理解这一点是解决方案的前半部分。

后半部分讨论如何重定向proxy_pass以及如何proxy_redirect影响重定向。如果proxy_redirect没有定义它仍然有默认值default。它的默认且相当隐藏的行为是从上游获取重定向,并用原始主机替换 proxy_pass 值的精确匹配。换句话说,给定,仅修改proxy_pass http://abcLocation 标头包含的那些重定向。http://abc最后,如果未找到匹配项,则 Location 标头保持不变。

因此,最初的问题是由于上游服务器已经用反向代理提供的主机头替换了其IP地址而引起的。这会导致 的值proxy_pass与重定向位置不匹配,从而阻止激活默认proxy_redirect指令。此外,未更改的重定向被传递到客户端并将客户端退回到无效 URL。

我想说,这是 Host 标头、proxy_pass、默认 proxy_redirect 和内置 301 重定向之间相当复杂的交互。幸运的是,问题得到了解决!