Debug Mode in Flask
Flask debug mode is designed to speed up development.
It usually provides:
- automatic reload when code changes
- interactive debugger with stack traces
Enabling debug mode
Section titled “Enabling debug mode”Option A: environment variable
Section titled “Option A: environment variable”export FLASK_DEBUG=1
flask runOption B: app.run
Section titled “Option B: app.run”app.run(debug=True)Why debug mode is dangerous in production
Section titled “Why debug mode is dangerous in production”The interactive debugger can allow code execution if it’s exposed.
Rule of thumb:
- Debug mode is fine locally.
- Never enable debug mode on a public server.
In production, you’ll use Gunicorn (or another WSGI server) and configuration-based logging.
Auto reload: what to know
Section titled “Auto reload: what to know”When auto-reload is enabled, Flask may start your app twice (a reloader process + the actual app).
If your code does something at import time (like sending emails, starting a scheduler, etc.), it may run twice.
Best practice:
- keep side effects out of module import time
- put startup code behind guards or proper app factories
Debug mode starts your program twice
Section titled “Debug mode starts your program twice”This is the behaviour that confuses everyone the first time. With the reloader on, there are two processes: a watcher that never serves requests, and a child that does. Module-level code runs in both.
flowchart TD S["python app.py with debug=True"] --> P["parent process
WERKZEUG_RUN_MAIN unset
watches files, restarts the child"] P --> C["child process
WERKZEUG_RUN_MAIN='true'
actually serves requests"] C --> R["file changes -> child killed and re-spawned"] R --> C
Measured output from a real run:
MODULE EXECUTED pid=17536 WERKZEUG_RUN_MAIN=None
* Serving Flask app 'reload_demo'
* Debug mode: on
* Running on http://127.0.0.1:5099
* Restarting with stat
MODULE EXECUTED pid=8324 WERKZEUG_RUN_MAIN='true'
* Debugger is active!
* Debugger PIN: 126-124-415The module-level print ran twice, in two different processes. Anything expensive
or one-shot at import time — opening a database, spawning a thread, sending a
notification — therefore happens twice in development and once in production, which is
a genuinely confusing class of bug.
import os
# runs only in the process that actually serves requests
if os.environ.get("WERKZEUG_RUN_MAIN") == "true" or not app.debug:
start_background_worker()What debug=True switches on
Section titled “What debug=True switches on”| setting | default | with debug=True |
|---|---|---|
app.debug | False | True |
| reloader | off | on — restarts on file change |
| interactive debugger | off | on, guarded by a PIN |
TEMPLATES_AUTO_RELOAD | None (follows debug) | effectively on |
| unhandled exception | 500 page | traceback, and the exception propagates |
# debug off: the client gets a generic page, the traceback goes to the log
GET /boom -> 500, body starts b'<!doctype html>\n<html lang=en>\n<title>'
# debug on: the exception reaches you (and the browser gets an interactive console)
GET /boom -> ValueError: kaboomSee it move
Section titled “See it move”Check yourself
Section titled “Check yourself”-
With debug=True, a print at module level appeared twice with two different pids. Why?
Measured pid 17536 with WERKZEUG_RUN_MAIN unset, then pid 8324 with it set to 'true'. Guard one-shot setup with that variable or it happens twice in development.
pch.quizShowAnswer
B — the reloader runs a parent watcher process and a child that serves requests; module code runs in both — Measured pid 17536 with WERKZEUG_RUN_MAIN unset, then pid 8324 with it set to 'true'. Guard one-shot setup with that variable or it happens twice in development.
-
Why must debug mode never be enabled on a public server?
The traceback console runs code inside your process. The PIN is a speed bump, not a control. Reachable debug mode is a full compromise of the host.
pch.quizShowAnswer
B — the interactive debugger allows arbitrary Python execution in your process, so it is a remote code execution hole — The traceback console runs code inside your process. The PIN is a speed bump, not a control. Reachable debug mode is a full compromise of the host.
-
With debug off, what does a view that raises ValueError return to the client?
Measured: status 500 and a body beginning '<!doctype html>'. With debug on the exception propagates instead, which is why tests often set it.
pch.quizShowAnswer
B — a generic 500 HTML page, with the traceback sent to the log — Measured: status 500 and a body beginning '<!doctype html>'. With debug on the exception propagates instead, which is why tests often set it.
pch.coffeeTagline
pch.coffeeCtapch.feedbackHeading
pch.feedbackSubheading