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

Flask Gunicorn Service Failed to Start

If your Flask Gunicorn service fails to start in production, this guide shows you how to identify the startup failure, correct the systemd or Gunicorn configuration, and bring the app back online. Use it to fix failed units, bad paths, missing dependencies, environment issues, and socket binding errors step-by-step.

Quick Fix / Quick Setup

Run these commands first:

bash
sudo systemctl status gunicorn --no-pager -l
sudo journalctl -u gunicorn -n 100 --no-pager
sudo systemctl cat gunicorn

# Validate the app manually as the service user
sudo -u www-data /path/to/venv/bin/gunicorn --bind 127.0.0.1:8000 wsgi:app

# After fixing paths/user/env, reload and restart
sudo systemctl daemon-reload
sudo systemctl restart gunicorn
sudo systemctl enable gunicorn

Most startup failures come from an incorrect ExecStart path, wrong working directory, missing virtualenv package, wrong WSGI module, or a service user that cannot access the app directory or socket.

What’s Happening

systemd starts Gunicorn using the unit file, configured user, working directory, environment, and ExecStart command. If any part of that chain is invalid, systemd marks the service as failed before Nginx can proxy requests to Flask.

Typical failures include import errors, missing Python packages, permission problems, bad bind targets, and invalid unit configuration.

Step-by-Step Guide

  1. Check the current failure state
    Run:
    bash
    sudo systemctl status gunicorn --no-pager -l
    

    Look for:
    • code=exited
    • status=203/EXEC
    • Permission denied
    • Python traceback
    • bind errors
  2. Read the Gunicorn service logs
    Run:
    bash
    sudo journalctl -u gunicorn -n 200 --no-pager
    

    Identify whether the failure happens:
    • before Gunicorn starts
    • during Flask app import
    • while binding the socket or port
  3. Inspect the active systemd unit
    Run:
    bash
    sudo systemctl cat gunicorn
    

    Verify these fields:
    • User=
    • Group=
    • WorkingDirectory=
    • Environment=
    • EnvironmentFile=
    • ExecStart=
  4. Confirm the Gunicorn binary exists in the expected virtualenv
    Run:
    bash
    ls -l /path/to/venv/bin/gunicorn
    source /path/to/venv/bin/activate
    pip show gunicorn flask
    

    If packages are missing:
    bash
    pip install gunicorn flask
    
  5. Verify the working directory and project files
    Run:
    bash
    cd /path/to/app && ls -la
    

    Confirm the Flask project root contains the expected entrypoint, such as:
    • wsgi.py
    • app.py
  6. Test the WSGI import manually
    Run:
    bash
    cd /path/to/app
    source /path/to/venv/bin/activate
    python -c "from wsgi import app; print(app)"
    

    If this fails, fix:
    • module names
    • import paths
    • app object name
    • configuration loading during import
  7. Run Gunicorn manually as the service user
    Use the same user configured in the unit file:
    bash
    sudo -u www-data bash -lc 'cd /path/to/app && /path/to/venv/bin/gunicorn --bind 127.0.0.1:8000 wsgi:app'
    

    This is one of the fastest ways to expose:
    • path problems
    • permission failures
    • missing environment variables
    • import errors
  8. Validate or replace the systemd service definition
    Example working unit:
    ini
    [Unit]
    Description=Gunicorn for Flask app
    After=network.target
    
    [Service]
    User=www-data
    Group=www-data
    WorkingDirectory=/var/www/myapp
    Environment="PATH=/var/www/myapp/venv/bin"
    ExecStart=/var/www/myapp/venv/bin/gunicorn --workers 3 --bind 127.0.0.1:8000 wsgi:app
    Restart=always
    
    [Install]
    WantedBy=multi-user.target
    

    Save it as:
    bash
    /etc/systemd/system/gunicorn.service
    
  9. Validate environment variables
    If your app depends on secret keys, database URLs, or Flask settings, load them with Environment= or EnvironmentFile=.
    Example:
    ini
    [Service]
    Environment="FLASK_ENV=production"
    Environment="SECRET_KEY=replace-me"
    EnvironmentFile=/etc/myapp/myapp.env
    

    Then verify the file exists and is readable:
    bash
    ls -l /etc/myapp/myapp.env
    
  10. Check directory and file permissions
    Run:
    bash
    namei -l /var/www/myapp
    ls -ld /var/www/myapp
    ls -la /var/www/myapp
    

    Confirm the service user can:
    • traverse parent directories
    • read project files
    • access the virtualenv
    • create the socket file if using Unix sockets
  11. Validate port or Unix socket binding
    For a TCP bind:
    bash
    /path/to/venv/bin/gunicorn --bind 127.0.0.1:8000 wsgi:app
    

    Check whether the port is already in use:
    bash
    ss -ltnp | grep 8000
    

    For a Unix socket bind:
    bash
    /path/to/venv/bin/gunicorn --bind unix:/run/gunicorn/myapp.sock wsgi:app
    

    Ensure the socket directory exists:
    bash
    sudo mkdir -p /run/gunicorn
    sudo chown www-data:www-data /run/gunicorn
    
  12. Verify the systemd unit syntax
    Run:
    bash
    sudo systemd-analyze verify /etc/systemd/system/gunicorn.service
    

    Fix any invalid directives or formatting errors.
  13. Reload systemd after changing the unit file
    Run:
    bash
    sudo systemctl daemon-reload
    
  14. Restart the service and verify it starts
    Run:
    bash
    sudo systemctl restart gunicorn
    sudo systemctl status gunicorn --no-pager -l
    

    Expected result:
    • active (running)
  15. Confirm Gunicorn is listening
    For TCP:
    bash
    ss -ltnp | grep 8000
    curl -I http://127.0.0.1:8000
    

    For Unix sockets:
    bash
    ss -lx | grep gunicorn
    
  16. If Nginx is in front of Gunicorn, test the full stack
    First validate Nginx:
    bash
    sudo nginx -t
    

    Then test upstream connectivity and request flow. If Nginx still cannot reach Gunicorn, continue with Fix: Nginx Not Connecting to Gunicorn (Connection Refused).
    For a complete baseline setup, see Deploy Flask with Nginx + Gunicorn (Step-by-Step Guide).

