Django 500 error with DEBUG=False (tested)

When Django returns Server Error (500) only with DEBUG = False, the default logging hides the traceback. Log django.request to the console to see it; the most common cause is Missing staticfiles manifest entry (run collectstatic).

Setup used in every block

common.py builds a minimal project with DEBUG=False and returns a test client that turns exceptions into 500 responses like a real server; show() prints the status and the start of the body. The views and the template:

import sys; sys.path.insert(0, '/w/proj')
import django
from django.conf import settings
def setup(**extra):
    conf = dict(DEBUG=False, SECRET_KEY='x', ALLOWED_HOSTS=['testserver'], ROOT_URLCONF='urls',
        INSTALLED_APPS=['django.contrib.staticfiles'], STATIC_URL='static/', STATIC_ROOT='/tmp/static',
        TEMPLATES=[{'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': ['/w/proj/templates']}])
    conf.update(extra)
    settings.configure(**conf)
    django.setup()
    from django.test import Client
    return Client(raise_request_exception=False)
def show(r):
    print(r.status_code, repr(r.content.decode().strip()[:120]))
from django.urls import path
from django.shortcuts import render
def boom(request):
    return 1 / 0
def home(request):
    return render(request, 'home.html')
urlpatterns = [path('boom/', boom), path('', home)]
{% load static %}<link rel="stylesheet" href="{% static "site.css" %}">Home

1. DEBUG=False: a 500 page and no traceback

from common import *
c = setup()                      # Django's default LOGGING
show(c.get('/boom/'))
print('(nothing else was printed: no traceback on the console)')

Output:

500 '<!doctype html>\n<html lang="en">\n<head>\n  <title>Server Error (500)</title>\n</head>\n<body>\n  <h1>Server Error (500)</h1>'
(nothing else was printed: no traceback on the console)

With DEBUG = False the yellow debug page is replaced by a bare Server Error (500) page, and with Django's default logging nothing is printed either: the default console handler has the require_debug_true filter, and errors go to mail_admins instead, which sends nothing unless ADMINS and email are configured. (Your server, e.g. gunicorn, may still log the request line, but not Django's traceback.)

2. Fix step 1: log the traceback

from common import *
c = setup(LOGGING={'version': 1, 'disable_existing_loggers': False,
    'handlers': {'console': {'class': 'logging.StreamHandler', 'stream': 'ext://sys.stdout'}},
    'loggers': {'django.request': {'handlers': ['console'], 'level': 'ERROR'}}})
show(c.get('/boom/'))

Output:

Internal Server Error: /boom/
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/core/handlers/base.py", line 199, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/w/proj/urls.py", line 4, in boom
    return 1 / 0
           ~~^~~
ZeroDivisionError: division by zero
500 '<!doctype html>\n<html lang="en">\n<head>\n  <title>Server Error (500)</title>\n</head>\n<body>\n  <h1>Server Error (500)</h1>'

A LOGGING setting that sends the django.request logger to a StreamHandler prints Internal Server Error: /boom/ with the full traceback, even with DEBUG = False. Add it to your production settings so the cause of every 500 lands in the server log.

3. The classic cause: missing staticfiles manifest entry

from common import *
c = setup(STATICFILES_DIRS=['/w/proj/static'],
          STORAGES={'staticfiles': {'BACKEND':
          'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'}})
from django.template.loader import render_to_string
r = c.get('/')
print('GET / ->', r.status_code)
try:
    render_to_string('home.html')
except ValueError as e:
    print('ValueError:', e)
from django.core.management import call_command
call_command('collectstatic', interactive=False, verbosity=1)
r = c.get('/')
print('GET / ->', r.status_code, r.content.decode().strip())

Output:

GET / -> 500
ValueError: Missing staticfiles manifest entry for 'site.css'

1 static file copied to '/tmp/static', 1 post-processed.
GET / -> 200 <link rel="stylesheet" href="/static/site.3c7253c0b179.css">Home

The page works with DEBUG = True but returns 500 with DEBUG = False when you use ManifestStaticFilesStorage (or WhiteNoise's CompressedManifestStaticFilesStorage, which subclasses it) and collectstatic has not been run, or a template references a file that does not exist: {% static %} raises ValueError: Missing staticfiles manifest entry for 'site.css'. Run python manage.py collectstatic as part of every deploy and fix any path it reports as missing.

Separately, runserver serves static files only when DEBUG = True (or with runserver --insecure); with DEBUG = False the CSS 404s but the page itself does not 500.

4. Optional: a custom 500.html

import shutil; shutil.copy('/w/500.html.txt', '/w/proj/templates/500.html')
from common import *
c = setup()
show(c.get('/boom/'))

Output:

500 'Sorry, something broke on our side.'

A 500.html in a template directory replaces the default body (here it contains one line of text). Keep it simple: it is rendered without a request context, so avoid tags that may fail themselves.

Related: Django 400 Bad Request with DEBUG=False, 500 Internal Server Error.

Frequently asked questions

Why does Django show Server Error (500) only when DEBUG is False?

Often ManifestStaticFilesStorage without collectstatic: {% static %} raises Missing staticfiles manifest entry. Configure LOGGING for django.request to see the exact traceback.

Why is there no traceback in the console with DEBUG=False?

Django's default console handler only logs when DEBUG is True; errors go to mail_admins. Add a LOGGING config with a StreamHandler for django.request.

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.