反向代理
使用与后端协议兼容的 pass 指令
All
proxy_*
directives are related to the backends that use the specific backend protocol.You should use
proxy_pass
only for HTTP servers working on the backend layer (set also thehttp://
protocol before referencing the HTTP backend) and other*_pass
directives only for non-HTTP backend servers (like a uWSGI or FastCGI).Directives such as
uwsgi_pass
,fastcgi_pass
, orscgi_pass
are designed specifically for non-HTTP apps and you should use them instead of theproxy_pass
(non-HTTP talking).For example:
uwsgi_pass
uses an uwsgi protocol.proxy_pass
uses normal HTTP to talking with uWSGI server. uWSGI docs claims that uwsgi protocol is better, faster and can benefit from all of uWSGI special features. You can send to uWSGI information what type of data you are sending and what uWSGI plugin should be invoked to generate response. With http (proxy_pass
) you won’t get that.
示例:
❌ 错误配置
server {
location /app/ {
## For this, you should use uwsgi_pass directive.
proxy_pass 192.168.154.102:4000; ## backend layer: uWSGI Python app.
}
...
}
⭕ 正确配置
server {
location /app/ {
proxy_pass http://192.168.154.102:80; ## backend layer: OpenResty as a front for app.
}
location /app/v3 {
uwsgi_pass 192.168.154.102:8080; ## backend layer: uWSGI Python app.
}
location /app/v4 {
fastcgi_pass 192.168.154.102:8081; ## backend layer: php-fpm app.
}
...
}
小心 proxy_pass
指令中的斜杠
注意尾随斜杠,因为 Nginx 会逐字替换部分,并且您可能会得到一些奇怪的 URL。
如果 proxy_pass 不带 URI 使用(即 server:port 之后没有路径),Nginx 会将原始请求中的 URI 与所有双斜杠
../
完全一样。
proxy_pass
中的 URI 就像别名指令一样,意味着 Nginx 将用proxy_pass
指令中的 URI 替换与位置前缀匹配的部分(我故意将其与位置前缀相同),因此 URI 将与请求的相同,但被规范化(没有小写斜杠和其他所有内容) 员工)。
示例:
location = /a {
proxy_pass http://127.0.0.1:8080/a;
...
}
location ^~ /a/ {
proxy_pass http://127.0.0.1:8080/a/;
...
}
仅使用 $host
变量设置和传递 Host 头
几乎应该始终将
$host
用作传入的主机变量,因为无论用户代理如何行为,它都是保证具有某种意义的唯一变量,除非您特别需要其他变量之一的语义。变量
$host
是请求行或http头中的主机名。 变量$server_name
是我们当前所在的服务器块的名称。区别:
$host
包含“按此优先顺序:请求行中的主机名,或“主机”请求标头字段中的主机名,或与请求匹配的服务器名”- 如果请求中包含HTTP主机标头字段,则
$http_host
包含该内容(始终等于HTTP_HOST请求标头)$server_name
contains theserver_name
of the virtual host which processed the request, as it was defined in the Nginx configuration. If a server contains multiple server names, only the first one will be present in this variable
http_host
, moreover, is better than$host:$server_port
because it uses the port as present in the URL, unlike$server_port
which uses the port that Nginx listens on.
示例:
proxy_set_header Host $host;
正确设置 X-Forwarded-For
头的值
Rationale
In the light of the latest httpoxy vulnerabilities, there is really a need for a full example, how to use
HTTP_X_FORWARDED_FOR
properly. In short, the load balancer sets the ‘most recent’ part of the header. In my opinion, for security reasons, the proxy servers must be specified by the administrator manually.
X-Forwarded-For
is the custom HTTP header that carries along the original IP address of a client so the app at the other end knows what it is. Otherwise it would only see the proxy IP address, and that makes some apps angry.The
X-Forwarded-For
depends on the proxy server, which should actually pass the IP address of the client connecting to it. Where a connection passes through a chain of proxy servers,X-Forwarded-For
can give a comma-separated list of IP addresses with the first being the furthest downstream (that is, the user). Because of this, servers behind proxy servers need to know which of them are trustworthy.The proxy used can set this header to anything it wants to, and therefore you can’t trust its value. Most proxies do set the correct value though. This header is mostly used by caching proxies, and in those cases you’re in control of the proxy and can thus verify that is gives you the correct information. In all other cases its value should be considered untrustworthy.
Some systems also use
X-Forwarded-For
to enforce access control. A good number of applications rely on knowing the actual IP address of a client to help prevent fraud and enable access.Value of the
X-Forwarded-For
header field can be set at the client’s side - this can also be termed asX-Forwarded-For
spoofing. However, when the web request is made via a proxy server, the proxy server modifies theX-Forwarded-For
field by appending the IP address of the client (user). This will result in 2 comma separated IP addresses in theX-Forwarded-For
field.A reverse proxy is not source IP address transparent. This is a pain when you need the client source IP address to be correct in the logs of the backend servers. I think the best solution of this problem is configure the load balancer to add/modify an
X-Forwarded-For
header with the source IP of the client and forward it to the backend in the correct form.Unfortunately, on the proxy side we are not able to solve this problem (all solutions can be spoofable), it is important that this header is correctly interpreted by application servers. Doing so ensures that the apps or downstream services have accurate information on which to make their decisions, including those regarding access and authorization.
There is also an interesing idea what to do in this situation:
To prevent this we must distrust that header by default and follow the IP address breadcrumbs backwards from our server. First we need to make sure the
REMOTE_ADDR
is someone we trust to have appended a proper value to the end ofX-Forwarded-For
. If so then we need to make sure we trust theX-Forwarded-For
IP to have appended the proper IP before it, so on and so forth. Until, finally we get to an IP we don’t trust and at that point we have to assume that’s the IP of our user. - it comes from Proxies & IP Spoofing by Xiao Yu.
Example
## The whole purpose that it exists is to do the appending behavior:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
## Above is equivalent for this:
proxy_set_header X-Forwarded-For $http_x_forwarded_for,$remote_addr;
## The following is also equivalent for above but in this example we use http_realip_module:
proxy_set_header X-Forwarded-For "$http_x_forwarded_for, $realip_remote_addr";
External resources
- Prevent X-Forwarded-For Spoofing or Manipulation
- Bypass IP blocks with the X-Forwarded-For header
- Forwarded header
不要在反向代理后面使用带有 $scheme
的 X-Forwarded-Proto
反向代理可以设置
X-Forwarded-Proto
,以告知应用程序它是HTTPS还是HTTP甚至是无效名称。schema 变量仅在需要的时候才会被评估(仅用于当前请求)。如果设置了 $schema 变量且沿途遇上多个代理,则会导致变形。例如:如果客户端转到https://example.com,则代理将方案值存储为HTTPS。 如果代理与下一级代理之间的通信是通过HTTP进行的,则后端会将方案视为HTTP。
示例:
## 1) client <-> proxy <-> backend
proxy_set_header X-Forwarded-Proto $scheme;
## 2) client <-> proxy <-> proxy <-> backend
## proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
始终将 Host,X-Real-IP 和 X-Forwarded 标头传递给后端
Rationale
When using Nginx as a reverse proxy you may want to pass through some information of the remote client to your backend web server. I think it’s good practices because gives you more control of forwarded headers.
It’s very important for servers behind proxy because it allow to interpret the client correctly. Proxies are the “eyes” of such servers, they should not allow a curved perception of reality. If not all requests are passed through a proxy, as a result, requests received directly from clients may contain e.g. inaccurate IP addresses in headers.
X-Forwarded
headers are also important for statistics or filtering. Other example could be access control rules on your app, because without these headers filtering mechanism may not working properly.If you use a front-end service like Apache or whatever else as the front-end to your APIs, you will need these headers to understand what IP or hostname was used to connect to the API.
Forwarding these headers is also important if you use the https protocol (it has become a standard nowadays).
However, I would not rely on either the presence of all
X-Forwarded
headers, or the validity of their data.
Example
location / {
proxy_pass http://bk_upstream_01;
## The following headers also should pass to the backend:
## - Host - host name from the request line, or host name from the Host request header field, or the server name matching a request
## proxy_set_header Host $host:$server_port;
## proxy_set_header Host $http_host;
proxy_set_header Host $host;
## - X-Real-IP - forwards the real visitor remote IP address to the proxied server
proxy_set_header X-Real-IP $remote_addr;
## X-Forwarded headers stack:
## - X-Forwarded-For - mark origin IP of client connecting to server through proxy
## proxy_set_header X-Forwarded-For $remote_addr;
## proxy_set_header X-Forwarded-For $http_x_forwarded_for,$remote_addr;
## proxy_set_header X-Forwarded-For "$http_x_forwarded_for, $realip_remote_addr";
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
## - X-Forwarded-Host - mark origin host of client connecting to server through proxy
## proxy_set_header X-Forwarded-Host $host:443;
proxy_set_header X-Forwarded-Host $host:$server_port;
## - X-Forwarded-Server - the hostname of the proxy server
proxy_set_header X-Forwarded-Server $host;
## - X-Forwarded-Port - defines the original port requested by the client
## proxy_set_header X-Forwarded-Port 443;
proxy_set_header X-Forwarded-Port $server_port;
## - X-Forwarded-Proto - mark protocol of client connecting to server through proxy
## proxy_set_header X-Forwarded-Proto https;
## proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Proto $scheme;
}
prefix 使用不带 X-
前缀的自定义头
Rationale
Internet Engineering Task Force released a new RFC (RFC-6648), recommending deprecation of
X-
prefix.The
X-
in front of a header name customarily has denoted it as experimental/non-standard/vendor-specific. Once it’s a standard part of HTTP, it’ll lose the prefix.If it’s possible for new custom header to be standardized, use a non-used and meaningful header name.
The use of custom headers with
X-
prefix is not forbidden but discouraged. In other words, you can keep usingX-
prefixed headers, but it’s not recommended and you may not document them as if they are public standard.
Example
Not recommended configuration:
add_header X-Backend-Server $hostname;
Recommended configuration:
add_header Backend-Server $hostname;