Une application Python : gunicorn et systemd
Exécuter Django, Flask ou FastAPI sur VDS : environnement virtuel, gunicorn avec un socket, service systemd et choix du nombre de workers selon votre palier.
Mis à jour le 23 août 2026
Le serveur de développement intégré (manage.py runserver, flask run) n'est pas conçu pour la charge de production. Sur un VPS, vous installez gunicorn ou uvicorn, et un serveur web les expose vers l'extérieur.
Environnement
N'installez jamais les paquets du projet dans le Python système — une mise à jour du système casserait l'application.
apt install -y python3 python3-venv python3-pip
useradd -r -m -d /srv/app -s /usr/sbin/nologin appuser
sudo -u appuser python3 -m venv /srv/app/venv
sudo -u appuser /srv/app/venv/bin/pip install -r /srv/app/requirements.txt
sudo -u appuser /srv/app/venv/bin/pip install gunicorn
Pour FastAPI, installez en plus uvicorn[standard] — gunicorn l'exécute comme classe de worker.
Service
/etc/systemd/system/myapp.service :
[Unit]
Description=Python app
After=network-online.target postgresql.service
[Service]
User=appuser
WorkingDirectory=/srv/app
EnvironmentFile=/srv/app/.env
ExecStart=/srv/app/venv/bin/gunicorn \
--workers 3 \
--bind unix:/run/myapp/gunicorn.sock \
--access-logfile - \
myproject.wsgi:application
RuntimeDirectory=myapp
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Pour FastAPI, remplacez la ligne de lancement par --worker-class uvicorn.workers.UvicornWorker app.main:app.
systemctl daemon-reload
systemctl enable --now myapp
journalctl -u myapp -f
RuntimeDirectory=myapp crée /run/myapp avec les permissions nécessaires à chaque démarrage — sinon il n'y a nulle part où placer le socket.
Combien de workers
La formule habituelle est 2 × cores + 1, mais elle se heurte aux limites de mémoire : chaque worker Django utilise 80–200 Mo.
| Offre | Cœurs | Workers |
|---|---|---|
| VDS-1 | 1 | 2 |
| VDS-2 | 2 | 3 |
| VDS-3 | 4 | 5 |
| VDS-4 | 6 | 9 |
Si l'application passe beaucoup de temps à attendre le réseau et la base de données, ajoutez --threads 2 plutôt que d'augmenter le nombre de processus.
Serveur web
location / {
proxy_pass http://unix:/run/myapp/gunicorn.sock;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /static/ { alias /srv/app/static/; }
Servez les fichiers statiques de Django directement avec Nginx après les avoir collectés : collectstatic. Les faire transiter par Python ajoute une charge inutile.
Erreurs courantes
| Symptôme | Cause |
|---|---|
502 et connect() to unix:... failed (13) | Nginx n'a pas les permissions sur le socket — définissez --umask 007 et un groupe partagé |
ModuleNotFoundError | Exécution avec le Python système au lieu de venv |
DisallowedHost dans Django | Le domaine n'est pas ajouté à ALLOWED_HOSTS |
| Les workers sont tués par timeout | Une requête longue — augmentez --timeout, déplacez le travail lourd vers une file d'attente |
| Tout s'est arrêté après une release | Vous avez oublié systemctl restart myapp — le code en mémoire ne se met pas à jour tout seul |
Gardez les secrets dans un
EnvironmentFileavec les permissions600, pas danssettings.py. Un fichier de configuration finira dans le dépôt tôt ou tard.