Djangoデプロイ時の注意点と解決策まとめ【本番環境で失敗しないための完全ガイド】

テックジム東京本校では、情報科目の受験対策指導やAI駆動開発コースもご用意しております。

目次

この記事でわかること

  • Djangoを本番環境へデプロイするときに必ずぶつかる落とし穴
  • エラーの原因と、すぐに使える具体的な解決策
  • セキュリティ・パフォーマンス・運用の観点での必須チェックリスト

1. デプロイ前の基本確認事項

本番環境へデプロイする前に、以下のコマンドで設定の問題を自動チェックできます。

python manage.py check --deploy

このコマンドはDjangoが用意しているシステムチェックフレームワークを使い、セキュリティ上の問題や設定の不備を一覧表示してくれます。警告(WARNINGS)をゼロにすることを目標に設定を見直しましょう。

よくある出力例と対応

チェック項目 対処方法
WARNINGS: security.W004 SECURE_HSTS_SECONDS を設定する
WARNINGS: security.W008 SECURE_SSL_REDIRECT = True にする
WARNINGS: security.W012 SESSION_COOKIE_SECURE = True にする
ERRORS: ?: (urls.E007) URLパターンの重複を修正する

2. DEBUG設定とSECRET_KEYの管理

問題:本番環境でDEBUG=Trueのままにしてしまう

DEBUG=True のままデプロイすると、エラーページにソースコード・環境変数・設定情報がすべて表示されます。これは重大なセキュリティリスクです。

# ❌ 危険:settings.py に直書き
DEBUG = True
SECRET_KEY = 'django-insecure-xxxxxx'

解決策:環境変数で管理する

# ✅ 正しい方法:settings.py
import os
from decouple import config  # python-decouple を使う場合

DEBUG = config('DEBUG', default=False, cast=bool)
SECRET_KEY = config('SECRET_KEY')
# .env ファイル(Gitに含めない!)
DEBUG=False
SECRET_KEY=your-very-long-random-secret-key-here

ポイント: SECRET_KEY は最低50文字以上のランダム文字列を使用してください。 生成コマンド:python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"


3. 静的ファイル(Static Files)の配信問題

問題:CSSや画像が本番環境で表示されない

Djangoの開発サーバー(runserver)は静的ファイルを自動配信しますが、本番環境では自分で配信設定が必要です。

解決策:collectstaticを実行する

# settings.py
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')  # collectstatic の出力先

STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static'),  # 開発時の静的ファイル置き場
]
# デプロイ時に必ず実行
python manage.py collectstatic --noinput

Whitenoise を使って Django 単体で配信する方法

小〜中規模のアプリならWhitenoiseが最もシンプルです。

pip install whitenoise
# settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware',  # SecurityMiddlewareの直後に追加
    ...
]

# 圧縮・キャッシュを有効化(本番推奨)
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'

4. データベース接続エラーと移行(migrate)の注意点

問題1:本番DBへの接続エラー

django.db.utils.OperationalError: could not connect to server
# settings.py(PostgreSQL の例)
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': os.environ.get('DB_NAME'),
        'USER': os.environ.get('DB_USER'),
        'PASSWORD': os.environ.get('DB_PASSWORD'),
        'HOST': os.environ.get('DB_HOST', 'localhost'),
        'PORT': os.environ.get('DB_PORT', '5432'),
    }
}

問題2:migrateを忘れてテーブルが存在しない

django.db.utils.ProgrammingError: relation "app_model" does not exist

解決策: デプロイスクリプトに migrate を組み込む

#!/bin/bash
# deploy.sh
python manage.py migrate --noinput
python manage.py collectstatic --noinput
gunicorn myproject.wsgi:application

問題3:本番DBに対してmigration競合が発生する

複数のサーバーが同時に migrate を実行すると競合します。

解決策: マイグレーションはデプロイ前に1回だけ実行する仕組みにする(例:CI/CDパイプラインで制御)


5. ALLOWED_HOSTSの設定ミス

問題:400 Bad Request / DisallowedHost エラー

Invalid HTTP_HOST header: 'yourdomain.com'. You may need to add 'yourdomain.com' to ALLOWED_HOSTS.

解決策

