为什么Nginx会响应任何域名?

168

我已经使用Ruby/Sinatra应用程序成功地将nginx启动并运行良好。但是,我现在尝试从同一服务器上运行第二个应用程序,并注意到了一些奇怪的问题。首先,这是我的nginx.conf文件:

pid /tmp/nginx.pid;
error_log /tmp/nginx.error.log;

events {
  worker_connections 1024;
  accept_mutex off;
}

http {
  default_type application/octet-stream;
  access_log /tmp/nginx.access.log combined;

  sendfile on;
  tcp_nopush on;
  tcp_nodelay off;

  gzip on;
  gzip_http_version 1.0;
  gzip_proxied any;
  gzip_min_length 500;
  gzip_disable "MSIE [1-6]\.";
  gzip_types text/plain text/xml text/css
             text/comma-separated-values
             text/javascript application/x-javascript
             application/atom+xml;

  upstream app {
    server unix:/var/www/app/tmp/sockets/unicorn.sock fail_timeout=0;
  }

  server {
    listen 80;
    client_max_body_size 4G;
    server_name FAKE.COM;

    keepalive_timeout 5;

    root /var/www/app/public;

    location / {
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      proxy_redirect off;

      if (!-f $request_filename) {
        proxy_pass http://app;
        break;
      }
    }

    error_page 500 502 503 504 /500.html;
    location = /500.html {
      root /var/www/app/public;
    }
  }
}
                                                          68,0-1        B

注意到 server_name 被设置为 FAKE.COM,但服务器仍会响应通过其他域名访问该服务器的所有主机。我如何使该特定服务器仅响应对 FAKE.COM 的请求?


listen fake.com | something.com:80 命令过滤器,而不是 server_name - Alexei Martchenko
8个回答

233
在nginx配置中的第一个服务器块是所有请求的默认值,这些请求命中服务器但没有特定服务器块的情况下会使用该默认值。假设你的真实域名为REAL.COM,在你的配置中,当用户输入该域名时,它会解析到你的服务器上。由于此设置没有任何服务器块,因此FAKE.COM的服务器块(在你的情况下是唯一的服务器块)将处理该请求。这就是为什么正确的Nginx配置在其他针对特定域名的服务器块之前有一个指定默认值的服务器块的原因。
# Default server
server {
    return 404;
}

server {
    server_name domain_1;
    [...]
}

server {
    server_name domain_2;
    [...]
}

** 编辑 **

看起来有些用户对这个示例感到有些困惑,并认为它仅限于一个 conf 文件等。

请注意,上述内容是为了让 OP 根据需要开发的一个简单示例。

我个人使用单独的虚拟主机配置文件,如下(CentOS/RHEL):

