How to Turn Off Flask Debug Mode in Production

Running Flask with debug=True in production turns on the Werkzeug interactive debugger and shows full tracebacks to anyone who triggers an error. A /debug route that returns os.environ is even worse - it hands out every secret. Delete the route, set debug to False, and confirm both leaks are closed. Here is how.

Start this project - freeLive cloud workspace in your browser · lands on your portfolio
Security Engineerpythonflaskdebug

Two leaks that often travel together

During development it is normal to add helpers that make debugging easier. Two of them are dangerous when they reach production:

@app.route("/debug")
def debug_info():
    return jsonify({
        "environment": dict(os.environ),
        "python_path": sys.path,
        "working_directory": os.getcwd(),
    })

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8000, debug=True)

The /debug route returns every environment variable. In most deployments, that is where the database password, API keys, and signing secrets live:

curl -s localhost:8000/debug | head -c 200
# {"environment":{"API_KEY":"sk-live-...","DB_PASSWORD":"...",...

debug=True does more than reload the code on save. It enables the Werkzeug interactive debugger: when a request raises an exception, the browser shows the full traceback with source code and local variables, and a console that can run Python inside the server process. Werkzeug protects that console with a PIN, but the traceback page itself already shows code and data, and a PIN is not a safe barrier on a public server.

Check whether the debugger is on

You do not need to cause an error to see it. The debugger serves its own static files, so a request for one of them tells you whether it is active:

curl -s "localhost:8000/health?__debugger__=yes&cmd=resource&f=debugger.js" | head -n 3

If you get JavaScript back, the debugger is live. If you get the normal response of the route, it is off.

Remove the debug route

Delete the whole route. Do not hide it behind a secret URL or a query parameter - a hidden route is found sooner or later by a scanner or a leaked log. If you really need runtime information in production, expose a small, safe set of values (version, uptime, health of dependencies), never the environment.

After you delete it, Flask returns 404 for the path:

curl -s -o /dev/null -w '%{http_code}\n' localhost:8000/debug
# 404

Turn debug mode off

Change the app.run call:

app.run(host="0.0.0.0", port=8000, debug=False)

Debug mode is read when the server starts, so restart the process after the change. A code reload alone is not enough.

A better pattern is to never write debug=True in the code at all, and to control it from the environment only in development:

import os
app.run(host="0.0.0.0", port=8000,
        debug=os.environ.get("FLASK_DEBUG") == "1")

Or use the Flask CLI, where debug mode is a flag you pass on your own machine:

flask --app app run --debug          # development only

Do not use the development server in production

app.run() starts the Werkzeug development server. It is meant for local work. In production, run Flask behind a WSGI server such as gunicorn:

gunicorn --bind 0.0.0.0:8000 --workers 2 app:app

gunicorn imports the app object and does not call app.run(), so the interactive debugger is never started. This is the strongest guard, because the production command cannot turn the debugger on even if someone leaves debug=True in the file.

Verify everything still works

After the restart, check the three things that matter:

curl -s -o /dev/null -w '%{http_code}\n' localhost:8000/debug         # 404
curl -s "localhost:8000/health?__debugger__=yes&cmd=resource&f=debugger.js"
# {"status":"ok"}  <- normal response, no debugger
curl -s localhost:8000/products                                       # real data

If secrets were exposed

If the debug route was reachable from the internet, treat every value it returned as leaked. Rotate the database password, API keys, and signing secrets, and check access logs for requests to /debug. Removing the route stops future leaks, but it does not undo the ones that already happened.

Keep it from coming back

Do this project yourself. It's ready in a live cloud workspace in your browser - when you finish, it lands on your portfolio.

Start this project - free

What you'll practice

FAQ

Is it safe to run Flask with debug=True in production?

No. Debug mode enables the Werkzeug interactive debugger, which shows tracebacks with source code and local variables and offers a Python console in the browser. Always run production with debug off, ideally under a WSGI server like gunicorn.

How do I check if the Flask debugger is enabled?

Request a debugger resource on any route, for example /health?__debugger__=yes&cmd=resource&f=debugger.js. If JavaScript comes back, the debugger is active. If you get the route's normal response, it is off.

Does the Werkzeug debugger PIN make debug mode safe?

No. The PIN only guards the console. Traceback pages still show code and data, and a PIN is a weak control on a public server. The right setting is debug off in production.

What should I do if a debug endpoint exposed my environment variables?

Remove the endpoint, then rotate every secret it returned - database passwords, API keys, and signing keys - and review access logs for requests to it.

Keep learning

Remove Hardcoded Credentials From Source CodeSecurity projectFind Hardcoded API Keys With gitleaksSecurity projectHarden Session Cookie Security FlagsSecurity projectSecurity roadmapStep by step to hiredSecurity interview questionsSTAR answersAll Security projectsProjects hub

Do this project yourself. It's ready in a live cloud workspace in your browser - when you finish, it lands on your portfolio.

Start this project - free
Start this project - free