1.

Django「Invalid HTTP_HOST header / ALLOWED_HOSTS に追加」の原因と対処

編集
この記事の要点
  • Django で DEBUG=False 時に発生するセキュリティ機能(Host ヘッダ攻撃の防御)
  • settings.pyALLOWED_HOSTS使用するすべてのドメインを列挙する
  • IP 直アクセスやヘルスチェックには 127.0.0.1, localhost, ALB の内部 IP も必要
  • ワイルドカード *非推奨(攻撃を完全許容)。.example.com のようなサブドメインワイルドカードは OK
  • リバースプロキシ経由なら USE_X_FORWARDED_HOST = True + SECURE_PROXY_SSL_HEADER も検討

エラーの全文

DisallowedHost at /
Invalid HTTP_HOST header: 'example.com'. You may need to add 'example.com' to ALLOWED_HOSTS.

Request Method: GET
Request URL:    http://example.com/
Django Version: 4.2.7
Exception Type: DisallowedHost
Exception Value:
    Invalid HTTP_HOST header: 'example.com'. You may need to add 'example.com' to ALLOWED_HOSTS.

同じく以下も同根です:

Invalid HTTP_HOST header: '192.168.1.10'. You may need to add '192.168.1.10' to ALLOWED_HOSTS.
Invalid HTTP_HOST header: 'localhost:8000'. ...

なぜこのエラーが出るのか

Django は DEBUG=False 時、Host ヘッダ攻撃(HTTP_HOST Header Injection)を防ぐためにリクエストの Host: ヘッダ値が ALLOWED_HOSTS リストに含まれているか検証します。含まれていないと DisallowedHost 例外を投げ、本記事のエラーになります。

攻撃の典型例:

GET / HTTP/1.1
Host: evil.com           ← 偽の Host ヘッダ
X-Forwarded-Host: evil.com

# パスワード再設定メールに含まれる URL が
# https://evil.com/reset?token=... になりトークンを盗まれる

これを防ぐため、Django は信頼するホスト名を明示的に許可リストに入れさせます。

解決策: settings.py の ALLOWED_HOSTS に追加

# settings.py
ALLOWED_HOSTS = [
    'example.com',
    'www.example.com',
    'api.example.com',
    'localhost',
    '127.0.0.1',
]

サブドメインワイルドカード(.example.com)も使えます:

# *.example.com を全部許可
ALLOWED_HOSTS = ['.example.com']
# → example.com / www.example.com / api.example.com 全部 OK

# 複数ワイルドカード
ALLOWED_HOSTS = ['.example.com', '.example.jp']

環境変数から読み込むパターン(推奨)

本番・ステージング・ローカルで値を変えたい場合は環境変数経由が便利:

# settings.py
import os

ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '').split(',')
ALLOWED_HOSTS = [h.strip() for h in ALLOWED_HOSTS if h.strip()]

# python-decouple 利用版
from decouple import config, Csv
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='', cast=Csv())

# .env
# ALLOWED_HOSTS=example.com,www.example.com,api.example.com

DEBUG 別の挙動

DEBUGALLOWED_HOSTS=[] のとき挙動
Truelocalhost, 127.0.0.1, [::1] のみ自動許可
False全リクエスト拒否(このエラー発生)
False['*']すべて許可(非推奨
False['example.com']example.com のみ許可

ロードバランサ / ヘルスチェック対応

AWS ELB / ALB / GCP Load Balancer はバックエンド EC2 / GCE インスタンスの内部 IP で直アクセスしてヘルスチェックします。そのため EC2 メタデータから IP を取得して許可します:

# settings.py
import requests

ALLOWED_HOSTS = ['example.com', 'www.example.com']

# EC2 のローカル IP を ALLOWED_HOSTS に加える
try:
    EC2_PRIVATE_IP = requests.get(
        'http://169.254.169.254/latest/meta-data/local-ipv4',
        timeout=0.1,
    ).text
    ALLOWED_HOSTS.append(EC2_PRIVATE_IP)
except requests.exceptions.RequestException:
    pass

または ALB のヘルスチェックを Host ヘッダ付きで送るように設定すれば不要です(ALB のターゲットグループで Host ヘッダを指定)。

リバースプロキシ経由の X-Forwarded-Host

CloudFront / ALB / Nginx の前段経由でアクセスする場合、Host ヘッダが書き換えられている可能性があります:

# settings.py
# Host ヘッダ ではなく X-Forwarded-Host を見る
USE_X_FORWARDED_HOST = True

# HTTPS 判定も X-Forwarded-Proto を信用する
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

# CSRF 起源も明示
CSRF_TRUSTED_ORIGINS = [
    'https://example.com',
    'https://www.example.com',
]

注意: USE_X_FORWARDED_HOST = True を有効化するなら必ず信頼できるリバースプロキシ経由であること。直接公開すると攻撃者が任意の値を送れます。

Nginx 側で Host ヘッダを正しく送る

server {
    listen 80;
    server_name example.com www.example.com;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;                # ← 重要
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $host;
    }
}

