Nginx未拦截无效Host header致Django报DisallowedHost错误排查
我运行一个基于Django的站点,在应用错误日志中发现如下错误:
django.core.exceptions.DisallowedHost: Invalid HTTP_HOST header: 'badhost.com'. You may need to add 'badhost.com' to ALLOWED_HOSTS.
我原本认为下方的Nginx配置(已精简)能阻止这类请求到达Django应用,尤其是最后一个server块,但显然未生效。我的配置存在什么问题?
我的目标是让Nginx直接拒绝带有无效Host header的请求,或者是否有办法在应用日志中过滤这类错误信息?
我的Nginx配置:
server { server_name mysite.com www.mysite.com; listen 80; return 302 https://$host$request_uri; } server { server_name mysite.com www.mysite.com; root /home/myuser/mysite.com/public/; location / { try_files $uri @proxy_to_app; } location @proxy_to_app { proxy_pass http://localhost:8001; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $remote_addr; } listen 443 ssl; # managed by Certbot ssl_certificate /etc/letsencrypt/live/mysite.com/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/mysite.com/privkey.pem; # managed by Certbot } server { listen 80 default_server; listen 443 ssl default_server; ssl_certificate /etc/letsencrypt/live/mysite.com/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/mysite.com/privkey.pem; # managed by Certbot return 444; }
问题分析
你的Nginx配置核心问题是默认拦截的server块位置错误,导致无SNI的HTTPS请求或部分无效Host请求被合法域名的server块处理,最终转发到Django。
Nginx对HTTPS请求的处理逻辑是:如果客户端不支持SNI(老旧浏览器/工具),无法通过SNI选择server块时,会直接使用配置中第一个监听443端口的server块,而非标记了default_server的块。你的配置中,合法域名的HTTPS server块排在默认拦截块前面,导致这类请求被错误转发。
解决方案
1. 修复Nginx配置,拦截所有无效请求
调整server块顺序,让默认拦截块排在最前面,确保所有不匹配合法域名的请求被直接拒绝:
# 优先放置默认拦截块,处理所有无效Host请求 server { listen 80 default_server; listen 443 ssl default_server; ssl_certificate /etc/letsencrypt/live/mysite.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/mysite.com/privkey.pem; return 444; # 直接关闭连接,不返回响应 } # 处理合法域名的HTTP跳转 server { server_name mysite.com www.mysite.com; listen 80; return 302 https://$host$request_uri; } # 处理合法域名的HTTPS请求 server { server_name mysite.com www.mysite.com; root /home/myuser/mysite.com/public/; location / { try_files $uri @proxy_to_app; } location @proxy_to_app { proxy_pass http://localhost:8001; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $remote_addr; } listen 443 ssl; ssl_certificate /etc/letsencrypt/live/mysite.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/mysite.com/privkey.pem; }
修改后执行重载命令生效:
sudo nginx -s reload
2. 可选:在Django中过滤DisallowedHost错误日志
如果需要临时屏蔽这类错误日志,可在Django项目的settings.py中添加日志过滤器:
LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'filters': { 'skip_disallowed_hosts': { '()': 'django.utils.log.CallbackFilter', 'callback': lambda record: 'Invalid HTTP_HOST header' not in record.getMessage() } }, 'handlers': { 'console': { 'class': 'logging.StreamHandler', 'filters': ['skip_disallowed_hosts'], }, 'file': { 'class': 'logging.FileHandler', 'filename': '/var/log/django/app.log', 'filters': ['skip_disallowed_hosts'], }, }, 'root': { 'handlers': ['console', 'file'], 'level': 'INFO', }, }
重启Django应用后,DisallowedHost类错误将不再出现在日志中。
内容的提问来源于stack exchange,提问作者Jonah Bishop

