Django 400 Bad Request with DEBUG=False (tested)
When Django returns 400 Bad Request only after setting DEBUG = False, the request's host is missing from ALLOWED_HOSTS. The log says Invalid HTTP_HOST header and names the host to add.
1. Reproduce it: DEBUG=False, host not listed
import logging, sys
import django
from django.conf import settings
from django.http import HttpResponse
from django.urls import path
def home(request): return HttpResponse('ok')
urlpatterns = [path('', home)]
settings.configure(ROOT_URLCONF=__name__, SECRET_KEY='dev', DEBUG=False, ALLOWED_HOSTS=['example.com'],
MIDDLEWARE=['django.middleware.common.CommonMiddleware'])
django.setup()
logging.basicConfig(stream=sys.stdout, format='log: %(name)s %(message)s')
logging.getLogger('django.request').disabled = True # only show the django.security log
from django.test import Client
c = Client()
for host in ['example.com', 'www.example.com', '127.0.0.1', 'example.com:8000']:
r = c.get('/', headers={'Host': host})
print(host, '->', r.status_code, r.content[:60])
Output:
example.com -> 200 b'ok'
log: django.security.DisallowedHost Invalid HTTP_HOST header: 'www.example.com'. You may need to add 'www.example.com' to ALLOWED_HOSTS.
Traceback (most recent call last):
File "/usr/local/lib/python3.14/site-packages/django/core/handlers/exception.py", line 56, in inner
response = get_response(request)
File "/usr/local/lib/python3.14/site-packages/django/utils/deprecation.py", line 441, in __call__
response = self.process_request(request)
File "/usr/local/lib/python3.14/site-packages/django/middleware/common.py", line 48, in process_request
host = request.get_host()
File "/usr/local/lib/python3.14/site-packages/django/http/request.py", line 210, in get_host
raise DisallowedHost(msg)
django.core.exceptions.DisallowedHost: Invalid HTTP_HOST header: 'www.example.com'. You may need to add 'www.example.com' to ALLOWED_HOSTS.
www.example.com -> 400 b'\n<!doctype html>\n<html lang="en">\n<head>\n <title>Bad Reques'
log: django.security.DisallowedHost Invalid HTTP_HOST header: '127.0.0.1'. You may need to add '127.0.0.1' to ALLOWED_HOSTS.
Traceback (most recent call last):
File "/usr/local/lib/python3.14/site-packages/django/core/handlers/exception.py", line 56, in inner
response = get_response(request)
File "/usr/local/lib/python3.14/site-packages/django/utils/deprecation.py", line 441, in __call__
response = self.process_request(request)
File "/usr/local/lib/python3.14/site-packages/django/middleware/common.py", line 48, in process_request
host = request.get_host()
File "/usr/local/lib/python3.14/site-packages/django/http/request.py", line 210, in get_host
raise DisallowedHost(msg)
django.core.exceptions.DisallowedHost: Invalid HTTP_HOST header: '127.0.0.1'. You may need to add '127.0.0.1' to ALLOWED_HOSTS.
127.0.0.1 -> 400 b'\n<!doctype html>\n<html lang="en">\n<head>\n <title>Bad Reques'
example.com:8000 -> 200 b'ok'
With DEBUG = False, every request whose Host header is not in ALLOWED_HOSTS gets a plain 400 Bad Request page. The real reason is only in the logs: DisallowedHost: Invalid HTTP_HOST header: 'www.example.com'. You may need to add 'www.example.com' to ALLOWED_HOSTS. Note that 'example.com' does not cover www.example.com, and the server's own IP (here 127.0.0.1) is a separate host too. The port is ignored: example.com:8000 passed.
2. Fix: list every host clients use
import logging, sys
import django
from django.conf import settings
from django.http import HttpResponse
from django.urls import path
def home(request): return HttpResponse('ok')
urlpatterns = [path('', home)]
settings.configure(ROOT_URLCONF=__name__, SECRET_KEY='dev', DEBUG=False, ALLOWED_HOSTS=['.example.com', 'localhost', '127.0.0.1'],
MIDDLEWARE=['django.middleware.common.CommonMiddleware'])
django.setup()
logging.basicConfig(stream=sys.stdout, format='log: %(name)s %(message)s')
logging.getLogger('django.request').disabled = True # only show the django.security log
from django.test import Client
c = Client()
for host in ['example.com', 'www.example.com', '127.0.0.1', 'example.com:8000']:
r = c.get('/', headers={'Host': host})
print(host, '->', r.status_code, r.content[:60])
Output:
example.com -> 200 b'ok'
www.example.com -> 200 b'ok'
127.0.0.1 -> 200 b'ok'
example.com:8000 -> 200 b'ok'
A leading dot, '.example.com', matches the domain and all its subdomains. Add localhost/127.0.0.1 if you test the production settings locally or a health check hits the server by IP. Avoid ['*'] in production: the Host check guards against Host-header attacks (e.g. poisoned password-reset links).
The check runs when something calls request.get_host(); in the trace above that is CommonMiddleware, which is in the default MIDDLEWARE. If you still get 400 after deploying, read the django.security.DisallowedHost log line: it names the exact host to add.
Related: Django CSRF verification failed, Django That port is already in use.
Frequently asked questions
Why does Django give 400 Bad Request when DEBUG is False?
The Host header is not in ALLOWED_HOSTS. The django.security.DisallowedHost log line names the host to add.
Does ALLOWED_HOSTS = ['example.com'] allow www.example.com?
No. Add 'www.example.com' too, or use '.example.com' to match the domain and all subdomains.
Written with AI assistance. Measured on 2026-10-23 with Django 6.1.2 and Python 3.14.7 (python:3-alpine in Docker, Django test client). Each block was run verbatim and the output pasted unedited. PlainHeaders is our extension; Python and Django are free.