Common Causes

  • Wrong ExecStart path → systemd cannot launch Gunicorn → point ExecStart to the correct virtualenv binary and reload systemd.
  • Incorrect WorkingDirectory → Gunicorn cannot import the Flask app module → set the project root correctly in the unit file.
  • Bad WSGI target such as app:app vs wsgi:app → Gunicorn starts but app import fails → use the correct module and object name.
  • Missing dependencies in the virtualenv → startup raises ModuleNotFoundError → install requirements in the same virtualenv used by the service.
  • Environment variables not loaded → app crashes during import or config initialization → add Environment= or EnvironmentFile= to the service.
  • Service user lacks permission to app files or socket path → Gunicorn cannot read code or create bind target → fix ownership and directory permissions.
  • Port already in use or socket path invalid → Gunicorn cannot bind → free the port, remove stale sockets, or fix the bind path.
  • Unit file changed without systemctl daemon-reload → old configuration still used → reload systemd and restart the service.
  • Syntax errors or runtime errors in app startup code → Gunicorn exits immediately → test imports manually and review traceback in logs.
  • SELinux or restrictive server policy blocks file or socket access → service appears correctly configured but still fails → inspect security policy and file contexts where applicable.

Debugging Section

Use these commands during diagnosis:

bash
sudo systemctl status gunicorn --no-pager -l
sudo journalctl -u gunicorn -n 200 --no-pager
sudo journalctl -xe --no-pager
sudo systemctl cat gunicorn
sudo systemd-analyze verify /etc/systemd/system/gunicorn.service
ls -l /path/to/venv/bin/gunicorn
cd /path/to/app && source /path/to/venv/bin/activate && python -c "from wsgi import app; print(app)"
sudo -u www-data bash -lc 'cd /path/to/app && /path/to/venv/bin/gunicorn --bind 127.0.0.1:8000 wsgi:app'
namei -l /path/to/app
ls -ld /path/to/app /path/to/app/*
ss -ltnp | grep 8000
ss -lx | grep gunicorn
sudo nginx -t
curl -I http://127.0.0.1:8000

What to look for:

  • ModuleNotFoundError or ImportError → Flask app import problem
  • Permission denied → service user cannot access code, virtualenv, or socket directory
  • No such file or directory → bad path in ExecStart, WorkingDirectory, or environment file
  • Failed at step EXEC or 203/EXEC → invalid executable path or non-executable binary
  • address already in use errors → port or socket conflict
  • service works manually but not in systemd → unit file environment, user, or working directory mismatch

If the Gunicorn process becomes active but requests still fail, check Nginx upstream configuration and reverse proxy behavior in Fix: Nginx Not Connecting to Gunicorn (Connection Refused).

Checklist

  • gunicorn binary path in ExecStart exists and is executable.
  • WorkingDirectory points to the Flask project root.
  • WSGI module reference is correct, such as wsgi:app.
  • The virtualenv contains Gunicorn and Flask dependencies.
  • The systemd service user can read the app files and enter all parent directories.
  • Required environment variables are available to the service.
  • The configured port or Unix socket can be created and is not already in use.
  • sudo systemctl daemon-reload was run after editing the unit file.
  • sudo systemctl restart gunicorn completes successfully.
  • systemctl status gunicorn shows active (running).

For final production verification, use Flask Production Checklist (Everything You Must Do).

FAQ

Q: What is the fastest way to identify why the Gunicorn service failed?
A: Check systemctl status gunicorn first, then read journalctl -u gunicorn for the exact startup error or traceback.

Q: Why does systemd report the service failed immediately after start?
A: Gunicorn usually exited during app import, failed to execute the configured binary, or could not bind the configured port or socket.

Q: Can a wrong virtualenv cause the service to fail?
A: Yes. If ExecStart points to the wrong environment, Gunicorn or Flask dependencies may be missing.

Q: Do I need to run Gunicorn as the same user configured in systemd?
A: Yes. Manual tests should use the same service user to reproduce permission and environment issues accurately.

Q: Should I debug with a TCP bind before using a Unix socket?
A: Yes. Binding to 127.0.0.1:8000 is often easier to validate during troubleshooting.

Q: What does code=exited, status=203/EXEC mean?
A: systemd could not execute the command in ExecStart, usually because the path is wrong or not executable.

Q: Why does Gunicorn work manually but fail as a service?
A: The service is likely using a different user, working directory, PATH, or environment variable set.

Q: Do I need daemon-reload after changing the unit file?
A: Yes. systemd does not apply unit file changes until you reload its configuration.

Final Takeaway

A failed Gunicorn service is usually caused by a systemd configuration error, an app import failure, or a permission problem. Verify the unit file, test the app manually as the service user, and confirm the bind target and environment before restarting the service.