167

I have nginx up and running with a Ruby/Sinatra app and all is well. However, I'm now trying to have a second application running from the same server and I noticed something weird. First, here's my 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

Notice how server_name is set to FAKE.COM yet the server is responding to all hosts that hit that server via other domain names. How can I make that particular server respond only to requests for FAKE.COM?

Martin
  • 2,865
  • 4
  • 24
  • 22

8 Answers8

233

The first server block in the nginx config is the default for all requests that hit the server for which there is no specific server block.

So in your config, assuming your real domain is REAL.COM, when a user types that in, it will resolve to your server, and since there is no server block for this setup, the server block for FAKE.COM, being the first server block (only server block in your case), will process that request.

This is why proper Nginx configs have a specific server block for defaults before following with others for specific domains.

# Default server
server {
    return 404;
}

server {
    server_name domain_1;
    [...]
}

server {
    server_name domain_2;
    [...]
}

etc

** EDIT **

It seems some users are a bit confused by this example and think it is limited to a single conf file etc.

Please note that the above is a simple example for the OP to develop as required.

I personally use separate vhost conf files with this as so (CentOS/RHEL):

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

/etc/nginx/conf.d/ will contain domain_1.conf, domain_2.conf... domain_n.conf which will be included after the server block in the main nginx.conf file which will always be the first and will always be the default unless it is overridden it with the default_server directive elsewhere.

The alphabetical order of the file names of the conf files for the other servers becomes irrelevant in this case.

In addition, this arrangement gives a lot of flexibility in that it is possible to define multiple defaults.

In my specific case, I have Apache listening on Port 8080 on the internal interface only and I proxy PHP and Perl scripts to Apache.

However, I run two separate applications that both return links with ":8080" in the output html attached as they detect that Apache is not running on the standard Port 80 and try to "help" me out.

This causes an issue in that the links become invalid as Apache cannot be reached from the external interface and the links should point at Port 80.

I resolve this by creating a default server for Port 8080 to redirect such requests.

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;
}

As nothing in the regular server blocks listens on Port 8080, the redirect default server block transparently handles such requests by virtue of its position in nginx.conf.

I actually have four of such server blocks and this is a simplified use case.

Mr.Moe
  • 507
  • 1
  • 5
  • 19
Dayo
  • 12,413
  • 5
  • 52
  • 67
  • 1
    Nginx requires case sensitivity-"Server" should be server and "Return" should be return. Hope this saves a few troubles when copying this code. – Capaj Feb 25 '13 at 08:28
  • 2
    static, see Oleg Neumyvkin's answer - if you have multiple config files in sites-available, then the first server in the first file by alphabetical order is your default. I suspect this might be an issue. Also, many distributions run an 'nginx -t' to test the config before restarting - you may have an error preventing a restart. – jwhitlock Dec 12 '13 at 21:10
  • 3
    This is incomplete and should not be the accepted answer. For this to work you would also have to remove default_server from any listen directives. – Ben Sep 10 '14 at 00:02
  • @ben Firstly, the OP did not have "default_server" in his problem example and the answer is tailored to the specifics of that question. Secondly, why anyone would define default_server on a separate server location when following instructions to make the first defined server the default is beyond me. In any case, if a default_server has been specifically defined, then this Q/A set is not what should be looked at to resolve whatever issues they may be having. – Dayo Sep 11 '14 at 20:24
  • 1
    @Dayo You are correct that you addressed the specific config posted by the OP. But for a complete answer of question asked, I think it is necessary to mention the default_server. It's very possible to read your answer and not realize how default_server would interfere with it. It's even more likely because some distros shipping with default_server defined in a file that may not be obvious to the user. – Ben Sep 11 '14 at 21:01
  • @ben Lets agree to disagree on the point of whether an answer that directly and correctly addresses a question should be accepted or not based on how it addresses the use case of an unrelated third party. – Dayo Sep 12 '14 at 00:30
  • Hi guys! I've just spent two hours pulling my hair. A moment I add an IP address or a hostname to the listen directive (listen 192.168.1.10:80;), nginx responds to any server name. Is it intended? – Industrial Control Freak Mar 30 '15 at 06:00
  • You might want to generate a new question explaining your issue in detail by using the "Ask A Question" button on the top right corner of this page in order to get a proper answer. – Dayo Mar 30 '15 at 16:31
  • It's worth noting that leaving 8080 open through the firewall is a security flaw, unless you implement a relatively complex firewall / user permission setup. Leaving a high-numbered port open means that, if the application bound to it ever crashes, a non-privileged user can bind to that port and transparently proxy traffic intended for the main site, nabbing e-mails, passwords, CCNs... Additionally, of course, providing a redirect for every single request means twice the effort per user and twice the latency (because now it's two RTT per request). Helpful answer otherwise, though. – Parthian Shot Apr 24 '15 at 17:20
  • Note that a 444 response is preferred in this case, like noted below by @iTech – ydaniv Apr 29 '15 at 11:06
75

You should have a default server for catch-all, you can return 404 or better to not respond at all (will save some bandwidth) by returning 444 which is nginx specific HTTP response that simply close the connection and return nothing

server {
    listen       80  default_server;
    server_name  _; # some invalid name that won't match anything
    return       444;
}
iTech
  • 18,192
  • 4
  • 57
  • 80
66

To answer your question - nginx picks the first server if there's no match. See documentation:

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...

Now, if you wanted to have a default catch-all server that, say, responds with 404 to all requests, then here's how to do it:

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;
}

