Django en prod : 7 erreurs qui m'ont coûté des nuits
Lecons & Erreurs

Django en prod : 7 erreurs qui m'ont coûté des nuits

Trois pannes en un mois sur une application que je croyais bien déployée : DEBUG laissé à True, runserver en production, requêtes N+1, aucun monitoring. Les 7 erreurs Django qui m'ont coûté des nuits blanches, avec la configuration exacte qui les règle.

2 février 2026
6 min de lecture
Par Tokam Darius

3h du matin, mon téléphone buzze. "Le site est down, les utilisateurs ne peuvent plus s'inscrire." C'était la troisième fois ce mois-là que Housing AI plantait en production. J'étais pourtant convaincu d'avoir tout bien fait avec Django. Spoiler : j'avais tout faux.

Après 2 ans et 15 APIs en production, j'ai enfin compris pourquoi mes applications Django tombaient comme des mouches. Voici les 7 erreurs qui m'ont coûté des nuits blanches, et surtout comment les éviter.

L'erreur qui tue : DEBUG=True en production

Ma première application déployée a planté spectaculairement le jour du lancement. J'avais laissé DEBUG=True dans mes settings de production. Résultat ? Pages d'erreur avec stack traces complets exposées aux utilisateurs, performances dégradées, et pire : toutes mes variables d'environnement affichées en clair.

Ce que j'aurais dû faire dès le début :

  • Séparer les settings par environnement (settings/dev.py, settings/prod.py)
  • Utiliser des variables d'environnement pour tous les secrets
  • DEBUG = False en production, TOUJOURS
  • Configurer ALLOWED_HOSTS correctement
python
# settings/prod.py
import os
from .base import *

DEBUG = False
ALLOWED_HOSTS = [os.getenv('DOMAIN'), 'www.monsite.com']
SECRET_KEY = os.getenv('SECRET_KEY')

# Logs détaillés sans DEBUG
LOGGING = {
    'version': 1,
    'handlers': {
        'file': {
            'class': 'logging.FileHandler',
            'filename': 'django.log',
        },
    },
    'root': {
        'handlers': ['file'],
    },
}

Cette erreur m'a appris une règle d'or : sécurité avant tout. Un secret exposé peut détruire ton business en une nuit.

Salle de serveurs avec voyants d'alerte rouges et messages d'erreur à l'écran

Servir avec runserver = suicide professionnel

J'ai honte de l'avouer, mais j'ai déployé ma première app avec python manage.py runserver sur un VPS. L'application s'écroulait dès 10 utilisateurs connectés. Le serveur de développement Django n'est PAS fait pour la production.

Ma stack actuelle (qui tient la charge) :

  1. Gunicorn comme serveur WSGI
  2. Nginx comme proxy inverse et pour les fichiers statiques
  3. Systemd pour gérer les processus

Configuration Gunicorn qui marche :

bash
gunicorn --workers=4 --worker-class=gthread --threads=2 --worker-connections=1000 myproject.wsgi:application

Nginx gère les fichiers statiques, Gunicorn s'occupe du Python. Cette séparation des responsabilités a divisé mes temps de réponse par 10.

D'ailleurs, j'explique en détail ma stack technique complète dans mon article sur comment j'ai construit Housing AI si tu veux voir l'architecture complète.

L'ORM Django : ami ou ennemi ?

L'ORM Django, c'est magique... jusqu'à ce que ça devienne ton pire cauchemar. J'ai eu des pages qui prenaient 15 secondes à charger à cause du problème N+1. Chaque article affiché déclenchait une requête pour récupérer l'auteur. 1000 articles = 1000 requêtes SQL.

Mes optimisations qui font vraiment la différence :

select_related() pour les ForeignKey :

python
# Lent - N+1 queries
articles = Article.objects.all()
for article in articles:
    print(article.author.name)  # Nouvelle requête à chaque fois

# Rapide - 1 seule requête
articles = Article.objects.select_related('author')

prefetch_related() pour les ManyToMany :

python
# Pour récupérer articles + tous leurs tags d'un coup
articles = Article.objects.prefetch_related('tags')

Index sur les colonnes les plus requêtées :

python
class Article(models.Model):
    title = models.CharField(max_length=200, db_index=True)
    created_at = models.DateTimeField(db_index=True)

Depuis que j'applique ces best practices, mes temps de réponse sont passés de 3 secondes à 200 ms. L'ORM Django est puissant, mais il faut savoir le dompter.

Serveurs de base de données avec câbles et voyants clignotants

Fichiers statiques : le piège invisible

Mes CSS ne se chargeaient jamais. Mes images disparaissaient après chaque déploiement. Les fichiers statiques avec Django, c'est un enfer si tu ne comprends pas la logique.

Ma configuration bulletproof :

python
# settings/prod.py
STATIC_URL = '/static/'
STATIC_ROOT = '/var/www/monsite/static/'
MEDIA_URL = '/media/'
MEDIA_ROOT = '/var/www/monsite/media/'

# Pour servir depuis un CDN
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'

Process de déploiement :

  1. python manage.py collectstatic --noinput
  2. Nginx sert directement /static/ et /media/
  3. Django ne touche jamais aux fichiers statiques

J'utilise WhiteNoise pour les petites apps et AWS S3 avec CloudFront pour les gros projets comme Housing AI. Ça change la vie.

Monitoring : voir les problèmes avant tes users

"Pourquoi personne ne m'a dit que le site plantait ?" Cette phrase, je l'ai dite trop souvent. J'apprenais les crashes par les utilisateurs qui me contactaient sur Twitter. Pas professionnel du tout.

Mon setup de monitoring actuel :

Sentry pour les erreurs :

python
# settings/prod.py
import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration

sentry_sdk.init(
    dsn="TON_DSN_SENTRY",
    integrations=[DjangoIntegration()],
    traces_sample_rate=0.1,
)

Logs structurés :

python
import logging

logger = logging.getLogger(__name__)

def ma_vue(request):
    try:
        # ton code
        pass
    except Exception as e:
        logger.error(f"Erreur dans ma_vue: {e}", extra={
            'user_id': request.user.id,
            'path': request.path,
        })

Maintenant, je sais en temps réel si quelque chose déconne. Sentry m'envoie un Slack, je peux debugger avec le contexte complet. Ça change tout.

Plusieurs écrans affichant des tableaux de bord de surveillance système en temps réel

Cache et performance : Redis to the rescue

Housing AI scrapait des milliers d'annonces de logement. Chaque page d'accueil déclenchait des calculs lourds. 5 secondes de chargement, c'était normal. Jusqu'à ce que je découvre Redis.

Ma stratégie de cache en 3 niveaux :

1. Cache de vues :

python
from django.views.decorators.cache import cache_page

@cache_page(60 * 15)  # 15 minutes
def liste_logements(request):
    # Vue mise en cache
    pass

2. Cache de queries :

python
from django.core.cache import cache

def get_logements_populaires():
    key = 'logements_populaires'
    result = cache.get(key)
    if not result:
        result = Logement.objects.filter(populaire=True)[:10]
        cache.set(key, result, 60 * 30)  # 30 minutes
    return result

3. Cache de templates :

django
{% load cache %}
{% cache 500 sidebar request.user.id %}
    <!-- Contenu lourd -->
{% endcache %}

Résultat : pages d'accueil passées de 5 secondes à 300 ms. Redis et Django, c'est magique quand c'est bien configuré.

Comme je le mentionnais dans mon article sur l'authentification JWT, la performance et la simplicité vont de pair. Pas besoin de sur-complexifier.

Base de données : migrations et sauvegardes

J'ai perdu une base de données complète une fois. Une migration foireuse, pas de backup, 3 mois de données utilisateurs parties en fumée. Depuis, je suis parano sur la sécurité des données.

Mon process de migrations bulletproof :

1. Toujours tester les migrations sur une copie de prod :

bash
# Dump de la prod
pg_dump production_db > backup_before_migration.sql

# Test sur environnement de staging identique
python manage.py migrate --plan
python manage.py migrate

2. Migrations réversibles :

python
# Éviter les migrations destructives
def forwards():
    # Supprimer une colonne directement

# Migrations en étapes
# Étape 1: Ajouter nouvelle colonne
# Étape 2: Migrer les données
# Étape 3: Supprimer ancienne colonne

3. Backups automatiques :

bash
# Cron job quotidien
0 2 * * * pg_dump monsite_prod | gzip > /backups/db_$(date +\%Y\%m\%d).sql.gz

Une donnée perdue ne revient jamais. Mieux vaut être parano que de pleurer après.

Ma checklist de déploiement Django

Après tous ces fails, j'ai créé une checklist que je suis religieusement pour chaque déploiement Django en production. Ça m'a sauvé la peau plus d'une fois :

Avant le déploiement :

  • DEBUG = False
  • Variables d'environnement pour tous les secrets
  • ALLOWED_HOSTS configuré
  • Tests qui passent
  • Backup de la base de données

Configuration serveur :

  • Gunicorn et Nginx
  • Certificat SSL (Let's Encrypt)
  • Firewall configuré
  • Logs rotatifs

Performance :

  • collectstatic exécuté
  • Redis configuré
  • CDN pour les assets
  • Compression gzip activée

Monitoring :

  • Sentry pour les erreurs
  • Uptime monitoring
  • Alertes Slack configurées

Cette liste paraît longue, mais elle se fait en 20 minutes quand on a l'habitude. Et elle évite 90% des problèmes classiques.


Chaque erreur de cette liste m'a coûté des heures de debug et des nuits sans sommeil. Mais elles m'ont aussi appris l'importance de ne pas prendre de raccourcis en production.

Django est un framework incroyable, mais il faut respecter ses règles. DEBUG=False, serveur WSGI approprié, monitoring actif, et backups réguliers. Ces quatre piliers peuvent sauver ton projet.

La prochaine fois que tu déploies du Django, garde cette liste sous les yeux. Tes futures insomnies te remercieront.

Tags

API
Django
Erreurs
Python
React

A lire aussi

T

Tokam Darius

Développeur web à Jonquière, au Saguenay. Je conçois et je code des applications web sur mesure en Django, React et TypeScript.