2026年10月4日
2026年10月4日
nginxとgunicornでDjangoを本番デプロイする手順(systemd・HTTPS対応)
Django を本番環境で公開するときは、開発用の runserver ではなく、アプリケーションサーバーの gunicorn と、その前段に置く nginx を組み合わせる構成が定番です。この記事では、Ubuntu サーバー上で Django の本番設定を整え、gunicorn を systemd で常駐させ、nginx から静的ファイル配信とリバースプロキシを行い、HTTPS 化するまでを順に説明します。502 エラーが出たときの確認順も最後にまとめます。
構成の全体像
リクエストは次の順に流れます。
- ブラウザ → nginx(80/443番ポートで受け付け、HTTPS終端・静的ファイル配信)
- nginx → gunicorn(UNIXソケット経由で動的なリクエストだけを転送)
- gunicorn → Django(WSGIアプリケーションとして処理)
gunicorn は Python のリクエスト処理に専念し、遅いクライアントへの応答やファイル配信は nginx に任せます。gunicorn を直接インターネットに公開しないのは、この役割分担のためです。
この記事では、プロジェクト名を myproject、配置先を /srv/myproject、実行ユーザーを django、ドメインを example.com とします。Django と gunicorn のバージョンは、サポート期間中のものを使ってください。Django はLTS版とそれ以外でサポート期間が異なるため、公式サイトのサポート表で確認できます(2026年10月現在)。
実行ユーザーと仮想環境を用意する
アプリ専用のユーザーを作り、root では動かしません。
sudo apt update
sudo apt install python3-venv python3-dev nginx
sudo useradd --system --create-home --shell /bin/bash django
sudo mkdir -p /srv/myproject
sudo chown django:www-data /srv/myproject
sudo -u django -H bash
cd /srv/myproject
git clone https://example.com/your/repo.git .
python3 -m venv venv
./venv/bin/pip install -r requirements.txt gunicorn
データベースに PostgreSQL や MySQL を使う場合は、対応するドライバーも requirements.txt に含めます。
Django の本番設定
settings.py は、開発用の設定のまま公開しないことが最も大切です。秘密情報は環境変数から読み込みます。
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
DEBUG = False
ALLOWED_HOSTS = ["example.com"]
CSRF_TRUSTED_ORIGINS = ["https://example.com"]
STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
MEDIA_URL = "media/"
MEDIA_ROOT = BASE_DIR / "media"
# nginx が X-Forwarded-Proto を上書きして渡す前提の設定
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
各設定の意味は次のとおりです。
DEBUG = Falseにしないと、エラー時に設定値やスタックトレースが外部に表示されます。ALLOWED_HOSTSに公開するドメインを入れないと、DEBUG = Falseの状態ではすべてのリクエストが 400 エラーになります。CSRF_TRUSTED_ORIGINSは、Django 4.0 以降ではスキーム(https://)付きで書きます。SECURE_PROXY_SSL_HEADERは、nginx がX-Forwarded-Protoヘッダーを毎回上書きしている場合にだけ安全です。クライアントが送ってきた値をそのまま通す構成では、HTTPS 判定を偽装されるおそれがあります。
環境変数は、権限を絞ったファイルに置きます。
sudo mkdir -p /etc/myproject
sudo tee /etc/myproject/env > /dev/null <<'EOF'
DJANGO_SECRET_KEY=your-secret-key
DJANGO_SETTINGS_MODULE=myproject.settings
EOF
sudo chown root:django /etc/myproject/env
sudo chmod 640 /etc/myproject/env
このファイルはリポジトリに含めません。設定が済んだら、静的ファイルの収集とデプロイ向けのチェックを行います。
sudo -u django -H bash -c 'set -a; . /etc/myproject/env; set +a; cd /srv/myproject && ./venv/bin/python manage.py migrate && ./venv/bin/python manage.py collectstatic --noinput && ./venv/bin/python manage.py check --deploy'
check --deploy は、HSTS やクッキーの設定など、本番で見直すべき項目を警告として表示します。警告の内容を読み、自分の環境に必要なものを設定してください。HSTS は一度有効にすると取り消しに時間がかかるため、短い期間から試すのが安全です。
gunicorn を systemd で常駐させる
ソケットとサービスの2つのユニットファイルを作ります。ソケットを systemd に管理させると、nginx からの接続を受けたときに gunicorn が起動していなければ自動で起動します。
/etc/systemd/system/gunicorn.socket:
[Unit]
Description=gunicorn socket for myproject
[Socket]
ListenStream=/run/gunicorn.sock
SocketUser=www-data
SocketMode=600
[Install]
WantedBy=sockets.target
/etc/systemd/system/gunicorn.service:
[Unit]
Description=gunicorn daemon for myproject
Requires=gunicorn.socket
After=network.target
[Service]
User=django
Group=www-data
WorkingDirectory=/srv/myproject
EnvironmentFile=/etc/myproject/env
ExecStart=/srv/myproject/venv/bin/gunicorn \
--workers 3 \
--bind unix:/run/gunicorn.sock \
--access-logfile - \
myproject.wsgi:application
ExecReload=/bin/kill -s HUP $MAINPID
Restart=on-failure
[Install]
WantedBy=multi-user.target
ワーカー数は、gunicorn のドキュメントで「(2 × CPUコア数) + 1」が出発点として示されています。メモリ使用量とレスポンスを見ながら調整します。設定したら起動して状態を確認します。
sudo systemctl daemon-reload
sudo systemctl enable --now gunicorn.socket
sudo curl --unix-socket /run/gunicorn.sock -H "Host: example.com" -I http://localhost/
sudo systemctl status gunicorn
curl でソケットに接続したタイミングで gunicorn が起動し、Django の応答ヘッダーが返れば成功です。ソケットは www-data だけが読み書きできる権限なので sudo を付け、ALLOWED_HOSTS に一致させるため Host ヘッダーを指定しています。コードを更新したときは sudo systemctl reload gunicorn でワーカーを入れ替えます。
nginx の設定
/etc/nginx/sites-available/myproject を作ります。
server {
listen 80;
server_name example.com;
client_max_body_size 10m;
location /static/ {
alias /srv/myproject/staticfiles/;
}
location /media/ {
alias /srv/myproject/media/;
}
location / {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://unix:/run/gunicorn.sock;
}
}
alias のパスは末尾のスラッシュまで location と対応させます。ここがずれると静的ファイルが 404 になります。client_max_body_size はアップロードを受け付ける上限で、既定値のままだと大きなファイルのアップロードが 413 エラーになります。
sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
nginx -t で構文エラーがないことを確認してから再読み込みします。
HTTPS 化する
Let's Encrypt の証明書は certbot で取得できます。
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com
sudo certbot renew --dry-run
certbot が nginx の設定に 443 番の server ブロックとリダイレクトを追加します。--dry-run で自動更新が動くことも確認しておきます。
502 Bad Gateway が出たときの確認順
nginx が gunicorn に接続できないと 502 になります。よくある原因の順に確認します。
- gunicorn が起動していない:
sudo journalctl -u gunicorn -n 50を見ます。ModuleNotFoundErrorなら仮想環境のパスかWorkingDirectory、KeyError: 'DJANGO_SECRET_KEY'なら環境変数ファイルの読み込みを確認します。 - ソケットに接続する権限がない:
/var/log/nginx/error.logにPermission deniedが出ていれば、ソケットの所有者(SocketUser)と nginx の実行ユーザーが一致しているかを確認します。 - ソケットのパスが一致していない: systemd の
ListenStream、gunicorn の--bind、nginx のproxy_passの3か所が同じパスかを見ます。 - 処理がタイムアウトしている: 重い処理で gunicorn のワーカーが既定のタイムアウトを超えて再起動されている場合は、ログに
WORKER TIMEOUTが出ます。処理を見直すか、非同期のジョブに分けます。
400 エラーの場合は ALLOWED_HOSTS、CSRF 検証エラーの場合は CSRF_TRUSTED_ORIGINS と nginx のヘッダー設定を確認します。
まとめ
- nginx は HTTPS と静的ファイル、gunicorn は Django の処理と役割を分ける
DEBUG = False、ALLOWED_HOSTS、CSRF_TRUSTED_ORIGINSを設定し、秘密情報は環境変数で渡す- gunicorn は systemd のソケットとサービスで常駐させ、専用ユーザーで動かす
- nginx の設定は
nginx -tで確認してから反映し、certbot で HTTPS 化する - 502 はログを見て、起動・権限・ソケットのパス・タイムアウトの順に確認する