Flask systemd + Gunicorn Service Setup
If you're trying to run a Flask app under Gunicorn as a managed Linux service, this guide shows you how to set up systemd step-by-step. The goal is a reliable service that starts on boot, restarts on failure, and can be controlled with standard systemctl commands.
Quick Fix / Quick Setup
Create a basic systemd unit, reload systemd, start the service, and verify the socket:
sudo tee /etc/systemd/system/myflask.service > /dev/null <<'EOF'
[Unit]
Description=Gunicorn service for Flask app
After=network.target
[Service]
User=www-data
Group=www-data
WorkingDirectory=/srv/myflask
Environment="PATH=/srv/myflask/venv/bin"
ExecStart=/srv/myflask/venv/bin/gunicorn --workers 3 --bind unix:/run/myflask.sock wsgi:app
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now myflask
sudo systemctl status myflask --no-pager
ls -l /run/myflask.sock
Replace /srv/myflask, the virtualenv path, service name, socket path, and wsgi:app with your actual project values. Use a TCP bind like 127.0.0.1:8000 instead of a Unix socket if that matches your Nginx setup.
What’s Happening
systemdruns Gunicorn as a managed background service.- Gunicorn serves the Flask WSGI app and listens on a Unix socket or local TCP port.
systemdhandles startup order, restarts, logs, and boot-time enablement.- Most failures come from incorrect paths, wrong WSGI entrypoint, missing virtualenv dependencies, or socket permission mismatches.
Step-by-Step Guide
- Verify the Flask app runs manually
Confirm the app works before introducing systemd:bashsource /srv/myflask/venv/bin/activate cd /srv/myflask gunicorn --bind 127.0.0.1:8000 wsgi:app
If this fails, fix the app import or dependencies first. - Create or verify the WSGI entrypoint
Examplewsgi.py:pythonfrom app import app
Your Gunicorn target must match this file and object:bashwsgi:app - Choose a bind method
Use one of these:
Unix socket:bashunix:/run/myflask.sock
TCP:bash127.0.0.1:8000
Match this exactly in your Nginx upstream configuration. - Create the systemd unit filebash
sudo nano /etc/systemd/system/myflask.service - Add the unit configuration
Recommended example:ini[Unit] Description=Gunicorn service for Flask app After=network.target [Service] User=www-data Group=www-data WorkingDirectory=/srv/myflask Environment="PATH=/srv/myflask/venv/bin" Environment="FLASK_ENV=production" ExecStart=/srv/myflask/venv/bin/gunicorn --workers 3 --bind unix:/run/myflask.sock wsgi:app Restart=always RestartSec=3 [Install] WantedBy=multi-user.target - Add environment variables if needed
If your app depends on secrets or database settings, use inlineEnvironment=values or an environment file.
Example environment file:bashsudo tee /etc/myflask.env > /dev/null <<'EOF' SECRET_KEY=change-me DATABASE_URL=postgresql://user:pass@localhost/dbname EOF
Then update the service:ini[Service] EnvironmentFile=/etc/myflask.env - Reload systemdbash
sudo systemctl daemon-reload - Start the servicebash
sudo systemctl start myflask - Enable the service at bootbash
sudo systemctl enable myflask - Verify the service state
sudo systemctl status myflask --no-pager
ps aux | grep gunicorn
- Verify the listener
For TCP:
ss -ltnp | grep 8000
For Unix socket:
ls -l /run/myflask.sock
- Confirm Nginx points to the same upstream
Example Nginx upstream for Unix socket:
location / {
proxy_pass http://unix:/run/myflask.sock;
include proxy_params;
}
Example Nginx upstream for TCP:
location / {
proxy_pass http://127.0.0.1:8000;
include proxy_params;
}
- Test restart behavior
sudo systemctl restart myflask
sudo systemctl status myflask --no-pager
- Review logs for startup and import errors
sudo journalctl -u myflask -n 100 --no-pager
- Apply updates safely after unit changes
If you edit the service file again:
sudo systemctl daemon-reload
sudo systemctl restart myflask
For full reverse proxy setup, see Deploy Flask with Nginx + Gunicorn (Step-by-Step Guide).
Common Causes
- Wrong
ExecStartpath → systemd cannot find Gunicorn → use the full virtualenv binary path such as:bash/srv/myflask/venv/bin/gunicorn - Incorrect WSGI target → Gunicorn cannot import the app → verify the module and object, for example:bash
wsgi:app - Wrong
WorkingDirectory→ relative imports or file paths fail → pointWorkingDirectoryto the project root. - Environment variables missing under systemd → app starts manually but fails as a service → add
Environment=lines orEnvironmentFile=. - Socket path mismatch with Nginx → service runs but Nginx returns 502 → make the Gunicorn bind path match the Nginx upstream exactly. See Fix Flask 502 Bad Gateway (Step-by-Step Guide).
- Permission issue on Unix socket or app files → Nginx or Gunicorn cannot access required paths → align
User,Group, and file permissions. - Virtualenv dependencies not installed → worker boot fails with
ModuleNotFoundError→ install dependencies inside the same virtualenv used byExecStart. - Service edited without
daemon-reload→ changes do not apply → run:bashsudo systemctl daemon-reload
Debugging Section
Check service status:
sudo systemctl status myflask --no-pager
Read recent logs:
sudo journalctl -u myflask -n 100 --no-pager
Follow logs live:
sudo journalctl -u myflask -f
Show the loaded service file:
sudo systemctl cat myflask
Reload and restart after changes:
sudo systemctl daemon-reload
sudo systemctl restart myflask
Check running Gunicorn processes:
ps aux | grep gunicorn
Check Unix socket:
ls -l /run/myflask.sock
Check TCP listener:
ss -ltnp | grep 8000
Test as the service user:
sudo -u www-data /srv/myflask/venv/bin/gunicorn --bind 127.0.0.1:8000 wsgi:app
Test Python import directly:
cd /srv/myflask && /srv/myflask/venv/bin/python -c "import wsgi; print(wsgi.app)"
Validate Nginx config:
sudo nginx -t
What to look for:
ModuleNotFoundErroror import errorsPermission deniedon app files or socket paths- wrong
WorkingDirectory - missing environment variables
- socket or port mismatch between Gunicorn and Nginx
- immediate restart loops in
systemd
If the unit refuses to start, see Flask Gunicorn Service Failed to Start. For systemd behavior and unit syntax, see systemd Basics for Flask Deployments.
Checklist
- Service file is saved in
/etc/systemd/system/ -
WorkingDirectorypoints to the project root -
ExecStartuses the full path to the Gunicorn binary in the virtualenv - WSGI module path matches the real app entrypoint
-
UserandGroupcan read the app files - Socket or TCP bind matches the Nginx upstream
- systemd daemon was reloaded after unit changes
- Service is enabled and running
-
journalctlshows no import or permission errors
Before go-live, validate against Flask Production Checklist (Everything You Must Do).
Related Guides
- Deploy Flask with Nginx + Gunicorn (Step-by-Step Guide)
- Fix Flask 502 Bad Gateway (Step-by-Step Guide)
- Flask Gunicorn Service Failed to Start
- Flask Production Checklist (Everything You Must Do)
FAQ
Q: Should I run Gunicorn as root?
A: No. Run it as a non-root service user such as www-data or a dedicated app user.
Q: Where should the service file go?
A: Use /etc/systemd/system/<service>.service for a custom system service.
Q: How do I apply changes to the service file?
A: Run sudo systemctl daemon-reload and then restart the service.
Q: What is the correct ExecStart format?
A: Use the full Gunicorn binary path, bind option, and WSGI target, for example:
/path/to/venv/bin/gunicorn --bind unix:/run/app.sock wsgi:app
Q: Should Gunicorn bind to a socket or a TCP port?
A: Either works. Unix sockets are common with local Nginx reverse proxy setups; TCP is simpler to debug.
Q: Why use systemd instead of running Gunicorn manually?
A: systemd handles boot startup, restarts, logging, and service management.
Q: Why does the service work manually but fail under systemd?
A: systemd uses a different environment, working directory, and PATH than your interactive shell.
Q: Do I need daemon-reload after editing the unit?
A: Yes. Run sudo systemctl daemon-reload before restarting the service.
Q: Why does Nginx still return 502 after the service starts?
A: Usually the upstream in Nginx does not match the Gunicorn socket or port, or the socket permissions are wrong.
Final Takeaway
A correct systemd unit makes Gunicorn predictable in production. Most setup failures are caused by wrong paths, wrong app import target, or mismatched socket settings. Validate the service manually first, then lock in the unit file, enable it, and confirm logs are clean.