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

2020-02-27 17:04:13 +08:00
 wsgzao

前言

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 端口过滤不合理的流量。

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

更新历史

2020 年 02 月 26 日 - 初稿

阅读原文 - https://wsgzao.github.io/post/nginx-default-server/


Server_name 指令

如果根据 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 匹配,例如:

server {
     listen       80;
     server_name  www.domain.com;
     ...
}

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

server {
     listen       80;
     server_name  *.domain.com;
     ...
}

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

server {
     listen       80;
     server_name  www.*;
     ...
}

( 4 )匹配正则表达式:

server {
     listen       80;
     server_name  ~^(?.+)\.domain\.com$;
     ...
}

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

server {
    listen  80   default_server;
    server_name  _;    return 444;
}

通过返回 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:

server {
    listen       80;
    server_name  example.org
                 www.example.org
                 ""
                 192.168.1.1
                 ;
    ...
}

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

server {
    listen       80  default_server;
    server_name  _;
    return       444;
}

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:

server {
    listen      80;
    server_name example.org www.example.org;
    ...
}

server {
    listen      80;
    server_name example.net www.example.net;
    ...
}

server {
    listen      80;
    server_name example.com www.example.com;
    ...
}

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:

server {
    listen      80 default_server;
    server_name example.net www.example.net;
    ...
}

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:

server {
    listen      80;
    server_name "";
    return      444;
}

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.

server {
    server_name _;
    listen 80 default_server;
    listen 443 ssl default_server;

    ## To also support IPv6, uncomment this block
    # listen [::]:80 default_server;
    # listen [::]:443 ssl default_server;

    ssl_certificate <path to cert>;
    ssl_certificate_key <path to key>;
    return 444; # or whatever
}

详细的配置步骤

# 未设置 default_server,会根据列表第一个 server 服务,产生垃圾流量
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"
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"

# 手动生成本地 ssl 公私钥
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/ssl/nginx.key -out /etc/nginx/ssl/nginx.crt

# 增加 default_server
cat << 'EOF' > /etc/nginx/sites-available/000_default
server {
    listen 80 default_server;
    listen 443 ssl default_server;
    ssl_certificate        /etc/nginx/ssl/nginx.crt;
    ssl_certificate_key    /etc/nginx/ssl/nginx.key;
    server_name _;
    return 444;
    access_log /var/log/nginx/000_default.access.log;
    error_log /var/log/nginx/000_default.error.log;
} 
EOF

# 设置软链接
ln -s /etc/nginx/sites-available/000_default /etc/nginx/sites-enabled/000_default

# reload nginx
nginx -t
nginx -s reload

# 查看日志,检查其他域名是否正常
tailf /var/log/nginx/000_default.access.log

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"

参考文章

nginx Server names

How nginx processes a request

Properly setting up a “default” nginx server for https

理解 Nginx 中 Server 和 Location 的匹配逻辑

2156 次点击
所在节点    程序员
5 条回复
wslsq
2020-02-27 22:11:47 +08:00
感谢分享
YUX
2020-02-28 01:25:21 +08:00
学习了
julyclyde
2020-02-28 14:34:47 +08:00
看你把一句话写成好几 K,本来我期待在最末尾有个广告链接
但是没有
失望啊差评!
wsgzao
2020-02-28 14:55:31 +08:00
@julyclyde #3 本来就是个人 Blog,纯粹的互联网分享,可能哪天写着写着就和大多数人一样停更了。因为是涉及技术类,换作几年前的自己可能和大多数网络上的分享一样直接写个几句话的结果就完事了,不思考不追根溯源,但是现在的知识体系都是相互交融的,既然需要建立系统化的金字塔就应该刻意多问问自己几个 Why,然后多看官网的理论解释,再实践验证结果一下就清晰了。像这篇文章最简单的一个区别就是网上有人写 default,有人写 default_server,有人加 ssl 参数又有人说不用,到底相信什么。做技术随着年龄的增长和家庭的琐事没有办法做到面面俱到总会有跟不上时代的错误认知,尽可能不给自己留坑也尽量不给别人留坑吧
neroransom
2020-02-28 15:06:35 +08:00
感谢博主

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/648099

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX