FR
Se connecter Crée ton compte

Erreurs fréquentes lors du déploiement de Django en production et comment les éviter

Bad Request (400), admin sans styles, échec de la vérification CSRF ? Voici 10 erreurs courantes lors du déploiement de Django en production, comment les reconnaître et les corriger, avec une checklist.

Grzegorz Kalmus ·

L'application Django fonctionne parfaitement sur ton ordinateur portable, mais une fois déployée sur le serveur, elle affiche « Bad Request (400) », le panneau d'administration sans styles ou une page blanche avec une erreur 500. Ce n'est pas une question de malchance, mais plutôt quelques pièges récurrents dans lesquels presque tout le monde tombe lors du premier déploiement. L'environnement de développement Django est volontairement indulgent : il affiche les erreurs, sert lui-même les fichiers statiques et ne demande pas de domaine. La production, en revanche, ne pardonne rien de tout cela.

Dans ce guide, nous avons rassemblé les 10 erreurs les plus courantes lors du déploiement de Django en production. Pour chacune, nous expliquons comment elle se manifeste, d'où elle vient et comment la corriger. À la fin, tu trouveras une liste de vérification rapide à passer avant chaque déploiement ainsi que des réponses aux questions les plus fréquentes.

Table des matières

  1. Pourquoi Django fonctionne-t-il en local mais pas sur le serveur ?
  2. Erreur 1 : Est-ce un gros problème si DEBUG = True en production ?
  3. Erreur 2 : Où conserver SECRET_KEY et les mots de passe ?
  4. Erreur 3 : D'où vient le « Bad Request (400) » ?
  5. Erreur 4 : Pourquoi le message « CSRF verification failed » apparaît-il derrière un proxy ?
  6. Erreur 5 : Pourquoi le panneau d’administration n’a-t-il pas de styles ?
  7. Erreur 6 : Peut-on laisser runserver actif ?
  8. Erreur 7 : Que se passe-t-il si tu oublies les migrations ?
  9. Erreur 8 : SQLite suffit-il en production ?
  10. Erreur 9 : Pourquoi le pip install casse-t-il le déploiement ?
  11. Erreur 10 : Comment savoir si l’application a planté ?
  12. Liste de contrôle avant le déploiement
  13. Comment cela se passe-t-il sur Saplo ?
  14. FAQ

Pourquoi Django fonctionne en local mais pas sur le serveur ?

La commande python manage.py runserver lance un serveur de développement qui fait plein de choses en coulisses pour le développeur. Il sert les fichiers CSS et JavaScript, affiche en cas d'erreur une page claire avec la trace de la pile, accepte les requêtes sur localhost et ne se soucie pas du HTTPS.

En production, ce confort disparaît. L'application est derrière un serveur web ou un reverse proxy, fonctionne sous un vrai domaine, le trafic arrive chiffré, et chaque élément qui « fonctionnait tout seul » en local doit être configuré explicitement. La plupart des erreurs décrites ci-dessous viennent justement de cette différence entre ces deux environnements.

Avant d'aller plus loin, retiens une commande. Django intègre un audit des réglages de production :

python manage.py check --deploy

Elle affiche des avertissements sur DEBUG, la clé, les cookies, le HTTPS et les en-têtes de sécurité. Le lancer avant la première mise en production évite la moitié des problèmes listés ici.

Erreur 1 : Est-ce un gros problème de laisser DEBUG = True en production ?

Oui, c'est un gros problème. Avec DEBUG activé, chaque erreur 500 affiche au visiteur une page de diagnostic complète : extraits de code, variables locales, chemins sur le serveur, liste des paramètres et requêtes SQL. Django masque certains noms sensibles, mais pas tous, et la structure même de l’application représente pour un attaquant une carte précieuse. De plus, le mode DEBUG garde en mémoire chaque requête à la base de données, ce qui fait augmenter la mémoire utilisée par le processus au fil du temps.

Comment réparer : configure DEBUG via une variable d’environnement, avec une valeur par défaut sécurisée.

import os
DEBUG = os.environ.get("DJANGO_DEBUG", "0") == "1"

En local, tu définis DJANGO_DEBUG=1, et sur le serveur tu ne définis rien. Garde juste en tête qu’une fois DEBUG désactivé, ALLOWED_HOSTS s’applique et Django cesse de servir les fichiers statiques. C’est pourquoi les erreurs 3 et 5 apparaissent souvent juste après cette correction.

Erreur 2 : Où stocker SECRET_KEY et les mots de passe ?

Pas dans le dépôt. SECRET_KEY signe les sessions, les tokens de réinitialisation de mot de passe et les cookies CSRF. Qui connaît cette clé peut usurper une session utilisateur connectée. La clé générée par startproject se retrouve souvent dans le premier commit et reste dans l'historique git pour toujours, même après sa suppression du fichier.

Il en va de même pour le mot de passe de la base de données, les clés API pour les paiements ou la messagerie.

Comment corriger : lis tous les secrets depuis les variables d'environnement, et ne laisse dans le dépôt que les noms.

SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]

L'absence de variable déclenchera une erreur au démarrage, ce qui est souhaitable : mieux vaut que l'application ne démarre pas que de démarrer avec la clé par défaut. Si la clé a déjà été mise dans un dépôt public, génère une nouvelle clé. Tous les utilisateurs seront déconnectés, mais l'ancienne clé ne sera plus un risque.

Erreur 3 : D'où vient le « Bad Request (400) » ?

Presque toujours d'un ALLOWED_HOSTS vide ou incomplet. Avec DEBUG désactivé, Django rejette toute requête dont l'en-tête Host ne figure pas dans cette liste. Les oublis typiques incluent l'absence de version avec www, l'oubli de la sous-domaine temporaire sous laquelle l'application fonctionnait avant la connexion du domaine, ou une faute de frappe.

Comment réparer : saisis tous les domaines sous lesquels l'application doit répondre.

ALLOWED_HOSTS = ["votreentreprise.fr", "www.votreentreprise.fr"]

Ne te sauve pas en ajoutant ["*"]. Cela désactive la protection contre les attaques ciblant l'en-tête Host, par exemple en insérant un faux domaine dans les liens de réinitialisation de mot de passe.

Erreur 4 : Pourquoi est-ce que « CSRF verification failed » apparaît derrière un proxy ?

En production, Django reçoit rarement les requêtes directement. Il y a devant lui un reverse proxy qui accepte le HTTPS et transmet une requête HTTP simple à l’application. Django voit donc une requête non chiffrée, alors que le navigateur envoie un formulaire depuis l’adresse https://. Depuis la version 4.0, Django compare l’en-tête Origin à la liste des sources de confiance et rejette sans elle les formulaires, y compris la connexion au panneau d’administration.

Une autre variante du même problème : tu actives SECURE_SSL_REDIRECT = True, et la page se retrouve dans une boucle infinie de redirections parce que Django croit toujours que la requête est venue en HTTP.

Comment réparer : indique à Django quel en-tête le proxy utilise pour transmettre l’information HTTPS, puis ajoute les sources de confiance.

SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
CSRF_TRUSTED_ORIGINS = ["https://votreentreprise.fr", "https://www.votreentreprise.fr"]
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

Le réglage SECURE_PROXY_SSL_HEADER est sûr uniquement si le proxy ajoute toujours cet en-tête lui-même et ne laisse pas passer une valeur venant du client.

Erreur 5 : Pourquoi le panneau admin n'a-t-il pas de styles ?

Parce qu'après avoir désactivé DEBUG, Django ne sert plus les fichiers statiques. Le panneau /admin/ fonctionne, mais ressemble à une page des années 90, et les CSS personnalisés ainsi que les images renvoient des erreurs 404. À cela s’ajoute un oubli classique collectstatic : les fichiers sont dans les dossiers des applications, mais personne ne les a rassemblés au même endroit.

