之前也曾问过类似的问题这里和这里但它们都不符合或解决了我遇到的问题。经过几个小时的拼命解决问题,我找到了一个出乎意料但又简单的解决方案,我想以问答形式分享它。
背景
一家公司有两台服务器:一台反向代理(Ubuntu 20.04 + Nginx 1.17)和一台上游应用服务器(也是 Ubuntu 20.04 + Nginx 1.17)。反向代理处理各种子域,例如,http://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;
}
}
应用服务器上的站点配置:
server {
listen 8000;
listen [::]:8000;
root /var/www/example.com;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
}
我尝试过
为了解决这个问题但没有成功,我尝试了各种组合和值代理重定向,绝对重定向,重定向的服务器名称, 和端口重定向指令与类似问题中所建议的相同。这些指令要么没有效果,要么只是部分修复了问题,例如隐藏端口。
某些组合部分禁用了重定向,并允许客户端访问项目页面(位于 items/index.html)而无需使用斜杠http://demo.example.com/items
。然而,这会破坏图像的相对链接,因为浏览器会尝试在/product.jpg
而不是 处找到它们/items/product.jpg
。重定向确实是必要的。
我尝试了以下改写指令:rewrite ^/(.*) /demo/$1 break;
使用proxy_pass http://12.12.12.12:8000/
。它按预期工作,但不影响重定向。
try_files $uri $uri/index.html $uri/ =404
我还尝试按照建议替换 try_files 行这里但几乎显然没有成功。来自 的重定向http://demo.example.com/items
顽固地将其位置保持在http://demo.example.com:8000/demo/items/
。
我觉得不应该这么复杂。我遗漏了什么?
答案1
经过数小时的艰苦努力,终于弄清了问题所在,以及 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;
}
}
或者,使用适当的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/;
}
}
这样做的原因是将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://abc
,只有 Location 标头包含 的重定向才会http://abc
被修改。最后,如果未找到匹配项,则位置标头保持不变。
因此,最初的问题是由于上游服务器已经用反向代理提供的主机头替换了其 IP 地址。这导致的值与proxy_pass
重定向位置不匹配,从而阻止了默认指令的激活proxy_redirect
。此外,未改变的重定向被传递给客户端,并将客户端反弹到无效的 URL。
我想说的是,这是 Host 标头、proxy_pass、默认 proxy_redirect 和内置 301 重定向之间的一个相当复杂的交互。幸运的是,它解决了!