Fait partie de Hébergement par cadricielHébergement · Django

Django : DisallowedHost et CSRF 403 après le déploiement, ce que Django vérifie.

Les deux erreurs viennent de Django, qui compare la requête à vos paramètres. Bad Request (400) : l’hôte manque dans ALLOWED_HOSTS. Un 403 CSRF sur un formulaire du même site signale souvent que Django croit la requête en HTTP, car le proxy a terminé le TLS. Corrigez la liste d’hôtes, faites confiance à X-Forwarded-Proto et réservez CSRF_TRUSTED_ORIGINS aux autres origines.

Faits vérifiés le

Les lignes d’erreur et ce qu’elles veulent dire

Avec DEBUG = False, le navigateur n’affiche qu’une courte page. La ligne utile est dans le journal de l’appli. Retrouvez-la ici :

Le journal ditCe que Django a vérifiéCorrectif
Invalid HTTP_HOST header: 'votre-projet.canner.app'. You may need to add 'votre-projet.canner.app' to ALLOWED_HOSTS. (le navigateur affiche Bad Request (400)) † †L’en-tête Host par rapport à ALLOWED_HOSTS. Avec DEBUG = False et une liste vide, toutes les requêtes reçoivent un 400Ajoutez l’hôte. '.canner.app' avec un point initial couvre tous les sous-domaines; ajoutez aussi chaque domaine personnalisé
Forbidden (Origin checking failed - https://exemple.ca does not match any trusted origins.): /accounts/login/ † † †L’Origin du navigateur par rapport au schéma et à l’hôte que Django croit servir. Si Django pense que la requête était en http, https://exemple.ca ne correspond pasFormulaire sur le même site : définissez SECURE_PROXY_SSL_HEADER. Formulaire ou fetch() sur une autre origine : ajoutez cette origine à CSRF_TRUSTED_ORIGINS
Referer checking failed - no Referer. †Pour une requête HTTPS sans Origin, Django exige un Referer d’une origine de confianceN’envoyez pas Referrer-Policy: no-referrer sur les pages qui ont des formulaires; same-origin garde la vérification fonctionnelle
CSRF cookie not set. †Le témoin csrftoken n’est pas revenu avec le POSTVérifiez que CSRF_COOKIE_DOMAIN correspond au site et que la page du formulaire contient {% csrf_token %}
Une erreur de vérification système au démarrage au sujet de CSRF_TRUSTED_ORIGINS †Depuis Django 4.0, les entrées doivent avoir un schéma : 'exemple.ca' et '.exemple.ca' ne sont plus validesÉcrivez 'https://exemple.ca' et 'https://*.exemple.ca'

† Les détails sur les concurrents proviennent des pages publiées par chaque fournisseur aux dates indiquées; les prix et forfaits changent, vérifiez auprès du fournisseur avant de décider. `Invalid HTTP_HOST header: 'votre-projet.canner.app'. You may need to add 'votre-projet.canner.app' to ALLOWED_HOSTS.` (le navigateur affiche `Bad Request (400)`) (2026-10-29) · `Invalid HTTP_HOST header: 'votre-projet.canner.app'. You may need to add 'votre-projet.canner.app' to ALLOWED_HOSTS.` (le navigateur affiche `Bad Request (400)`) (2026-10-29) · `Forbidden (Origin checking failed - https://exemple.ca does not match any trusted origins.): /accounts/login/` (2026-10-29) · `Forbidden (Origin checking failed - https://exemple.ca does not match any trusted origins.): /accounts/login/` (2026-10-29) · `Forbidden (Origin checking failed - https://exemple.ca does not match any trusted origins.): /accounts/login/` (2026-10-29) · `Referer checking failed - no Referer.` (2026-10-29) · `CSRF cookie not set.` (2026-10-29) · Une erreur de vérification système au démarrage au sujet de `CSRF_TRUSTED_ORIGINS` (2026-10-29)

Ce que le proxy de Canner transmet à Django

Sur Canner, Caddy termine le HTTPS et transmet chaque requête à votre appli sur 127.0.0.1. Il laisse l’en-tête Host intact : Django voit l’hôte saisi par le visiteur, votre-projet.canner.app ou votre domaine personnalisé. Il définit aussi X-Forwarded-Proto: https, X-Forwarded-For et X-Forwarded-Host, et il écarte les valeurs X-Forwarded-* qu’un client tenterait d’envoyer lui-même.

Django est lancé avec gunicorn --bind 127.0.0.1:$PORT <projet>.wsgi:application (voir la documentation Python; inscrivez gunicorn dans requirements.txt). Par défaut, gunicorn fait confiance à X-Forwarded-Proto venant de 127.0.0.1 et de ::1 et marque ces requêtes https : request.is_secure() est donc déjà vrai sur un déploiement standard. C’est pourquoi bien des applis Django sur Canner ne voient jamais l’erreur CSRF.

Définissez quand même SECURE_PROXY_SSL_HEADER. Quand Canner ne trouve pas de paquet contenant wsgi.py à côté de manage.py, il se rabat sur manage.py runserver, qui ne lit pas l’en-tête. Le paramètre garde aussi votre appli correcte si vous changez un jour de serveur ou de forwarded_allow_ips.

Des paramètres qui marchent sur canner.app et sur un domaine personnalisé

  • `.canner.app` dans `ALLOWED_HOSTS` couvre les aperçus. L’aperçu d’une demande de tirage est à branche--projet.canner.app, que le point initial couvre. Caddy n’achemine vers votre appli que les noms d’hôte de votre projet : le joker ne laisse pas entrer le trafic d’un autre projet.
  • `www` n’a pas besoin d’entrée quand Servir www est activé. Canner répond à www.exemple.ca par une redirection 301 vers exemple.ca avant que Django la voie (domaines personnalisés).
  • **Ne mettez jamais https://*.canner.app dans CSRF_TRUSTED_ORIGINS.** Les applis d’autres clients vivent sur ce domaine, et le paramètre dit à Django d’accepter leurs POST. Listez des origines exactes. Pour un aperçu, sa propre adresse est dans CANNER_URL.
  • Les formulaires de même origine n’ont besoin d’aucun `CSRF_TRUSTED_ORIGINS` dès que Django voit le HTTPS. La liste sert à un frontal sur un domaine qui poste vers Django sur un autre.
# settings.py
import os

DEBUG = False

# The Host header Django sees is the one the visitor typed.
ALLOWED_HOSTS = [".canner.app", "example.ca"]

# Tell Django the request was HTTPS when the proxy says so.
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

# Only for forms or fetch() calls that POST from ANOTHER origin.
# Full origins, scheme included: "https://app.example.ca,https://admin.example.ca"
CSRF_TRUSTED_ORIGINS = [
    o for o in os.environ.get("CSRF_TRUSTED_ORIGINS", "").split(",") if o
]

Pourquoi le déploiement a réussi alors que le site répond 400

La vérification d’état de Canner appelle votre appli sur http://localhost:<port>/ (le chemin est un paramètre du projet) et considère tout statut inférieur à 500 comme vivant. Django répond 400 à cette sonde quand localhost n’est pas permis : le déploiement réussit, puis chaque visiteur reçoit Bad Request (400). N’ajoutez pas localhost pour faire taire la sonde; elle fait son travail, et le correctif, ce sont les vrais noms d’hôte.

Le journal de compilation vous prévient déjà : [canner] ⚠ django checks the Host header — if every request fails (Django: 400 Bad Request / DisallowedHost; Rails, Sinatra: "Blocked hosts"), allow this app's host and your custom domain:, suivi d’une ligne ALLOWED_HOSTS suggérée avec l’adresse <projet>.canner.app de votre appli.

Vérifiez avant et après le correctif

Lancez ces commandes depuis votre poste et dans le projet. check --deploy signale d’un coup les paramètres de témoins sécurisés et de HTTPS manquants. L’interface en ligne de commande diffuse le journal de l’appli : vous voyez la ligne Invalid HTTP_HOST ou Forbidden (…) exacte au moment où elle arrive. Définissez CSRF_TRUSTED_ORIGINS avec canner env set ou dans les variables d’environnement du tableau de bord, puis redéployez.

# What does Django answer for each host?
curl -sI https://your-project.canner.app/ | head -1
curl -sI https://example.ca/ | head -1

# Django's own pre-flight list for production settings
python manage.py check --deploy

# Watch the app's log while you reproduce the error
canner logs --follow

Les mêmes erreurs chez d’autres hébergeurs

La cause est la même partout où un proxy ou un répartiteur de charge termine le TLS : Django reçoit du HTTP simple et un nom d’hôte dont on ne lui a pas parlé. Ce qui change, c’est si le serveur devant Django définit déjà le schéma (gunicorn le fait pour les connexions venant de l’adresse de bouclage) et si la plateforme réécrit Host. D’autres notes par cadriciel sont dans hébergement par cadriciel et sur la page hébergement Django.

Questions

Pourquoi ai-je Bad Request (400) seulement en production?

Avec DEBUG = True et un ALLOWED_HOSTS vide, Django accepte localhost, 127.0.0.1 et [::1]. Avec DEBUG = False, il n’accepte que les hôtes listés : un nom d’hôte déployé absent de la liste reçoit un 400.

ALLOWED_HOSTS = ['*'] est-il sûr?

Il désactive la vérification d’hôte de Django. Tout code qui construit des URL absolues à partir de request.get_host(), comme les liens de réinitialisation de mot de passe, utilisera alors n’importe quel Host reçu. Listez plutôt vos vrais hôtes.

Faut-il CSRF_TRUSTED_ORIGINS pour l’administration de Django?

Non, si l’administration est sur le même hôte et que Django sait que la requête était en HTTPS. Le 403 à la connexion de l’administration veut presque toujours dire que SECURE_PROXY_SSL_HEADER manque ou que le serveur ne définit pas le schéma.

Pourquoi CSRF_TRUSTED_ORIGINS doit-il inclure https://?

Depuis Django 4.0, le paramètre contient des origines, pas des noms d’hôte, et une origine inclut le schéma. 'https://*.exemple.ca' remplace l’ancienne forme '.exemple.ca'.

Django chez un hébergeur qui vous dit le nom d’hôte.

Canner signale ALLOWED_HOSTS dans le journal de compilation et affiche les lignes d’erreur de l’appli à côté de chaque déploiement.