Http request not caught by nginx server block?

Daniel asked:

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

[email protected]:~# 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 }};
    }
}

My answer:


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.

View the full question and any other answers on Server Fault.

Creative Commons License
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.