proxy_set_header Host $host; が無いと Host: 127.0.0.1:8000 として Django に届きエラーになります。

* (ワイルドカード)を使うべきでない理由

# ❌ 非推奨(攻撃に無防備)
ALLOWED_HOSTS = ['*']

# 攻撃シナリオ:
# 1. 攻撃者が Host: evil.com で API を叩く
# 2. Django が「パスワード再設定 URL」を生成するとき Host を信用
#    https://evil.com/reset?token=xxx という URL がユーザにメール送信
# 3. ユーザがクリック → evil.com にトークンを送信

本当にワイルドカードが必要なケース(社内サブドメインが動的)でも、.example.com(先頭ドット)を使ってください。

確認コマンド

# Django shell で現在の設定確認
python manage.py shell
>>> from django.conf import settings
>>> settings.ALLOWED_HOSTS
>>> settings.DEBUG

# 実際のリクエストヘッダ確認
curl -I -H "Host: example.com" http://127.0.0.1:8000/
curl -I http://example.com/

# Nginx の proxy_set_header が効いているか
sudo nginx -T | grep -i "proxy_set_header"

よくあるトラブル

症状原因対処
本番だけ DisallowedHost本番の ALLOWED_HOSTS 未設定環境変数 / 本番 settings に追加
www あり/無しのどちらかで死ぬ片方しか登録していない両方追加 or .example.com
ELB ヘルスチェックで 400内部 IP が ALLOWED_HOSTS に無いEC2 メタデータから自動登録
ローカルで動かないlocalhost / 127.0.0.1 未登録両方追加
Host が 127.0.0.1:8000 で届くNginx の proxy_set_header Host 無しNginx 設定追加

FAQ

Q: DEBUG=False のときだけエラーが出る
A: 仕様通りです。DEBUG=True 時はチェック自体がスキップされます。本番デプロイ時に必ず ALLOWED_HOSTS を設定してください。

Q: ポート番号も含めるべきか
A: 不要です。ALLOWED_HOSTS = ['example.com'] のままで example.com:8000 も OK。Host ヘッダのポート部分は無視されます。

Q: テスト時に毎回追加するのが面倒
A: pytest.iniconftest.pysettings.ALLOWED_HOSTS = ['*'] をテスト時のみ強制するのが定番です(本番には影響しない)。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. Invalid HTTP_HOST header: '...'. You may need to add '...' to ALLOWED_HOSTS
  2. CommandError: You must set settings.ALLOWED_HOSTS if DEBUG is False.
  3. django.utils.datastructures.MultiValueDictKeyError
  4. Forbidden (403) CSRF verification failed. Request aborted.
  5. ModuleNotFoundError: No module named 'MySQLdb'
  6. WARNINGS: ?: (mysql.W002) MySQL Strict Mode is not set for database connection
  7. Unknown column 'table_name.id' in 'field list'
  8. RuntimeError: Model class ~ doesn't declare an explicit app_label and isn't in an application in INSTALLED_APPS.
  9. get() returned more than one MynumberRegist -- it returned 2!
  10. django.db.utils.OperationalError: (2006, "Can't connect to MySQL server")
  11. 'include' is not defined