http {
    [...]
    # Default server
    server {
        return 404;
    }
    # Other servers
    include /etc/nginx/conf.d/*.conf;
}

/etc/nginx/conf.d/目录包含domain_1.conf、domain_2.conf... domain_n.conf等配置文件,在主nginx.conf文件的server块后被引用,该文件始终是第一个并且除非在其他地方使用default_server指令覆盖掉,否则始终是默认的。

在这种情况下,其他服务器的conf文件名的字母顺序变得无关紧要。

此外,这种安排提供了很大的灵活性,可以定义多个默认值。

在我的特定情况下,我让Apache仅监听内部接口上的端口8080,并将PHP和Perl脚本代理到Apache.

然而,我运行两个不同的应用程序,它们都会在输出的HTML中带有“:8080”的链接,因为它们检测到Apache未在标准端口80上运行并试图“帮助”我。

这导致一个问题,链接变得无效,因为Apache不能从外部接口访问,链接应该指向端口80。

我通过创建Port 8080的默认服务器来解决此问题以重定向此类请求。

http {
    [...]
    # Default server block for undefined domains
    server {
        listen 80;
        return 404;
    }
    # Default server block to redirect Port 8080 for all domains
    server {
        listen my.external.ip.addr:8080;
        return 301 http://$host$request_uri;
    }
    # Other servers
    include /etc/nginx/conf.d/*.conf;
}

因为常规服务器块中没有任何内容监听Port 8080,所以由于其在nginx.conf中的位置,重定向默认服务器块可以透明地处理此类请求。

实际上我有四个这样的服务器块,这只是一个简化的用例。


1
Nginx需要区分大小写 - "Server"应该是server,"Return"应该是return。希望这可以在复制此代码时节省一些麻烦。 - Capaj
2
静态的,见 Oleg Neumyvkin 的回答 - 如果您在 sites-available 中有多个配置文件,则按字母顺序排列的第一个文件中的第一个服务器是默认值。我怀疑这可能是一个问题。此外,许多发行版运行“nginx -t”来测试配置文件以进行重新启动 - 您可能有一个阻止重新启动的错误。 - jwhitlock
3
这个回答不完整,不能被接受。要使其有效,您还需要从任何监听指令中删除default_server。 - Ben
@ben 首先,OP在他的问题示例中没有“default_server”,而答案是根据该问题的具体情况量身定制的。其次,为什么在按照使第一个定义的服务器成为默认值的说明时,任何人都会在单独的服务器位置上定义default_server,这超出了我的理解范围。无论如何,如果已经明确定义了default_server,则不应查看此Q/A集以解决可能遇到的任何问题。 - Dayo
1
@Dayo,你说得对,你解决了OP发布的特定配置。但是为了完整回答问题,我认为有必要提到default_server。很可能读者会看不出default_server会如何干扰它。这更有可能发生,因为一些发行版在一个文件中定义了default_server,这对用户来说可能不太明显。 - Ben
显示剩余5条评论

75

在IT技术中,您应该设置一个默认服务器来处理所有请求。如果没有匹配的请求,您可以选择返回HTTP状态码404,或者更好的方式是不做任何响应(这将节省一些带宽)。您可以使用nginx来实现此功能,返回特定于nginx的HTTP响应状态码444,它将关闭连接并不返回任何内容。

server {
    listen       80  default_server;
    server_name  _; # some invalid name that won't match anything
    return       444;
}

对于nginx 1.8.0,这对我有用。在早期版本的nginx中,我没有server_name _;,但它仍然有效。现在对于更新的nginx版本,似乎需要server_name _;。谢谢。 - daniel
2
@iTech:由于某些原因,它对所有请求返回444。有什么线索吗? - Divick
3
关于 444 的这个技巧很棒,而且在我看来比返回错误代码更加简洁的解决方案。 - qqilihq
1
重要的是要指定证书/密钥,否则所有SSL连接都将匹配失败,正如@AndreyT在下面的回答中指出的那样。 - Mark Fletcher
小心!在444上我得到了非常奇怪的行为。我更喜欢使用官方的HTTP状态码,如400。 - Impulse The Fox
显示剩余3条评论

67
为了回答你的问题——如果没有匹配的服务器,nginx选择第一个服务器。请参见文档
如果它的值与任何服务器名称都不匹配,或者请求根本不包含此标头字段,则nginx将路由请求到该端口的默认服务器。 在上面的配置中,默认服务器是第一个服务器...
现在,如果你想要一个默认的捕获所有服务器,比如对所有请求都使用404响应,那么可以这样做:
server {
    listen 80 default_server;
    listen 443 ssl default_server;
    server_name _;
    ssl_certificate <path to cert>
    ssl_certificate_key <path to key>
    return 404;
}

请注意,您需要指定证书/密钥(可以是自签名的),否则所有SSL连接都将失败,因为nginx将尝试使用此default_server接受连接并找不到cert/key。


11
我不知道为什么这个答案排在列表的最后。这个答案直接回答了问题,没有被其他闪亮的东西分散注意力。 - mmc
1
这帮助我解决了一个问题,我试图从非www重定向到www,我必须在这个重定向路由中包含我的ssl证书,否则它会尝试获取我的默认ssl证书,而这是属于另一个域的。 - endyourif
5
这似乎是大多数配置的最佳答案...不确定现在还有谁在使用没有SSL的nginx,但事实是这是唯一一个覆盖SSL的选项,这非常说明问题。 - mpowered
似乎根本不需要 server_name _; - Julien Salinas
如果您启用了SSL,则这是正确的答案,您可以推断出来,但我没有,所以谢谢andreycpp! - Siamore
我也喜欢这个答案——它是一个好的、简洁的解释,并且它考虑了TLS。 - Torsten Römer

36

我无法通过任何其他答案解决我的问题。最终,我检查主机是否匹配并在不匹配时返回403,解决了这个问题。(我发现一些随意的网站链接到我的Web服务器内容)。我猜测这是为了劫持搜索排名。

server {
    listen 443;
    server_name example.com;

    if ($host != "example.com") {
        return 403;
    }

    ...
}

1
如果使用if语句是一种不好的做法:https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/ - Esolitos
1
有时候你会遇到无法避免使用 if 的情况,比如当你需要测试一个没有等价指令的变量时。 - Edward
6
@Esolitos文章的第三行字面上说这种用法是可以的。我理解需要谨慎,但不要对合理的情况指责。 - mpowered
在我看来,最简单且最简洁的解决方案是只需要三行代码即可,你可以很愉快地将它们粘贴到任何虚拟主机文件中,只需稍后更改所比较的$host即可。 - Akito

27

有几种指定默认服务器的方法。

第一种方式 - 如果您将服务器配置保存在一个配置文件中(如Dayo所示),可以首先在列表中指定默认服务器。

第二种方式(更好) 更加灵活 - 为listen指令提供default_server参数,例如:

server {
    listen  *:80 default_server;
    root /www/project/public/;
}

这里有更多的信息:Nginx文档 / Listen

当您将服务器配置保存在单独的文件中并且不想按字母顺序命名这些文件时,此方法更加实用。


3
这应该是正确的答案。被接受的答案是误导性的。如果另一个服务器被配置为默认服务器,则仅将另一个服务器添加到配置中将无法解决问题。 - Ben
1
@Pavel,提供的答案可以在多个单独的虚拟主机文件中使用,没有任何理由不能这样做。此外,通过这种方式,我知道我的默认服务器始终位于主要的nginx.conf文件中,不必记住我的许多单独的虚拟主机文件中哪一个包含它。 - Dayo
不要忘记同时监听443端口以支持SSL(请参考@AndreyT的回答)。 - Constantinos

9

关于答案的小注释:

如果您有几个虚拟主机,在sites-available/中有几个配置文件和几个IP,则按字母顺序从第一个文件获取该IP的“默认”域。

正如Pavel所说,“listen”指令有“default_server”参数http://nginx.org/en/docs/http/ngx_http_core_module.html#listen


6
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    listen 443 default_server;
    listen [::]:443 default_server;
    return 444;
}

如果您想忽略那些具有不需要的Host或根本没有Host的请求,请创建一个汇聚服务器,使每个可能的address:port对都默认为该服务器,并在那里执行return 444;
不必支持HTTP/2、HTTP/3或SSL连接到该汇聚服务器。对于这些不受支持的连接,服务器会拒绝此类连接。
对于被支持类型的连接,它将立即挂断连接而不需要发送响应,这就是return 444;的作用。

来自文档

http://nginx.org/en/docs/http/request_processing.html

...NGINX仅测试请求头字段“Host”,以确定要将请求路由到哪个服务器。如果其值与任何服务器名称不匹配,或请求根本不包含此标头字段,则NGINX将将请求路由到该端口的默认服务器。

https://nginx.org/en/docs/http/ngx_http_core_module.html#listen

...如果存在default_server参数,则会使该服务器成为指定的address:port对的默认服务器。


1
运行 nginx -t 命令告诉我需要定义 ssl_certificate - Paul
@Paul,如果你想为sink支持TLS。这可能是一个误报。我在答案中提供的配置对我来说效果很好。 - Константин Ван
运行 sudo service nginx restart 命令会打印出 Job for nginx.service failed because the control process exited with error code.,并且所有配置的站点都报告服务器不可用。似乎 nginx 拒绝在 443 端口加载站点,除非配置了 ssl_certificate,即使只是一个简单的私有证书也是如此。 - Paul
它并不强制我启用,只是如果我想要使用您的配置与端口443,则必须在服务器块中配置ssl_certificate,否则nginx会出现故障。版本1.18.0。 - Paul
1
对于nginx版本1.23.0,我可以确认该答案完美运行,无需指定证书。 - Samir Seth
显示剩余2条评论

0
为避免nginx配置失败,您可能需要使用自签名证书。但请记住,不要在nginx配置的其他部分中使用自签名证书。
# /etc/nginx/sites-available/fallback
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;
    server_name _;

    include snippets/snakeoil.conf;

    return 444;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接