Nginx的反向代理使用较为简单,对大部分网站直接使用proxy_pass命令即可搞定。但在使用过程中遇到一些问题。
遇到的一些典型问题:
以下 mydomain.com为 反向代理服务器,upstream.com为被代理的服务器
1、被代理的网站(上游服务器 upstream)采用301、302重定向,导致反向代理后,在客户端最终仍然跳转到原网站地址
此种情况,一些可以直接用proxy_redirect或error_page来搞定。
proxy_intercept_errors on; error_page 301 302 307 = @handle_redirects; location @handle_redirects { #proxy_pass https://mydomain.com; proxy_redirect https://upstream.com/ /; }
可以参考:
https://stackoverflow.com/questions/20254456/intercepting-backend-301-302-redirects-proxy-pass-and-rewriting-to-another-loc
https://serverfault.com/questions/641070/Nginx-302-redirect-resolve-internally
2、一些上游服务器页面中的CSS、Javascript仍然指向原网站,导致css、js未正常代理
另外有一些网站是在页面上用JavaScript 来重定向,单纯采用proxy_redirect无法解决,此种情况可以结合sub_filter来替代页面响应内容。
针对页面内容的代理,可以使用Nginx的sub_filter的对原代理网站的页面响应内容进行替换。
sub_filter ‘upstream.com’ ‘mydomain.com’;
sub_filter_once off;
备注:sub_filter不支持正则表达式替换,如果在替换时候使用正则表达式,可以使用subs_filter
3、 启用gzip压缩导致sub_filter失效
与Nginx 启用gzip的几个相关设置值
gzip_http_version 1.0 #1.0|1.1 缺省值为1.1 proxy_http_version 1.0 #1.0|1.1 缺省值为1.0 proxy_set_header Accept-Encoding 'gzip'; gzip_proxied off
Nginx 缺省情况下以 HTTP/1.0 连接上游服务器,而 Nginx 的 gzip_http_version 缺省值是 HTTP/1.1。
gzip_http_version指令是用来设置 Nginx 启用 gzip 压缩所需的 HTTP 最低版本。也就是说缺省情况下,Nginx 连接上游服务器为不启用gzip压缩。
sub_filter 只能处理未经压缩的内容,因此启用gzip压缩后,会导致sub_filter失效。
要解决启用gzip压缩导致sub_filter的问题,有两个大的方案:
方案1:反向代理服务器对上游服务器连接请求禁用gzip压缩
通过设置
proxy_http_version 1.0
或者
proxy_set_header Accept-Encoding ''
或者
gzip_proxied off
缺点:由于gzip压缩对文本内容压缩效果明显,禁用gzip压缩后,会导致与上游服务器的流量过大。另外有一个网站会强制要求使用gzip压缩。
方案2:与上游服务器连接依然启用gzip压缩,但在调用sub_filter前先解压缩,然后再做替换操作
方案2又有几种思路:
思路1:使用Nginx ngx_http_gunzip_module
官方的 ngx_http_gunzip_module
该模块不是默认的,需要在编译时候通过指定 –with-http_gunzip_module 启用
proxy_pass https://upstream.com/; gunzip on; gzip_disable "."; proxy_set_header Accept-Encoding "gzip"; sub_filter 'upstream.com' 'mydomain.com'; sub_filter_once off;
注意:此处需要设置 gzip_disable “.”; 如果不设置,sub_filter不起作用。
来源:https://www.mail-archive.com/debian-bugs-dist@lists.debian.org/msg1313433.html
较早还有其他一些gunzip 的方案,例如
ngx_http_gunzip_filter_module
https://github.com/huangqiheng/Nginx-gunzip
思路2:另一种思路比较直观,做两次proxy_pass
Nginx反向代理使用的一些坑(续)–gzip/gunzip 与sub_filter的那些事
思路3:使用ngx_lua的Lua脚本
可以参考:http://www.pataliebre.net/howto-make-Nginx-decompress-a-gzipped-request.html#.Vm-GOmR95GH
除了可以使用Openrestry的Lua来实现gunzip外,还可以使用Lua来修改response的header参数
4、一些上游服务器只支持https访问,导致反向代理无法成功
可以禁用ssl验证:
proxy_ssl_verify off; proxy_ssl_server_name on;
5、一些上游服务器的网站会检查referer及User-Agent
proxy_set_header Referer "https://upstream.com"; proxy_set_header User-Agent "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:73.0) Gecko/20100101 Firefox/73.0";
6、proxy_pass中URI参数路径带不带/问题
在proxy_pass时候,一定要注意 / 的问题,有巨大的坑。
官方说明:https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass
proxy_pass指令中,如果目标地址中不带URI,则proxy_pass时候,对location 匹配的URI部分不做任何修改。
所谓不带URI,是指:也即proxy_pass的参数形如”http://127.0.0.1″,
例子:
location /test1/test2/ { proxy_pass http://127.0.0.1; }
则:
请求:http://mydomain.com/test1/test2/index.html ,经过proxy_pass 后,实际请求地址为:http://127.0.0.1/test1/test2/index.html
请求:http://mydomain.com/test1/test2/ ,经过proxy_pass 后,实际请求地址为: http://127.0.0.1/test1/test2/
proxy_pass指令中,如果目标地址中带URI,则proxy_pass时候 ,location中匹配的URI部分会被替换成 proxy_pass的参数的URI
例子:
location /test1/test2/ { proxy_pass http://127.0.0.1/; }
则:
请求:http://mydomain.com/test1/test2/index.html ,经过proxy_pass 后,请求地址为:http://127.0.0.1/index.html
请求:http://mydomain.com/test1/test2/ ,经过proxy_pass 后,请求地址为: http://127.0.0.1/
7、上游服务器强制返回特定压缩算法导致sub_filter失效说明
具体参考:Nginx反向代理使用的一些坑(续)–gzip,br压缩算法 与sub_filter的那些事
8、Cookie处理机制
具体参考:Nginx反向代理使用的一些坑(续)– Cookie的那些事
需要反向代理访问某个网站(upstream.com),由于需要动态替换反向代理页面中的链接地址,因此使用sub_filter,但发现未替换成功。认为是响应结果gzip导致的,设置 proxy_set_header Accept-Encoding ” 或 proxy_http_version 1.0 ,sub_filter依然不生效。
按照《Nginx反向代理使用的一些坑》的经验,应该是上游网站强制返回gzip压缩过的内容。在使用文中提到的“与上游服务器连接依然启用gzip压缩,但在调用sub_filter前先解压缩,然后再做替换操作”几种方法时候,发现一些新的问题及注意事项,作为《Nginx反向代理使用的一些坑》的补充说明。
1、使用Nginx ngx_http_gunzip_module
proxy_pass https://upstream.com/; gunzip on; sub_filter 'upstream.com' 'mydomain.com'; sub_filter_once off;
使用以上代码,发现一个奇怪现象,有时候能够替换成功,有时候不行,现象随机,怀疑是gunzip与sub_filter并非串行执行的。
在 https://www.mail-archive.com/debian-bugs-dist@lists.debian.org/msg1313433.html 发现一个回答,提到gunzip on 需要配合 gzip_disable “.” 使用。
(gzip_disable is needed because the author of gunzip had a different use case in mind. Without this option, as an optimization nginx does not decompress the proxied data for a client which breaks the substitution filter.)
尝试gzip_disable “.” 后,发现确实有效。
也即:
proxy_pass https://upstream.com/; gunzip on; gzip_disable "."; sub_filter 'upstream.com' 'mydomain.com'; sub_filter_once off;
2、做两次proxy_pass
基本思路:
a、mydomain.com server 通过unix socket将请求proxy_pass发送给监听server
b、监听unix socket的server向上游upstream发起反向代理请求,请求为gzip on(proxy_set_header Accept-Encoding gzip;)
c、监听unxi socket的server对请求结果gunzip,将gunzip结果回复给mydomain.com server
d、在mydomain.com server对响应结果调用sub_filter替换
server { listen unix:/var/run/nginx-gunzip.sock; location / { proxy_pass https://upstream.com ; gunzip on; proxy_set_header Host "upstream.com"; proxy_set_header Referer "https://upstream.com"; proxy_set_header User-Agent "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:73.0) Gecko/20100101 Firefox/73.0"; #proxy_http_version 1.1; proxy_set_header Accept-Encoding gzip; proxy_set_header X-Real-IP $remote_addr; #proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_connect_timeout 60s; proxy_read_timeout 5400s; proxy_send_timeout 5400s; proxy_buffer_size 64k; proxy_buffers 32 32k; proxy_busy_buffers_size 128k; proxy_ssl_verify off; proxy_ssl_server_name on; } } server { server_name mydomain.com; listen 80 ; listen 443 ssl http2; index index.html index.htm; root /data/wwwroot/mydomain.com; error_page 400 = /400.html; location / { proxy_pass http://unix:/var/run/nginx-gunzip.sock:/; sub_filter_types *; sub_filter 'upstream.com' 'mydomain.com'; sub_filter_once off; } }
值得说明一下,相对于通过http请求,unix socket更为高效,但我用的1.6.1 版本存在重启nginx,不会自动删除创建的unix socket情况(SIGQUIT信号不会删除,SIGTERM会自动删除,参见:https://trac.nginx.org/nginx/ticket/753)。
可以把删除操作放到nginx 的service脚本中。
在Centos中,修改 vi /etc/systemd/system/nginx.service,增加
ExecStartPre=/usr/bin/rm -f /var/run/nginx-gunzip.sock