# settings.py
ALLOWED_HOSTS = [
    'yourdomain.com',
    'www.yourdomain.com',
    '203.0.113.10',  # サーバーのIPアドレス(必要な場合)
]

環境変数で管理する場合:

ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '').split(',')
# .env
ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com

注意: ALLOWED_HOSTS = ['*'] はセキュリティリスクになるため、本番環境では絶対に使わないでください。


6. WSGIサーバー(Gunicorn / uWSGI)の設定

Djangoの開発サーバーを本番で使ってはいけない理由

python manage.py runserver はシングルスレッドかつデバッグ用途のサーバーです。本番環境では Gunicorn または uWSGI を使います。

Gunicornの基本設定

pip install gunicorn
# 基本起動
gunicorn myproject.wsgi:application --bind 0.0.0.0:8000

# 推奨オプション付き
gunicorn myproject.wsgi:application \
  --bind 0.0.0.0:8000 \
  --workers 4 \           # CPU数 × 2 + 1 が目安
  --timeout 120 \
  --access-logfile /var/log/gunicorn/access.log \
  --error-logfile /var/log/gunicorn/error.log

gunicorn.conf.py で管理する(推奨)

# gunicorn.conf.py
bind = "0.0.0.0:8000"
workers = 4
worker_class = "gthread"
threads = 2
timeout = 120
accesslog = "/var/log/gunicorn/access.log"
errorlog = "/var/log/gunicorn/error.log"
loglevel = "info"
gunicorn -c gunicorn.conf.py myproject.wsgi:application

systemdで自動起動・自動再起動させる

# /etc/systemd/system/gunicorn.service
[Unit]
Description=Gunicorn Django Application
After=network.target

[Service]
User=www-data
Group=www-data
WorkingDirectory=/var/www/myproject
EnvironmentFile=/var/www/myproject/.env
ExecStart=/var/www/myproject/venv/bin/gunicorn \
    -c gunicorn.conf.py \
    myproject.wsgi:application
Restart=always

[Install]
WantedBy=multi-user.target
sudo systemctl enable gunicorn
sudo systemctl start gunicorn

7. Nginxとのリバースプロキシ設定

基本的なNginx設定

# /etc/nginx/sites-available/myproject
server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;

    # 静的ファイルはNginxが直接配信(高速)
    location /static/ {
        alias /var/www/myproject/staticfiles/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    # メディアファイル
    location /media/ {
        alias /var/www/myproject/media/;
    }

    # Djangoアプリへのプロキシ
    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_connect_timeout 60s;
        proxy_read_timeout 120s;
    }
}

問題:X-Forwarded-Proto を Django が認識しない

NginxのHTTPSをDjangoが認識できず、リダイレクトループが発生する場合があります。

# settings.py
USE_X_FORWARDED_HOST = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

8. メディアファイルのアップロード・配信問題

問題:アップロードしたファイルが表示されない

# settings.py
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# urls.py(開発環境のみ)
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    ...
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

本番環境では urls.pystatic() 設定は不要DEBUG=False のとき自動的に無効化されます)。Nginxで配信するのが基本です。

クラウドストレージ(S3等)を使う場合

pip install django-storages boto3
# settings.py
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
STATICFILES_STORAGE = 'storages.backends.s3boto3.S3StaticStorage'

AWS_ACCESS_KEY_ID = os.environ.get('AWS_ACCESS_KEY_ID')
AWS_SECRET_ACCESS_KEY = os.environ.get('AWS_SECRET_ACCESS_KEY')
AWS_STORAGE_BUCKET_NAME = os.environ.get('AWS_STORAGE_BUCKET_NAME')
AWS_S3_REGION_NAME = 'ap-northeast-1'  # 東京リージョン
AWS_S3_CUSTOM_DOMAIN = f'{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com'

9. 環境変数・.envファイルの管理

.gitignoreに必ず追加する

# .gitignore
.env
*.env
.env.local
.env.production

python-decoupleを使った管理(推奨)

pip install python-decouple
# settings.py
from decouple import config, Csv

SECRET_KEY = config('SECRET_KEY')
DEBUG = config('DEBUG', default=False, cast=bool)
ALLOWED_HOSTS = config('ALLOWED_HOSTS', cast=Csv())
DATABASE_URL = config('DATABASE_URL')

本番サーバーへの.envの渡し方

