前言

nginx 在决定请求由哪个 server 块执行时,主要关注的是 server 块中的 listen 和 server_name 两个字段,如果根据 listen 指令无法得到最佳匹配,将会开始解析 server_name 指令。nginx 会检查请求中的 “Host” 头,这个值包含了客户端实际试图请求的域名或者 ip 地址。nginx 会根据这个值去匹配 server_name 指令,匹配规则会在文章中详细描述。其中有一个需要大家注意的地方是如果没有匹配到任何规则的话,则会选择可用列表中的第一个 server,带来的问题就是未绑定域名或 IP 直接访问 80 和 443 端口会给后端逻辑服务增加压力并产生不合理的错误日志,合适的解决办法是通过在 nginx 的 server 块中添加 default_server 禁止未绑定域名或 IP 访问 80 和 443 端口过滤不合理的流量。

如果根据 listen 指令无法得到最佳匹配, 将会开始解析 server_name 指令. nginx 会检查请求中的 “Host” 头, 这个值包含了客户端实际试图请求的域名或者 ip 地址. nginx 会根据这个值去匹配 server_name 指令, 匹配规则如下:

  1. nginx 会尝试寻找一个和 sever_name 和 Host 值完全匹配的 server 块, 如果找到多个精确匹配, 则会使用第一个匹配的 server 块
  2. 如果没有找到精确匹配的 server 块, 则 nginx 尝试找到 server_name 带有 * 开头的 server 块, 如果找到多个, 则选择最长匹配的 server 块
  3. 如果没有找到使用开头的 server 块, 则会寻找以结尾的 server 块, 同样, 如果有多个匹配, 选择最长匹配
  4. 如果没有找到使用 * 匹配的 server 块, 则会寻找使用正则表达式 (以~ 开头) 定义 server_name 的 server 块, 如果找到多个匹配, 会使用第一个匹配
  5. 如果没有找到正则表达式匹配的 server 块, 则 nginx 将会选择一个匹配 listen 字段的 default server 块. 每一个 ip 和端口组合都可以配置一个且只能配置一个默认的 default_server 块, 如果没有的话, 则会选择可用列表中的第一个 server

示例如下:

( 1 )准确的 server_name 匹配, 例如:

  1. server {
  2. listen 80;
  3. server_name www.domain.com;
  4. ...
  5. }

( 2 )以 * 通配符开始的字符串:

  1. server {
  2. listen 80;
  3. server_name *.domain.com;
  4. ...
  5. }

( 3 )以 * 通配符结束的字符串

  1. server {
  2. listen 80;
  3. server_name www.*;
  4. ...
  5. }

( 4 )匹配正则表达式:

  1. server {
  2. listen 80;
  3. server_name ~^(?.+)\.domain\.com$;
  4. ...
  5. }

( 5 )如果以上都没有匹配, 则使用 default_server. 如果没有指定 default_server, 则会选择第一个可用的 server. 我们可以指定对于没有匹配的 host 值时, 返回错误到客户端. 可以用来防止别人把垃圾流量转到你的网站。

  1. server {
  2. listen 80 default_server;
  3. server_name _; return 444;
  4. }

通过返回 444 这个 nginx 的非标准错误码让 nginx 断开与浏览器的连接

Nginx 官方对 default_server 的解释

Miscellaneous names

If someone makes a request using an IP address instead of a server name, the “Host” request header field will contain the IP address and the request can be handled using the IP address as the server name:

  1. server {
  2. listen 80;
  3. server_name example.org
  4. www.example.org
  5. ""
  6. 192.168.1.1
  7. ;
  8. ...
  9. }

In catch-all server examples the strange name “_” can be seen:

  1. server {
  2. listen 80 default_server;
  3. server_name _;
  4. return 444;
  5. }

There is nothing special about this name, it is just one of a myriad of invalid domain names which never intersect with any real name. Other invalid names like “—” and “!@#” may equally be used.

Name-based virtual servers

nginx first decides which server should process the request. Let’s start with a simple configuration where all three virtual servers listen on port *:80:

  1. server {
  2. listen 80;
  3. server_name example.org www.example.org;
  4. ...
  5. }
  6. server {
  7. listen 80;
  8. server_name example.net www.example.net;
  9. ...
  10. }
  11. server {
  12. listen 80;
  13. server_name example.com www.example.com;
  14. ...
  15. }

In this configuration nginx tests only the request’s header field “Host” to determine which server the request should be routed to. If its value does not match any server name, or the request does not contain this header field at all, then nginx will route the request to the default server for this port. In the configuration above, the default server is the first one — which is nginx’s standard default behaviour. It can also be set explicitly which server should be default, with the default_server parameter in the listen directive:

  1. server {
  2. listen 80 default_server;
  3. server_name example.net www.example.net;
  4. ...
  5. }

The default_server parameter has been available since version 0.8.21. In earlier versions the default parameter should be used instead. Note that the default server is a property of the listen port and not of the server name. More about this later.

How to prevent processing requests with undefined server names

If requests without the “Host” header field should not be allowed, a server that just drops the requests can be defined:

  1. server {
  2. listen 80;
  3. server_name "";
  4. return 444;
  5. }

Here, the server name is set to an empty string that will match requests without the “Host” header field, and a special nginx’s non-standard code 444 is returned that closes the connection.

Since version 0.8.48, this is the default setting for the server name, so the server_name “” can be omitted. In earlier versions, the machine’s hostname was used as a default server name.

nginx 禁止未绑定域名或 IP 访问 80 和 443 端口实践

Nginx uses ‘Host’ header for server_name matching. It does not use TLS SNI. This means that for an SSL server, nginx must be able to accept SSL connection, which boils down to having certificate/key. The cert/key can be any, e.g. self-signed.

  1. server {
  2. server_name _;
  3. listen 80 default_server;
  4. listen 443 ssl default_server;
  5. ## To also support IPv6, uncomment this block
  6. # listen [::]:80 default_server;
  7. # listen [::]:443 ssl default_server;
  8. ssl_certificate <path to cert>;
  9. ssl_certificate_key <path to key>;
  10. return 444; # or whatever
  11. }

详细的配置步骤

  1. # 未设置 default_server,会根据列表第一个 server 服务,产生垃圾流量
  2. xxx - - [27/Feb/2020:15:17:58 +0800] "GET / HTTP/1.1" 301 162 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.116 Safari/537.36"
  3. xxx - - [27/Feb/2020:15:18:24 +0800] "GET /api HTTP/1.1" 301 162 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.116 Safari/537.36"
  4. # 手动生成本地 ssl 公私钥
  5. openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/ssl/nginx.key -out /etc/nginx/ssl/nginx.crt
  6. # 增加 default_server
  7. cat << 'EOF' > /etc/nginx/sites-available/000_default
  8. server {
  9. listen 80 default_server;
  10. listen 443 ssl default_server;
  11. ssl_certificate /etc/nginx/ssl/nginx.crt;
  12. ssl_certificate_key /etc/nginx/ssl/nginx.key;
  13. server_name _;
  14. return 444;
  15. access_log /var/log/nginx/000_default.access.log;
  16. error_log /var/log/nginx/000_default.error.log;
  17. }
  18. EOF
  19. # 设置软链接
  20. ln -s /etc/nginx/sites-available/000_default /etc/nginx/sites-enabled/000_default
  21. # reload nginx
  22. nginx -t
  23. nginx -s reload
  24. # 查看日志,检查其他域名是否正常
  25. tailf /var/log/nginx/000_default.access.log
  26. xxx - - [27/Feb/2020:12:15:52 +0800] "GET /api HTTP/1.1" 444 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.116 Safari/537.36"