反向代理

使用与后端协议兼容的 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 the http:// 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, or scgi_pass are designed specifically for non-HTTP apps and you should use them instead of the proxy_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.

示例:

❌ 错误配置

  1. server {
  2. location /app/ {
  3. ## For this, you should use uwsgi_pass directive.
  4. proxy_pass 192.168.154.102:4000; ## backend layer: uWSGI Python app.
  5. }
  6. ...
  7. }

⭕ 正确配置

  1. server {
  2. location /app/ {
  3. proxy_pass http://192.168.154.102:80; ## backend layer: OpenResty as a front for app.
  4. }
  5. location /app/v3 {
  6. uwsgi_pass 192.168.154.102:8080; ## backend layer: uWSGI Python app.
  7. }
  8. location /app/v4 {
  9. fastcgi_pass 192.168.154.102:8081; ## backend layer: php-fpm app.
  10. }
  11. ...
  12. }

小心 proxy_pass 指令中的斜杠

注意尾随斜杠,因为 Nginx 会逐字替换部分,并且您可能会得到一些奇怪的 URL。

如果 proxy_pass 不带 URI 使用(即 server:port 之后没有路径),Nginx 会将原始请求中的 URI 与所有双斜杠 ../ 完全一样。

proxy_pass 中的 URI 就像别名指令一样,意味着 Nginx 将用 proxy_pass 指令中的 URI 替换与位置前缀匹配的部分(我故意将其与位置前缀相同),因此 URI 将与请求的相同,但被规范化(没有小写斜杠和其他所有内容) 员工)。

示例:

  1. location = /a {
  2. proxy_pass http://127.0.0.1:8080/a;
  3. ...
  4. }
  5. location ^~ /a/ {
  6. proxy_pass http://127.0.0.1:8080/a/;
  7. ...
  8. }

仅使用 $host 变量设置和传递 Host 头

几乎应该始终将 $host 用作传入的主机变量,因为无论用户代理如何行为,它都是保证具有某种意义的唯一变量,除非您特别需要其他变量之一的语义。

变量 $host 是请求行或http头中的主机名。 变量 $server_name 是我们当前所在的服务器块的名称。

区别:

  • $host 包含“按此优先顺序:请求行中的主机名,或“主机”请求标头字段中的主机名,或与请求匹配的服务器名”
  • 如果请求中包含HTTP主机标头字段,则 $http_host 包含该内容(始终等于HTTP_HOST请求标头)
  • $server_name contains the server_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.

示例:

  1. 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 as X-Forwarded-For spoofing. However, when the web request is made via a proxy server, the proxy server modifies the X-Forwarded-For field by appending the IP address of the client (user). This will result in 2 comma separated IP addresses in the X-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 of X-Forwarded-For. If so then we need to make sure we trust the X-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

  1. ## The whole purpose that it exists is to do the appending behavior:
  2. proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  3. ## Above is equivalent for this:
  4. proxy_set_header X-Forwarded-For $http_x_forwarded_for,$remote_addr;
  5. ## The following is also equivalent for above but in this example we use http_realip_module:
  6. proxy_set_header X-Forwarded-For "$http_x_forwarded_for, $realip_remote_addr";

External resources

不要在反向代理后面使用带有 $schemeX-Forwarded-Proto

反向代理可以设置 X-Forwarded-Proto,以告知应用程序它是HTTPS还是HTTP甚至是无效名称。schema 变量仅在需要的时候才会被评估(仅用于当前请求)。

如果设置了 $schema 变量且沿途遇上多个代理,则会导致变形。例如:如果客户端转到https://example.com,则代理将方案值存储为HTTPS。 如果代理与下一级代理之间的通信是通过HTTP进行的,则后端会将方案视为HTTP。

示例:

  1. ## 1) client <-> proxy <-> backend
  2. proxy_set_header X-Forwarded-Proto $scheme;
  3. ## 2) client <-> proxy <-> proxy <-> backend
  4. ## proxy_set_header X-Forwarded-Proto https;
  5. 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

  1. location / {
  2. proxy_pass http://bk_upstream_01;
  3. ## The following headers also should pass to the backend:
  4. ## - Host - host name from the request line, or host name from the Host request header field, or the server name matching a request
  5. ## proxy_set_header Host $host:$server_port;
  6. ## proxy_set_header Host $http_host;
  7. proxy_set_header Host $host;
  8. ## - X-Real-IP - forwards the real visitor remote IP address to the proxied server
  9. proxy_set_header X-Real-IP $remote_addr;
  10. ## X-Forwarded headers stack:
  11. ## - X-Forwarded-For - mark origin IP of client connecting to server through proxy
  12. ## proxy_set_header X-Forwarded-For $remote_addr;
  13. ## proxy_set_header X-Forwarded-For $http_x_forwarded_for,$remote_addr;
  14. ## proxy_set_header X-Forwarded-For "$http_x_forwarded_for, $realip_remote_addr";
  15. proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  16. ## - X-Forwarded-Host - mark origin host of client connecting to server through proxy
  17. ## proxy_set_header X-Forwarded-Host $host:443;
  18. proxy_set_header X-Forwarded-Host $host:$server_port;
  19. ## - X-Forwarded-Server - the hostname of the proxy server
  20. proxy_set_header X-Forwarded-Server $host;
  21. ## - X-Forwarded-Port - defines the original port requested by the client
  22. ## proxy_set_header X-Forwarded-Port 443;
  23. proxy_set_header X-Forwarded-Port $server_port;
  24. ## - X-Forwarded-Proto - mark protocol of client connecting to server through proxy
  25. ## proxy_set_header X-Forwarded-Proto https;
  26. ## proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
  27. proxy_set_header X-Forwarded-Proto $scheme;
  28. }

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 using X- prefixed headers, but it’s not recommended and you may not document them as if they are public standard.

Example

Not recommended configuration:

  1. add_header X-Backend-Server $hostname;

Recommended configuration:

  1. add_header Backend-Server $hostname;

External resources