Http request not caught by nginx server block?
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 }};
}
}
Solution 1:
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.