Reference Guide Detailed deployment notes with production context and concrete examples.

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:

bash
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

  1. Confirm where the 500 is generated.
    Check the public endpoint and the backend directly:
    bash
    curl -I https://your-domain.com
    curl -I http://127.0.0.1:8000
    

    If Gunicorn returns 500 directly, the issue is inside the app or Gunicorn process.
  2. Check Gunicorn service status.
    bash
    sudo systemctl status gunicorn --no-pager
    

    Look for:
    • failed workers
    • import errors
    • missing modules
    • wrong working directory
    • permission problems
  3. 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 final 500 wrapper.
  4. Validate Nginx configuration.
    bash
    sudo nginx -t
    

    If the config test fails, correct syntax or upstream settings, then reload:
    bash
    sudo systemctl reload nginx
    
  5. 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
  6. Verify Gunicorn bind target matches Nginx upstream.
    Example Nginx upstream using TCP:
    nginx
    upstream 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:
    bash
    gunicorn -w 3 -b 127.0.0.1:8000 wsgi:app
    

    Example using a Unix socket:
    nginx
    location / {
        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.
  7. Test the Flask app process directly.
    bash
    curl -v http://127.0.0.1:8000
    

    If direct access fails, the problem is not Nginx.
  8. Check environment variables used in production.
    Inspect the systemd service:
    bash
    sudo 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
    

    Missing SECRET_KEY, DATABASE_URL, or other required settings often trigger startup or request-time exceptions.
  9. 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:
    bash
    cd /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.
  10. Check database connectivity.

Test from the production environment:

bash
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.

  1. Apply pending migrations.

If logs show missing tables or columns:

bash
cd /path/to/app
source venv/bin/activate
flask db upgrade
  1. Check file and directory permissions.

Ensure the Gunicorn user can read the app code, templates, static files, and config files:

bash
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.

  1. Review installed dependencies inside the active environment.
bash
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.

  1. Restart services after fixes.
bash
sudo systemctl restart gunicorn
sudo systemctl reload nginx

Then test again:

bash
curl -I https://your-domain.com
curl -v http://127.0.0.1:8000
  1. Improve logging if the traceback is incomplete.

Start with service logs:

bash
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 variablesSECRET_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, and module: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 configDEBUG=False exposes 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

bash
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

bash
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

bash
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

bash
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

bash
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 -t passes 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 curl to Gunicorn works or returns the expected app response
  • public request no longer returns HTTP 500

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.