Comment réparer ça : configure STATIC_ROOT, collecte les fichiers à chaque déploiement et sers-les via le serveur web ou la bibliothèque WhiteNoise.

STATIC_ROOT = BASE_DIR / "staticfiles"
MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "whitenoise.middleware.WhiteNoiseMiddleware",
    # reszta middleware
]
python manage.py collectstatic --noinput

WhiteNoise ne gère que les fichiers statiques issus du code. Les fichiers téléchargés par les utilisateurs (MEDIA_ROOT, par exemple des photos de produits) doivent être servis séparément, via le serveur web ou un stockage externe. C’est la deuxième source fréquente de souci après la première.

Erreur 6 : Peut-on laisser runserver en marche ?

Non. La Documentation Django précise clairement que le serveur de développement n'a pas passé d'audits de sécurité ni de tests de performance. Il traite les requêtes sans être adapté au trafic, ne gère pas correctement les processus, et en cas de plantage, personne ne le redémarre.

Comment corriger cela : lance l'application via un serveur WSGI, le plus souvent Gunicorn, sous le contrôle d'un gestionnaire de processus qui le relancera après une panne ou un redémarrage de la machine.

gunicorn yourproject.wsgi:application --bind 127.0.0.1:8000 --workers 2

Le nombre de processus workers dépend de la mémoire. Chaque processus Gunicorn est une copie indépendante de l'application en RAM, donc sur un petit serveur avec 1 GB, il vaut mieux commencer avec deux et surveiller la consommation, plutôt que d'en mettre huit et risquer que le système tue des processus.

Erreur 7 : Que se passe-t-il quand tu oublies les migrations ?

La nouvelle version du code fait référence à une colonne qui n'existe pas encore dans la base de données. La page utilisant ce modèle renvoie une erreur 500, et les logs affichent no such column ou relation does not exist. Une variante de cette erreur est un fichier de migration qui n'a pas été ajouté au dépôt car il était en .gitignore ou personne ne l'a ajouté.

Comment réparer : considère les migrations comme une étape systématique de chaque déploiement, et non comme une tâche manuelle.

python manage.py makemigrations --check --dry-run
python manage.py migrate --noinput

La première commande, lancée avant le commit, échoue si des modifications dans les modèles ne sont pas accompagnées d’un fichier de migration. La seconde s’exécute sur le serveur avant le lancement de la nouvelle version. Avant une migration supprimant des colonnes ou des tables, fais une copie de la base.

Erreur 8 : SQLite suffit-il en production ?

Pour une petite application avec quelques utilisateurs et peu d'écritures, SQLite peut fonctionner des années. Les problèmes apparaissent quand il y a de nombreuses écritures simultanées (database is locked) et lors des sauvegardes : le fichier de base copié pendant une écriture peut être corrompu.

Le véritable problème n'est pas tant le choix de SQLite, mais de stocker la base dans un endroit qui disparaît lors du déploiement. Un fichier db.sqlite3 dans un répertoire écrasé à chaque déploiement ou dans un conteneur sans volume persistant signifie qu'à chaque mise à jour, la base recommence à zéro.

Comment réparer : si ton application prévoit de grandir, passe à PostgreSQL, et indique l'adresse de la base dans la variable DATABASE_URL. Quel que soit le moteur de base, assure-toi que les données sont stockées dans un emplacement persistant et qu'elles bénéficient d'une sauvegarde.

Erreur 9 : Pourquoi pip install bloque le déploiement ?

Parce que l’environnement local est différent de celui sur le serveur. Scénarios typiques : un fichier requirements.txt sans version, donc le serveur télécharge une bibliothèque plus récente et incompatible ; un paquet installé manuellement en local, absent du fichier (No module named ...) ; ou encore une autre version de Python. Exemple concret : Django 6.0 nécessite Python 3.12 ou plus récent, donc sur un serveur avec Python 3.11, la commande pip install django sans verrouillage de version échouera ou installera un paquet inutilisable.

