A Django HTTP 400 Bad Request error indicates that the web server rejected an incoming client HTTP request before processing view logic. In production (when DEBUG = False), Django triggers a 400 error for security violations—most commonly caused by unconfigured ALLOWED_HOSTS headers, CSRF token mismatches, or malformed JSON payloads.

Cause 1: Missing Domain in settings.py ALLOWED_HOSTS (Most Common)

When deploying Django to production with DEBUG = False, Django validates incoming Host: headers against ALLOWED_HOSTS. If the domain or IP is missing, Django responds with HTTP 400:

myproject/settings.pypython
# Add your production domain name, subdomains, and server IP address
ALLOWED_HOSTS = [
    'example.com',
    'www.example.com',
    '192.168.1.100',
    'localhost',
    '127.0.0.1'
]

Cause 2: Malformed JSON Payloads in REST APIs

If a client sends an invalid JSON string (e.g. trailing commas or unquoted keys) to a Django REST Framework API, Django throws a 400 Bad Request during request parsing:

views.pypython
import json
from django.http import JsonResponse, HttpResponseBadRequest
 
def api_endpoint(request):
    if request.method == 'POST':
        try:
            data = json.loads(request.body)
        except json.JSONDecodeError:
            return HttpResponseBadRequest(JsonResponse({'error': 'Invalid JSON body format'}))
            
        return JsonResponse({'status': 'success'})