gunicorn 500 Internal Server Error: where the traceback is (tested)

A gunicorn 500 Internal Server Error means your app raised an exception; gunicorn itself is fine. The traceback is in gunicorn's error log (stderr by default) as Error handling request, or, for Flask, ERROR in app: Exception on /path. Both cases reproduced below.

1. Reproduce with a plain WSGI app

app.py raises on /boom:

def app(environ, start_response):
    if environ['PATH_INFO'] == '/boom':
        1 / 0  # unhandled exception in the app
    start_response('200 OK', [('Content-Type', 'text/plain')])
    return [b'ok']

b1.sh starts gunicorn, requests the failing URL, then prints the error log (startup/shutdown INFO lines filtered, timestamps shortened):

gunicorn app:app --bind 127.0.0.1:8966 --workers 1 > err.log 2>&1 &
until curl -s -o /dev/null 127.0.0.1:8966/; do :; done
curl -s -i 127.0.0.1:8966/boom | tr -d '\r'
echo; echo '--- gunicorn error log ---'
kill %1; wait
grep -v 'Starting\|Listening\|Using worker\|Booting\|Handling signal\|Shutting\|Worker exiting\|Goodbye\|Control socket' err.log | sed 's/^\[[^]]*\] \[[0-9]*\]/[time] [pid]/'

Output (gunicorn 26.2.0, Python 3.13):

HTTP/1.1 500 Internal Server Error
Connection: close
Content-Type: text/html
Content-Length: 141

<html>
  <head>
    <title>Internal Server Error</title>
  </head>
  <body>
    <h1><p>Internal Server Error</p></h1>
    
  </body>
</html>

--- gunicorn error log ---
[time] [pid] [ERROR] Error handling request GET /boom
Traceback (most recent call last):
  File "/tmp/fx384/ve/lib/python3.13/site-packages/gunicorn/workers/sync.py", line 141, in handle
    self.handle_request(listener, req, client, addr)
    ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/fx384/ve/lib/python3.13/site-packages/gunicorn/workers/sync.py", line 184, in handle_request
    respiter = self.wsgi(environ, resp.start_response)
  File "/tmp/g5416/app.py", line 3, in app
    1 / 0  # unhandled exception in the app
    ~~^~~
ZeroDivisionError: division by zero

The browser only sees gunicorn's bare Internal Server Error page. The cause is in the error log: [ERROR] Error handling request GET /boom followed by the full traceback, ending in the exception from your code.

2. Same bug inside Flask

from flask import Flask
app = Flask(__name__)

@app.get('/boom')
def boom():
    return 1 / 0

b2.sh is the same script with fapp:app on port 8967:

HTTP/1.1 500 INTERNAL SERVER ERROR
Server: gunicorn
Date: Fri, 23 Oct 2026 22:22:45 GMT
Connection: close
Content-Type: text/html; charset=utf-8
Content-Length: 265

<!doctype html>
<html lang=en>
<title>500 Internal Server Error</title>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p>

--- gunicorn error log ---
[2026-10-23 23:22:45,915] ERROR in app: Exception on /boom [GET]
Traceback (most recent call last):
  File "/tmp/fx384/ve/lib/python3.13/site-packages/flask/app.py", line 1511, in wsgi_app
    response = self.full_dispatch_request()
  File "/tmp/fx384/ve/lib/python3.13/site-packages/flask/app.py", line 919, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/tmp/fx384/ve/lib/python3.13/site-packages/flask/app.py", line 917, in full_dispatch_request
    rv = self.dispatch_request()
  File "/tmp/fx384/ve/lib/python3.13/site-packages/flask/app.py", line 902, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)  # type: ignore[no-any-return]
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^
  File "/tmp/g5416/fapp.py", line 6, in boom
    return 1 / 0
           ~~^~~
ZeroDivisionError: division by zero

This time Flask catches the exception and renders its own 500 page, so gunicorn never sees it: there is no Error handling request line. The traceback still lands in the same stream because Flask's default logger writes ERROR in app: Exception on /boom [GET] to stderr, which gunicorn's error log captures. Django behaves the same way: with DEBUG=False it returns its own 500 page and logs through the django.request logger (see Django 500 error with DEBUG=False).

3. How to find the cause

Frequently asked questions

Why does gunicorn return 500 Internal Server Error?

An unhandled exception in your WSGI app. gunicorn logs it at ERROR level as "Error handling request GET /path" with the traceback in its error log.

Why is there no "Error handling request" line for my Flask app?

Flask catches the exception and returns its own 500 page; the traceback is logged by Flask as "ERROR in app: Exception on /path [GET]" to the same stderr stream.

Where is the gunicorn error log?

stderr by default (journalctl for systemd, docker logs for containers), or the file given by --error-logfile.

Written with AI assistance. Measured on 2026-10-23 with gunicorn 26.2.0, Flask 3.1.3 and Python 3.13 on Linux. The scripts ran verbatim and the output was pasted unedited (timestamps/pids masked by the script). PlainTables is our extension; gunicorn and Flask are free.