Comment réparer: fixe les versions et vérifie quelle version de Python tourne sur le serveur.

pip freeze > requirements.txt
python --version

Si le serveur utilise Python 3.11, dans le fichier des dépendances mets Django>=5.2,<6. La branche 5.2 bénéficie d’un support étendu, ce qui en fait un choix judicieux pour la production.

Erreur 10 : Comment savoir que l'application est tombée ?

Le pire scénario, c'est d'apprendre la panne par ton client. Avec DEBUG désactivé, les erreurs ne sont pas visibles sur le site, donc si elles ne vont ni dans les logs ni par mail, elles disparaissent tout simplement.

Comment réparer :

  • configure LOGGING pour que les erreurs aillent vers la sortie standard ou vers un fichier que tu peux consulter après la panne,
  • définis ADMINS ainsi qu'une configuration mail correcte, pour que Django envoie un mail à chaque erreur 500,
  • ajoute une URL simple de contrôle, par exemple /health/, qui renvoie {"status": "ok"}, et connecte-la à une surveillance de disponibilité.

Cette URL de contrôle sera aussi utile lors des déploiements : le système de déploiement peut vérifier que la nouvelle version est bien lancée avant de lui faire prendre le relais du trafic.

Liste de contrôle avant le déploiement

Avant de lancer une nouvelle version, passe par ces étapes :

Point Comment vérifier
DEBUG désactivé DJANGO_DEBUG n'est pas défini sur le serveur
Secrets en dehors du dépôt git grep SECRET_KEY ne montre pas les valeurs
Domaines dans ALLOWED_HOSTS version avec www et sans, plus un sous-domaine temporaire
HTTPS derrière un proxy SECURE_PROXY_SSL_HEADER et CSRF_TRUSTED_ORIGINS configurés
Fichiers statiques collectstatic dans le processus de déploiement, l'admin a les styles
Serveur d'applications Gunicorn au lieu de runserver
Migrations makemigrations --check passe, migrate dans le processus de déploiement
Base de données et sauvegardes données dans un endroit persistant, sauvegarde fonctionne
Dépendances versions fixées, compatibles avec Python sur le serveur
Logs et monitoring erreurs remontent bien dans les logs, /health/ est opérationnel
Audit Django python manage.py check --deploy sans avertissements

Comment ça se passe sur Saplo ?

Sur Saplo, la plupart de cette liste est déjà configurée avant même que tu écrives la première ligne de code. Quand tu crées une application Django depuis le catalogue, la recette installe Gunicorn et WhiteNoise, configure STATIC_ROOT, ajoute le domaine dans ALLOWED_HOSTS et CSRF_TRUSTED_ORIGINS, intègre SECURE_PROXY_SSL_HEADER, effectue les migrations et collectstatic, et le processus de l'application est supervisé par un gestionnaire de processus qui le relance en cas de panne. Le certificat SSL pour le domaine est inclus dans le prix du plan.

Lors du déploiement de ton propre dépôt, les étapes sont contrôlées par le fichier saplo.yaml. Les migrations sont inscrites comme un hook avant la build, et la collecte des fichiers statiques comme commande de build :

build:
  install: "pip install -r requirements.txt"
  command: "python manage.py collectstatic --noinput"
hooks:
  pre_build: "python manage.py migrate --noinput"

