Flask 500 Internal Server Error in Production
If you're seeing a 500 Internal Server Error from a Flask app in production, this guide shows you how to identify the failing layer and fix it step-by-step. The goal is to restore the app, verify the root cause, and prevent repeat failures.
Quick Fix / Quick Setup
Run the core checks first:
sudo journalctl -u gunicorn -n 100 --no-pager
sudo nginx -t
sudo tail -n 100 /var/log/nginx/error.log
curl -I http://127.0.0.1:8000
# If Gunicorn crashed, restart and watch logs
sudo systemctl restart gunicorn
sudo journalctl -u gunicorn -f --no-pager
Most production 500s come from an application exception, missing environment variables, bad database settings, migration issues, or a failing Gunicorn process. Check Gunicorn logs first, then validate Nginx, app config, and database connectivity.
What’s Happening
- HTTP 500 means the request reached your application stack but failed during processing.
- The failure may be in Flask application code, Gunicorn startup, environment loading, database access, template rendering, file permissions, or reverse proxy configuration.
- The fastest path is to isolate which layer fails: Nginx, Gunicorn, Flask app, or a dependency such as PostgreSQL.
Step-by-Step Guide
- Confirm where the 500 is generated.
Check the public endpoint and the backend directly:bashcurl -I https://your-domain.com curl -I http://127.0.0.1:8000
If Gunicorn returns500directly, the issue is inside the app or Gunicorn process. - Check Gunicorn service status.bash
sudo systemctl status gunicorn --no-pager
Look for:- failed workers
- import errors
- missing modules
- wrong working directory
- permission problems
- Read recent Gunicorn logs.bash
sudo journalctl -u gunicorn -n 200 --no-pager
Find the actual Python traceback. Fix the first real exception, not the final500wrapper. - Validate Nginx configuration.bash
sudo nginx -t
If the config test fails, correct syntax or upstream settings, then reload:bashsudo systemctl reload nginx - Check Nginx error logs.bash
sudo tail -n 100 /var/log/nginx/error.log
Look for:- upstream connection failures
- socket path problems
- timeouts
- permission denials
- Verify Gunicorn bind target matches Nginx upstream.
Example Nginx upstream using TCP:nginxupstream flask_app { server 127.0.0.1:8000; } server { listen 80; server_name your-domain.com; location / { proxy_pass http://flask_app; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }
Matching Gunicorn bind:bashgunicorn -w 3 -b 127.0.0.1:8000 wsgi:app
Example using a Unix socket:nginxlocation / { proxy_pass http://unix:/run/gunicorn.sock; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }
Confirm both sides use the same port or socket path. - Test the Flask app process directly.bash
curl -v http://127.0.0.1:8000
If direct access fails, the problem is not Nginx. - Check environment variables used in production.
Inspect the systemd service:bashsudo systemctl cat gunicorn
Example service configuration:ini[Service] User=www-data Group=www-data WorkingDirectory=/path/to/app Environment="FLASK_ENV=production" Environment="SECRET_KEY=change-me" Environment="DATABASE_URL=postgresql://user:pass@db-host/dbname" ExecStart=/path/to/app/venv/bin/gunicorn -w 3 -b 127.0.0.1:8000 wsgi:app
MissingSECRET_KEY,DATABASE_URL, or other required settings often trigger startup or request-time exceptions. - Verify the application entry point.
Confirm Gunicorn references the correct module and app object:bash/path/to/app/venv/bin/gunicorn -w 3 -b 127.0.0.1:8000 wsgi:app
Validate import manually:bashcd /path/to/app source venv/bin/activate python -c "from wsgi import app; print(app)"
If this fails, fix the module path, app object name, or working directory. - Check database connectivity.
Test from the production environment:
cd /path/to/app
source venv/bin/activate
python - <<'PY'
from wsgi import app
print("app import ok")
PY
If your app depends on PostgreSQL, also verify host, port, username, password, SSL mode, and firewall access. If the traceback shows connection errors, correct the database settings before retrying.
- Apply pending migrations.
If logs show missing tables or columns:
cd /path/to/app
source venv/bin/activate
flask db upgrade
- Check file and directory permissions.
Ensure the Gunicorn user can read the app code, templates, static files, and config files:
sudo chown -R www-data:www-data /path/to/app
sudo find /path/to/app -type d -exec chmod 755 {} \;
sudo find /path/to/app -type f -exec chmod 644 {} \;
If the app writes uploads, logs, or temporary files, those directories must also be writable by the service user.
- Review installed dependencies inside the active environment.
cd /path/to/app
source venv/bin/activate
pip freeze | grep -E 'Flask|gunicorn|psycopg|mysql|dotenv'
Missing packages are common after deploys, especially when the wrong virtualenv is active.
- Restart services after fixes.
sudo systemctl restart gunicorn
sudo systemctl reload nginx
Then test again:
curl -I https://your-domain.com
curl -v http://127.0.0.1:8000
- Improve logging if the traceback is incomplete.
Start with service logs:
sudo journalctl -u gunicorn -f --no-pager
Common Causes
- Application exception during request handling → Unhandled Python error in a route, template, or service layer → Read the Gunicorn traceback and fix the first raised exception.
- Missing environment variables →
SECRET_KEY, database URL, API keys, or config flags are not loaded in systemd or Docker → Add the variables in the real production runtime and restart Gunicorn. - Wrong Gunicorn app module or working directory → Gunicorn starts with the wrong import path and workers fail → Correct
ExecStart,WorkingDirectory, andmodule:app. - Database connection failure → Wrong credentials, host, SSL mode, or firewall rules cause runtime failures → Test connectivity and correct database settings.
- Pending migrations → App code expects schema changes that are not applied → Run migration commands before serving traffic.
- Template or file permission problem → Gunicorn cannot read templates, uploads, or config files → Fix ownership and permissions for the service user.
- Dependency mismatch after deploy → New code requires packages not installed in the active virtualenv or image → Reinstall dependencies and redeploy.
- Socket or upstream mismatch → Nginx points to the wrong socket or port → Align Nginx upstream and Gunicorn bind settings.
- Bad production config →
DEBUG=Falseexposes real config gaps such as invalid hosts, secret key issues, or disabled services → Review production settings explicitly. - External service failure → SMTP, cache, object storage, or third-party API errors raise exceptions in the request flow → Add timeouts, retries, and defensive error handling.
Debugging Section
Check the failing layer in this order:
1. Gunicorn service and logs
sudo systemctl status gunicorn --no-pager
sudo journalctl -u gunicorn -n 200 --no-pager
sudo journalctl -u gunicorn -f --no-pager
What to look for:
- Python tracebacks
- module import errors
- worker boot failures
- permission denied messages
- missing environment values
2. Nginx validation and error log
sudo nginx -t
sudo systemctl status nginx --no-pager
sudo tail -n 100 /var/log/nginx/error.log
What to look for:
- invalid config syntax
- upstream connection errors
- socket path mismatches
- timeout events
3. Backend listener state
ps aux | grep gunicorn
sudo ss -ltnp | grep 8000
sudo ss -lx | grep gunicorn
What to look for:
- Gunicorn master and worker processes
- expected TCP port listening
- expected Unix socket present
4. Direct app import and dependency validation
cd /path/to/app && source venv/bin/activate && python -c "from wsgi import app; print(app)"
cd /path/to/app && source venv/bin/activate && pip freeze
What to look for:
- import success
- correct Python environment
- required packages installed
5. Schema and deploy state
cd /path/to/app && source venv/bin/activate && flask db upgrade
What to look for:
- unapplied migrations
- schema drift after deploy
If the issue began after deployment, compare the current release with the last known-good release:
- environment file changes
- dependency changes
- migration state
- Gunicorn service edits
- Nginx upstream edits
Checklist
- gunicorn service is active and not crashing
-
nginx -tpasses without errors - Nginx upstream matches Gunicorn bind target
- Flask app imports successfully in the production environment
- required environment variables are loaded
- database connection succeeds from the app host
- migrations are applied
- file permissions allow Gunicorn to read app files
- direct
curlto Gunicorn works or returns the expected app response - public request no longer returns HTTP 500
Related Guides
- Deploy Flask with Nginx + Gunicorn (Step-by-Step Guide)
- Flask Gunicorn Service Failed to Start
- Flask Environment Variables Not Loading in Production
- Flask Production Checklist (Everything You Must Do)
FAQ
Q: What is the first log to check for a Flask 500 in production?
A: Check Gunicorn logs first. They usually contain the Python traceback that caused the 500.
Q: How do I tell if the problem is Nginx or Flask?
A: Request Gunicorn directly on its port or socket target. If it still returns 500, the issue is in Gunicorn or the Flask app.
Q: Can missing environment variables cause intermittent 500 errors?
A: Yes. Some routes may fail only when they access a missing secret, API key, or database setting.
Q: Why did the app work before deploy and fail after deploy?
A: Common causes are missing dependencies, wrong app entry point, missing migrations, changed config, or stale environment values.
Q: Should I restart both Gunicorn and Nginx after fixing the issue?
A: Yes. Restart Gunicorn after app or environment fixes, and reload Nginx after proxy config changes.
Final Takeaway
A production Flask 500 is usually an application exception, not a generic server problem. Read Gunicorn logs first, validate Nginx second, then verify environment variables, database connectivity, migrations, and file permissions until the direct app request succeeds.