Note that you need to specify certificate/key (that can be self-signed), otherwise all SSL connections will fail as nginx will try to accept connection using this default_server and won't find cert/key.

andreycpp
  • 1,109
  • 9
  • 9
  • 11
    I have no idea why this answer is so far down the list. This is the one that answers the question without getting distracted by shiny things along the way. – mmc Nov 20 '18 at 18:06
  • 1
    This helped me solve a problem where I was trying a redirect from non-www to www that I had to include my ssl certificate in this redirect route otherwise is was trying to grab my default ssl cert which was a for a different domain. – endyourif Feb 07 '19 at 00:58
  • 5
    This seems like the best answer for most configurations... not sure who's using nginx without SSL these days, but the fact that this is the only one that covers SSL is extremely telling. – mpowered Feb 08 '19 at 04:18
  • It seems that `server_name _;` is not even needed. – Julien Salinas Aug 08 '19 at 08:19
  • This is the right answer if you have ssl enabled, you could infer it but I didn't, so thank you andreycpp! – Siamore May 04 '21 at 13:32
  • I prefer this answer too - it is a good, concise explanation and it takes care of TLS. – Torsten Römer Mar 23 '22 at 21:05
36

I was unable to resolve my problem with any of the other answers. I resolved the issue by checking to see if the host matched and returning a 403 if it did not. (I had some random website pointing to my web servers content. I'm guessing to hijack search rank)

server {
    listen 443;
    server_name example.com;

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

    ...
}
Matt Carrier
  • 5,602
  • 6
  • 26
  • 30
  • 1
    If are a bad practice: https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/ – Esolitos Oct 07 '17 at 12:56
  • 1
    There are cases where you simply cannot avoid using an if, for example, if you need to test a variable which has no equivalent directive. – Edward May 02 '18 at 16:06
  • 6
    @Esolitos Literally the third line of that article says that this use case is okay. I understand the need to be cautious, but let's not wag fingers at reasonable use cases. – mpowered Feb 08 '19 at 04:22
  • In my opinion, the easiest and most concise solution, as it only requires 3 lines of code you can gladly paste into any vhost file, only changing the compared $host, afterward. – Akito Aug 24 '19 at 15:25
27

There are few ways to specify default server.

First way - Specify default server first in list, if you keep your server configurations in one config file, like Dayo showed above.

Second way (better) More flexible - provide default_server parameter for listen instruction, for example:

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

More information here: Nginx doc / Listen

This way more useful when you keep server configurations in separate files and do not want to name those files alphabetically.

Pavel
  • 3,967
  • 2
  • 29
  • 35
  • 3
    This should be the correct answer. The accepted answer is misleading. Simply adding another server to the config will not solve the problem if another server is configured to be the default server. – Ben Sep 10 '14 at 00:00
  • 1
    @Pavel there is no reason why the provided answer cannot work with multiple separate vhost files. Also, with this, I know that my default server is always in the main nginx.conf file and do not have to remember which one of my many separate vhost files contains this. – Dayo Sep 11 '14 at 11:53
  • Don't forget to listen to 443 as well for ssl (see @AndreyT's response below). – Constantinos Jul 25 '18 at 08:10
9

Little comment to answer:

if you have several virtual hosts on several IPs in several config files in sites-available/, than "default" domain for IP will be taken from first file by alphabetic order.

And as Pavel said, there is "default_server" argument for "listen" directive http://nginx.org/en/docs/http/ngx_http_core_module.html#listen

Oleg Neumyvakin
  • 9,706
  • 3
  • 58
  • 62
6
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    listen 443 default_server;
    listen [::]:443 default_server;
    return 444;
}

If you just want to ignore such requests with unwanted Hosts or no Host at all, create a sink server that every possible pair of address:port defaults to, and do return 444; there.

Don't bother supporting HTTP/2, HTTP/3, or SSL connections for the sink. For the unsupported, the server just refuses such connections.

For connections of the supported type, it’ll just hang the connections up immediately without bothering sending a response, which is what return 444; does.


From the documentation

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

...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.

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

...The default_server parameter, if present, will cause the server to become the default server for the specified address:port pair.

  • 1
    Running `nginx -t` tells me I need to define `ssl_certificate`. – Paul Nov 03 '21 at 03:51
  • @Paul Well, if you want to support TLS for the sink. It’s likely a false positive. The configuration in my answer is what I ended up using and it worked well for me. – Константин Ван Nov 03 '21 at 04:53
  • Running `sudo service nginx restart` prints `Job for nginx.service failed because the control process exited with error code.` and all sites configured report the server is not available. It seems `nginx` refuses to load sites on 443 without `ssl_certificate` configured, even if just a simple private one. – Paul Nov 03 '21 at 13:22
  • @Paul Then it might be something to do with the latest NGINX. Mine back then didn’t force me to enable TLS for _all_ 443 ports. May I know your version? – Константин Ван Nov 04 '21 at 01:35
  • It doesn't force me to enable, it's that if I want to use your configuration with port 443, then there must be a `ssl_certificate` configured in the server block, or else nginx breaks. Version 1.18.0. – Paul Nov 04 '21 at 02:01
  • @Paul You mean, it demands TLS certificates even though you didn’t specify the `ssl` option? Eh, that’s weird. – Константин Ван Nov 04 '21 at 04:18
  • 1
    For nginx version 1.23.0, i can confirm that the answer works perfectly without the need to specify certificates. – Samir Seth Jul 05 '22 at 07:21
0

To avoid nginx config failures for ssl you may want to use self-signed certificates.

But remember not to use self-signed certificates for the rest of your nginx config.

# /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;
}