1

I have an nginx server with exactly one site configuration (the default site).

I keep seeing the following lines in my access.log

xxx.xxx.xxx.xxx - - [10/Jul/2018:16:32:14 +0200] "GET / HTTP/1.1" 400 37 "-" "-"

I know for a fact that this happens if the request doesn't send a Host:header, as in curl -v -H "Host:" https://{{ server_ip }}

root@mypc:~# curl -v -H "Host:" http://{{ server_ip }}
* Rebuilt URL to: http://{{ server_ip }}/
*   Trying {{ server_ip }}...
* Connected to {{ server_ip }} ({{ server_ip }}) port 80 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 400 Bad Request
< Server: nginx/1.14.0 (Ubuntu)
< Date: Wed, 11 Jul 2018 08:14:07 GMT
< Content-Type: text/html
< Content-Length: 182
< Connection: close
<
<html>
<head><title>400 Bad Request</title></head>
<body bgcolor="white">
<center><h1>400 Bad Request</h1></center>
<hr><center>nginx/1.14.0 (Ubuntu)</center>
</body>
</html>
* Closing connection 0

I did configure my site to respond to requests with no Host: header with an 444 error code. Obviously, this doesn't work. I assume I did misunderstand the docs and my config is incorrect. I would be glad if someone could point out what's missing:

ssl_certificate /etc/letsencrypt/live/{{ hostname_fqdn }}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{{ hostname_fqdn }}/privkey.pem;

# Respond to requests without a host header with HTTP status 444
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name "";
    return 444;
}

server {
    listen 80;
    listen [::]:80;

    server_name {{ hostname_fqdn }};

    if ($allowed_country = no) {
        return 444;
    }

    return 301 https://{{ hostname_fqdn }}$request_uri;
}

server {
    listen 443 ssl;
    #listen [::]:443 ssl;

    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    server_name {{ hostname_fqdn }};

    if ($allowed_country = no) {
        return 444;
    }

    location / {
        include uwsgi_params;
        uwsgi_pass unix:/run/uwsgi/app/{{ django_project_name }}/socket;
    }

    location /static/ {
        root {{ django_static_root }};
    }
}

server {
    listen 80;
    listen [::]:80;

    listen 443 ssl;
    #listen [::]:443 ssl;

    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    server_name api.zeitschrift.hausbesitzerverlag.de;

    if ($allowed_country = no) {
        return 444;
    }

    location / {
        include uwsgi_params;
        uwsgi_pass unix:/run/uwsgi/app/{{ django_project_name }}/socket;
    }

    location /pmx-api/ {
        auth_basic "Restricted Content";
        auth_basic_user_file /etc/nginx/htpasswd;

        limit_req zone=api burst=5;

        include uwsgi_params;
        uwsgi_pass unix:/run/uwsgi/app/{{ django_project_name }}/socket;
    }

    location /komtrigon-api/ {
        limit_req zone=api burst=5;

        include uwsgi_params;
        uwsgi_pass unix:/run/uwsgi/app/{{ django_project_name }}/socket;
    }

    location /static/ {
        root {{ django_static_root }};
    }
}
6
  • I think you are getting a Bad request, because you are using http in the Host header http://{{ server_ip }} – c4f4t0r Jul 11 '18 at 9:34
  • Nah, just -H "Host:". I think you missed the ending quote. – Daniel Jul 11 '18 at 9:41
  • Now is clear, you are sending the Host header without any value :) – c4f4t0r Jul 11 '18 at 12:57
  • @c4f4t0r -H "Host:" does not send the host header at all. – Rick May 31 '19 at 4:06
  • 1
    @c4f4t0r Yes, with HTTP/1.0 it's valid. – Rick May 31 '19 at 8:52
4

HTTP requests without a Host: header are not valid in HTTP/1.1 (or later), and nginx is correctly sending a 400 Bad Request error without even attempting to serve it with any server block. From RFC 7230 section 5.4:

A server MUST respond with a 400 (Bad Request) status code to any HTTP/1.1 request message that lacks a Host header field and to any request message that contains more than one Host header field or a Host header field with an invalid field-value.

Your server block which serves requests without a Host: header will only be hit when a request comes in with the ancient HTTP/1.0. You can simulate this using curl with the -0 command line option:

curl -v -0 http://203.0.113.87/

From the man page:

       -0, --http1.0
              (HTTP)  Tells  curl to use HTTP version 1.0 instead of using its
              internally preferred HTTP version.
2
  • Hey Michael, your answer seemed plausible, but then I figured out that the 400-code was served by the application behind it, so it wasn't nginx that returned the 400. Guess they don't follow that RFC. But I noticed that the spammers were doing a https-request, which made me aware that the default server didn't listen to ssl requests. Rookie mistake. Still, thanks for making me aware about that RFC. It's good to know! – Daniel Jul 11 '18 at 13:47
  • @Daniel Michael is right about this. Try a plain nginx server. Set a sever block with server_name "", and test it without the Host header by curl -v -H "Host:" http://localhost, you can't reach that server block. But you can reach it with additional -0. – Rick May 31 '19 at 3:58
0

Beginner's mistake. I forgot to add listen 443 ssl default_server; to the first server block.

...

server {
    listen 80 default_server;
    listen 443 ssl default_server;
    listen [::]:80 default_server;
    server_name "";
    return 444;
}

...

Although Micheal is completely correct about the RFC, it wasn't true in regards on how nginx works, as nginx has no built-in functionality that returns a 400 in that case.

It also didn't specifically answer my question. Since 10 out of 10 times my web server receives a request without host header, the request is sent by a spammer, I decided to ignore the RFC and just close the connection without letting the spammer know (return 444;).

0

I think you misunderstood something.

I did configure my site to respond to requests with no Host: header with an 444 error code. Obviously, this doesn't work.

You does not configure specifically your site to respond to requests with no Host header with 444.

It is: you configure your site that any host value that do not match other server_name value in server blocks, will come to the default_server block.

It's the default_server directive that actually matters. you can put any logically invaild value in server_name, within the default server, server block.

https://nginx.org/en/docs/http/server_names.html

There is nothing special about this name(server_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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.