Running Flask or Django with gunicorn and systemd on a VPS

The server that ships with Flask and Django (flask run, manage.py runserver) is for development, not for the public. On a VPS, the usual combination is gunicorn (which runs the application with several processes) managed by systemd (which starts it with the server and brings it back if it dies), with nginx in front handling HTTPS. This article builds the first two pieces. It is for a VPS with root; on shared hosting you use “Setup Python App” instead.

Step by step

1 Create the virtual environment and install gunicorn in the application folder, with a user of its own (not root): cd /home/appuser/app
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt gunicorn
If pip complains about “externally-managed-environment”, see that error.
2 Test by hand first. Flask: .venv/bin/gunicorn --bind 127.0.0.1:8000 app:appapp:app means “the file app.py, the variable app”. In Django, use the project’s WSGI module: myproject.wsgi:application. If it answers curl http://127.0.0.1:8000, stop with Ctrl+C and move on.
3 Create the service, with sudo nano /etc/systemd/system/my-app.service:[Unit]
Description=My application
After=network.target

[Service]
User=appuser
Group=appuser
WorkingDirectory=/home/appuser/app
EnvironmentFile=/home/appuser/app/.env
ExecStart=/home/appuser/app/.venv/bin/gunicorn --workers 3 --bind 127.0.0.1:8000 app:app
Restart=on-failure

[Install]
WantedBy=multi-user.target
4 Enable and start it:sudo systemctl daemon-reload
sudo systemctl enable --now my-app
sudo systemctl status my-app
enable makes it start with the server; --now starts it right away.
5 Read the logs when something fails: journalctl -u my-app -n 100. After updating the code: sudo systemctl restart my-app.

The points that usually cause trouble

Point What to do
Number of processes (--workers) The 3 above is an example. Each process uses memory: start with few and see what the VPS can take.
The bind address 127.0.0.1 only accepts requests from the server itself, which is what you want behind a proxy. Do not use 0.0.0.0 without a reason.
Secrets In the file pointed to by EnvironmentFile, with chmod 600, not written into the service or the code.
Static files (Django) gunicorn does not serve them well: leave them to nginx, or see Django static files.
The service will not start systemctl status and journalctl tell you why: reading systemctl status.
Always use the virtual environment’s gunicorn in ExecStart, with the full path. systemd does not activate the environment: if you write just gunicorn, either it uses the system’s (and cannot find your libraries) or it finds none at all. And never run the application as root.
HTTPS and the domain are still missing. gunicorn behind nginx is done as in nginx as a reverse proxy: proxy_pass points to http://127.0.0.1:8000. For the general picture of services of your own, see running your own program as a service. On an unmanaged VPS the service is yours to maintain: how far our support goes.

Need a server for your Python application? Have a look at our VPS plans.

See the VPS servers

SEE ALSO

Running a Flask or FastAPI application

How to run your own program as a systemd service that starts at boot

Nginx as a reverse proxy in front of a container

RECOMMENDED PRODUCT

VPS server with root access

Resources of your own, the OS you choose, reinstall whenever you like. from 7.560,00 Kz/mo (3-year plan, with coupon)

See plans
  • 0 Users Found This Useful
Was this answer helpful?