Quelques points à savoir :

  • Python 3.11. Le builder utilise Python 3.11 du système, donc dans requirements.txt utilise Django en branche 5.x (bug 9).
  • Secrets. SECRET_KEY ou le mot de passe de la base se stockent avec la commande saplo env set --secret ou dans le panneau, onglet Variables et secrets. Le panneau, l'API et l'assistant IA n'affichent alors que des étoiles. Le changement prend effet au prochain déploiement ou redémarrage. Plus de détails dans la Documentation des variables et secrets.
  • Un déploiement raté ne met pas le site hors ligne. Si la build ou le contrôle de santé échouent, la nouvelle version prend un statut d'erreur mais l'ancienne continue de tourner. Le contrôle de santé attend une réponse jusqu'à 60 secondes.
  • PostgreSQL s'exécute en conteneur dans le fichier docker-compose.yml de l'application, avec le port exposé uniquement sur 127.0.0.1 et l'adresse dans la variable DATABASE_URL.
  • Sauvegardes. Chaque Box dispose d'une sauvegarde quotidienne chiffrée transférée dans un autre centre de données (7 jours de rétention pour Box S, M et L). À partir de Box L, s'ajoutent des snapshots automatiques restaurables en un clic, et sur tous les plans il est possible de faire un snapshot manuel avant une migration risquée.
  • Logs accessibles avec la commande saplo logs --follow ou via le panneau, dans les détails du déploiement.

Le déploiement depuis GitHub à chaque git push est expliqué dans la documentation Git deploy, et les messages d'erreur fréquents sont listés dans la résolution des problèmes.

Quel plan choisir ? Pour une petite application Django, Box S suffit (1 GB RAM, 27 € par an). Si tu as besoin d'un accès SSH au serveur ou d'une base PostgreSQL dans un conteneur séparé, Box M est un bon départ (2 GB RAM, SSH, 63 € par an). Box L (6 GB RAM, 136 € par an) ajoute les snapshots automatiques sur 7 jours. Le prix de la première année est le même que celui du renouvellement, et la migration d'une application existante d'un autre hébergeur se fait gratuitement. La comparaison complète des plans est disponible dans le tarif.

Si tu débutes avec Django, commence par lire qu'est-ce que Django et à quoi il sert, et trouve le guide d'installation dans le tutoriel comment déployer Django sur Saplo.

FAQ

Comment vérifier si mon application Django est prête pour la production ?

Lance python manage.py check --deploy avec les paramètres de production. La commande affichera les avertissements concernant DEBUG, la clé, HTTPS, les cookies et les en-têtes de sécurité. Ensuite, passe en revue la liste de contrôle de cet article, car l'audit ne vérifie pas les migrations, les sauvegardes ni les fichiers statiques.

Pourquoi la page affiche-t-elle Bad Request (400) après la désactivation de DEBUG ?

Parce qu'à partir de ce moment Django vérifie ALLOWED_HOSTS, et que le domaine par lequel tu accèdes n'est pas dans la liste. Ajoute toutes les variantes de l'adresse, y compris avec www et le sous-domaine temporaire, puis redémarre l'application.

WhiteNoise suffit-il pour les fichiers statiques en production ?

Pour la plupart des petites et moyennes applications, oui. WhiteNoise sert les fichiers avec compression et un temps de stockage long dans la mémoire du navigateur. Cependant, il ne gère pas les fichiers uploadés par les utilisateurs, qui doivent être servis séparément.

De combien de mémoire RAM Django a-t-il besoin en production ?

Une application simple avec deux processus Gunicorn tient dans quelques centaines de mégaoctets, donc un serveur avec 1 Go de RAM suffit pour commencer. Plus de mémoire est nécessaire si la base PostgreSQL est sur le même serveur, avec une file de tâches en arrière-plan ou un plus grand nombre de processus workers.

Puis-je transférer une application Django en fonctionnement vers Saplo ?

Oui. L'application est déployée depuis un dépôt Git ou via le CLI, et nous effectuons gratuitement la migration depuis un autre hébergeur avec tous les plans. Avant la migration, il est conseillé de suivre la liste de contrôle de cet article, car les réglages dépendant de l'ancien serveur, comme les domaines ou les chemins de fichiers, doivent être adaptés au nouveau.

  • #Sécurité
  • #Django
  • #Guide
  • #Python
  • #Déploiement