方法 適用場面
サーバーの /etc/environment に記載 VPS・専用サーバー
systemdの EnvironmentFile に指定 systemd管理のサービス
Docker の --env-file オプション Dockerコンテナ
Heroku Config Vars / AWS Parameter Store クラウドPaaS

10. セキュリティ設定チェックリスト

# settings.py(本番環境の推奨設定)

# HTTPS関連
SECURE_SSL_REDIRECT = True                    # HTTPをHTTPSにリダイレクト
SECURE_HSTS_SECONDS = 31536000               # HSTSを1年間有効化
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True

# Cookie
SESSION_COOKIE_SECURE = True                  # CookieをHTTPS専用に
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
CSRF_COOKIE_HTTPONLY = True

# クリックジャッキング対策
X_FRAME_OPTIONS = 'DENY'

# コンテンツタイプスニッフィング対策
SECURE_CONTENT_TYPE_NOSNIFF = True

# XSSフィルター
SECURE_BROWSER_XSS_FILTER = True

11. ログ設定とエラー監視

本番環境向けのログ設定

# settings.py
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
            'style': '{',
        },
    },
    'handlers': {
        'file': {
            'level': 'ERROR',
            'class': 'logging.FileHandler',
            'filename': '/var/log/django/error.log',
            'formatter': 'verbose',
        },
        'console': {
            'class': 'logging.StreamHandler',
            'formatter': 'verbose',
        },
    },
    'loggers': {
        'django': {
            'handlers': ['file', 'console'],
            'level': 'ERROR',
            'propagate': True,
        },
    },
}

メール通知でエラーを受け取る(ADMINS設定)

# settings.py
ADMINS = [('Your Name', 'admin@yourdomain.com')]

EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD')
SERVER_EMAIL = 'server@yourdomain.com'

ADMINS に登録したメールアドレスに、500エラー発生時の詳細なトレースバックが届くようになります。


12. よくあるエラーと解決策 早見表

エラーメッセージ 原因 解決策
DisallowedHost ALLOWED_HOSTS にドメインがない ドメインを ALLOWED_HOSTS に追加
CSRF verification failed CSRF トークンがない / Cookie設定ミス フォームに {% csrf_token %} を追加、CSRF_COOKIE_SECURE を確認
Static files not found (404) collectstatic 未実行 or Nginx設定ミス collectstatic 実行後、Nginxのパスを確認
OperationalError: no such table migrate 未実行 python manage.py migrate を実行
500 Internal Server Error アプリのバグ or 設定ミス ログファイル(/var/log/django/error.log)を確認
[Errno 111] Connection refused DB or Redisが起動していない サービスの起動状態を確認(systemctl status postgresql
Invalid HTTP_HOST header プロキシの Host ヘッダーが渡されていない Nginxで proxy_set_header Host $host; を設定
ModuleNotFoundError 仮想環境にパッケージが入っていない 本番の venvpip install -r requirements.txt を実行

まとめ:Djangoデプロイ チェックリスト

□ DEBUG=False にしている
□ SECRET_KEY を環境変数で管理している
□ ALLOWED_HOSTS に本番ドメインを設定している
□ python manage.py collectstatic を実行した
□ python manage.py migrate を実行した
□ Gunicorn/uWSGI を使っている(runserver は使っていない)
□ Nginx のリバースプロキシが正しく設定されている
□ HTTPS・セキュリティ設定を有効化している
□ .env ファイルを .gitignore に追加している
□ ログの出力先を設定している
□ python manage.py check --deploy でエラー・警告がない

参考リンク

    ゼロから始めるClaudeCode講座のご案内

    テックジム東京本校では「ClaudeCode」の体験講座を開催。
    「その日のうちに動かす」 をゴールに、環境構築から実践まで。
    毎週土曜日15時。参加は無料です。対面・ハンズオンだから初心者でも安心。

    らくらくPython塾 – 読むだけでマスター

    共通テスト「情報I」対策解説講座

    実践で学ぶPython速習講座

    テックジム東京本校

    格安のプログラミングスクールといえば「テックジム」。
    講義動画なし、教科書なし。「進捗管理とコーチング」で効率学習。
    対面型でより早くスキル獲得、月額2万円のプログラミングスクールです。
    情報科目の受験対策指導やAI駆動開発コースもご用意しております。