Nginx 反向代理问题汇总
7月 18, 2022Nginx 反向代理问题汇总
作者 Zgao 在安全运维使用Nginx反向代理中出现的多类问题统一汇总分析并给出解决思路。
文章目录
- Nginx 反代提示421 misdirected request
- 反向代理解决图片等资源防盗链
- 反向代理视频资源
- 反向代理sub_filter字符替换不生效
- Location 和 proxy_pass 加不加斜杠的区别?
- alias指定返回某个文件
- root与alias的区别?
Nginx 反代提示421 misdirected request
在一次反向代理某星球网站时出现了421的问题。
https://www.zsxq.com/ 以该网站为例。
使用最简单反代配置。
server { listen 88; server_name localhost; location /api/ { proxy_pass https://api.zsxq.com/; proxy_set_header Host api.zsxq.com; } }返回421的状态码。
是什么导致“421 错误定向请求”错误?
客户端需要为此请求建立新连接,因为请求的主机名与用于此连接的服务器名称指示 (SNI) 不匹配。
Misdirected Request
当一个 TLS 证书在多个域之间共享时,该证书要么具有通配符名称,例如“*.example.org”,要么带有多个备用名称。使用 HTTP/2 的浏览器会识别这一点,并为此类主机重用已打开的连接。
但是当在同一个 TLS 连接上有多个主机的多个请求时,重新协商就变得不可能了。然后它可能会向客户端触发错误“421 Misdirected Request”。
上面的配置报错也是这种情况。*.zsxq.com使用了通配符的域名证书。nginx 不知道具体要访问哪一个网站,所以需要指定对应的 proxy_ssl_name
。
正确的配置如下,指定ssl_name多个参数:
server { listen 88; server_name localhost; location /api/ { proxy_ssl_server_name on; proxy_ssl_name api.zsxq.com; proxy_ssl_verify off; proxy_pass https://api.zsxq.com/; proxy_set_header Host api.zsxq.com; proxy_set_header Accept-Encoding ''; proxy_set_header Cookie 'xxxx'; } }反代成功!
反向代理解决图片等资源防盗链
还是以zsxq.com
为例,该网站的图片资源会检测referer头,如果不是从自己网站过去的就会返回403,但直接用浏览器打开图片链接就能正常显示。
区别在于前者的request请求header中是带有referer头,浏览器访问时默认会带上。
这里有两种解决思路:
- 将图片等静态资源也进行代理,但比较占用反代服务器的带宽资源。
- 让浏览器发起请求时,不带上referer头。
解决方法:
在反代的页面中插入meta标签。
虽然Nginx没有直接插入的指令来实现,但可以用sub_filter
替换的方式曲线救国。
添加配置如下:
sub_filter_once off; sub_filter_types *; sub_filter '</head>' '<meta name="referrer" content="no-referrer"></head>';通过替换实现在head标签中插入meta标签。此时浏览器在请求资源时就不会携带referer头。
图片资源加载成功!
反向代理视频资源
设置反向代理缓冲大小。之前文章有写过,参考:
反向代理sub_filter字符替换不生效
通常有以下两种原因:
- 源站点启用了gzip压缩。
- 需替换的MIME类型为text/html之外的字符串,比如server端返回json格式的内容,sub_filter是不会进行替换的。
第一种的解决方案如下:
proxy_set_header Accept-Encoding "";如果增加这行代码后问题依旧存在,大概率是源站点启用了强制gzip压缩。nginx反代替换关键字前并不会自动解压缩,所以无法执行替换内容。
解决思路:
反代2次。第一次反代时增加gzip off;
设置项,以输出无压缩的内容,第二次反代本机地址,实现关键字替换。
第二种的解决方案如下:
sub_filter_types *; #替换所有类型Location 和 proxy_pass 加不加斜杠的区别?
proxy_pass
指令后面的参数有讲究,但在实际的应用中就分为两种情况。
proxy_pass 后面不带路径,就原封不动传给后端。
proxy_pass 后面带路径,就去掉 Location 的匹配再传给后端。
proxy_pass 后面url只有host没有路径
这里指不包含 $uri
,如:
http://host
✅https://host
✅http://host:port
✅https://host:port
✅http://host/
❌http://host:port/
❌
这时候 location
匹配的完整路径将直接透传给后端,如:
proxy_pass 后面的url中带有路径
注意,这里的路径哪怕只是一个 /
也是存在的,如:
http://host
❌https//host/
✅http://host:port
❌https://host:port/
✅http://host/api
✅http://host/api/
✅
当 proxy_pass url
的 url
包含路径时,匹配时会根据 location
的匹配后的链接透传给 url
,注意匹配后是下面这样。
location 规则 | 访问的原始链接 | 匹配之后的路径 |
---|---|---|
location / | / | |
location / | /a | a |
location / | /a/b/c?d | a/b/c?d |
location /a/ | /a/ | |
location /a/ | /a/b/c?d | b/c?d |
可以看出,当 proxy_pass url
中包含路径时,结尾的 /
最好同 location
匹配规则一致。
alias指定返回某个文件
alias使用注意两点:
- Windows使用绝对路径一定要用反斜杠 \ 。
- 使用相对路径注意将文件放到默认路径下面,直接指定文件名。
这里测试发现使用alias默认会拼接路径导致报错。可以通过error.log进行debug。
比如我是用绝对路径,但是注意一定要用 \ ,不然会出现下面的 failed (3: The system cannot find the path specified)
的报错。
root与alias的区别?
root与alias主要区别在于nginx如何解释location后面的uri,这会使两者分别以不同的方式将请求映射到服务器文件上。
root的处理结果是:root路径+location路径
alias的处理结果是:使用alias路径替换location路径
alias是一个目录别名的定义,root则是最上层目录的定义。
还有一个重要的区别是alias后面必须要用“/”
结束,否则会找不到文件的,而root则可有可无。
如果一个请求的URI是/path/a.html时,web服务器将会返回服务器上的/www/root/html/path/a.html的文件。
location ^~ /path/ { alias /www/root/html/new_path/; }如果一个请求的URI是/path/a.html时,web服务器将会返回服务器上的/www/root/html/new_path/a.html的文件。注意这里是new_path,因为alias会把location后面配置的路径丢弃掉,把当前匹配到的目录指向到指定的目录。
注意:
1. 使用alias时,目录名后面一定要加”/”。
3. alias在使用正则匹配时,必须捕捉要匹配的内容并在指定的内容处使用。
4. alias只能位于location块中。(root可以不放在location中)