できない.dev

Django で「Invalid HTTP_HOST header」の 400 が出てアクセスできない(ALLOWED_HOSTS)

DisallowedHost: Invalid HTTP_HOST header は、Host ヘッダが ALLOWED_HOSTS に一致しないときに返る 400 エラーだ。
エラー文の「You may need to add」に出ているホスト名を追加すれば解決する。

公開:

要約

Django はリクエストの Host ヘッダを ALLOWED_HOSTS と照合し、一致しなければ DisallowedHost を送出して 400 Bad Request を返す。
ログには次のような行が出る。

django.core.exceptions.DisallowedHost: Invalid HTTP_HOST header: '192.168.1.10:8000'. You may need to add '192.168.1.10' to ALLOWED_HOSTS.

エラー文の後半「You may need to add」に書かれた名前が、そのまま ALLOWED_HOSTS に足すべき値だ。
照合はポートを含まないので、:8000 は書かなくてよい。

よくある原因

  1. ALLOWED_HOSTS が空のまま別の名前でアクセスしている: DEBUG = True かつ ALLOWED_HOSTS が空のとき、Django は ['.localhost', '127.0.0.1', '[::1]'] だけを許可する。
    LAN の IP アドレスや独自ドメインで開くと、この 3 つに一致せず 400 になる。
  2. DEBUG = False で ALLOWED_HOSTS が空: この組み合わせでは既定の許可リストも無くなる。runserver は起動時点で CommandError: You must set settings.ALLOWED_HOSTS if DEBUG is False. を出して止まる。
  3. プロキシが Host ヘッダを書き換えている: nginx は既定で proxy_set_header Host $proxy_host; を使い、proxy_pass に書いた転送先の名前を Host として送る。
    Docker Compose で proxy_pass http://web:8000; としていると、Django には web:8000 が届く。
  4. 環境変数を文字列のまま代入している: ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS") のように文字列を入れると、現行の Django は The ALLOWED_HOSTS setting must be a list or a tuple. で設定の読み込み自体を拒否する。

解決策

1. エラー文のホスト名を追加する

settings.py の ALLOWED_HOSTS にリストで書く。

# settings.py
ALLOWED_HOSTS = ["localhost", "127.0.0.1", "192.168.1.10"]

サブドメインをまとめて許可したいときは、先頭にドットを付けた ".example.com" と書く。
これで example.com と www.example.com の両方に一致する。"*" はすべてを許可するが、その場合は Host ヘッダの検証を自前で行う責任があると公式ドキュメントが明記しているので、回避策として使わない。

LAN の別端末から開発サーバに接続するなら、待ち受けアドレスも変える。runserver の既定は 127.0.0.1:8000 で、他の端末からは届かない。

python manage.py runserver 0.0.0.0:8000

2. 本番用の設定では公開ドメインを列挙する

DEBUG = False にした環境では、公開するドメインを漏れなく書く。

DEBUG = False
ALLOWED_HOSTS = ["example.com", "www.example.com"]

3. プロキシで元の Host を渡す

nginx では proxy_set_header で、ブラウザが送った Host をそのまま転送する。

location / {
    proxy_pass http://web:8000;
    proxy_set_header Host $host;
}

これで Django に届く Host が公開ドメインになり、ALLOWED_HOSTS にコンテナ名を足す必要がなくなる。

4. 環境変数はリストに変換してから代入する

環境ごとに値を変えるなら、カンマ区切りの文字列を受けて分割する。

import os
 
ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", "localhost").split(",")

DJANGO_ALLOWED_HOSTS=example.com,www.example.com を渡せばリストになる。example.com, www.example.com のように空白を入れると " www.example.com" が一致しなくなるので、カンマの前後に空白を置かない。

この記事は役立ちましたか?