概要
VPS上にPostgreSQLを構築し、Djangoからローカル接続する設定、マイグレーション、静的ファイルの収集、本番環境の起動確認までを整理する。
Djangoサイトを公開する最後の段階では、データベースへ接続できるだけでなく、秘密情報をソースコードから分離し、Nginx、uWSGI、Django、PostgreSQLの境界をそろえる必要がある。
この記事ではPostgreSQLをDjangoと同じVPSで動かし、5432番ポートをインターネットへ公開しない構成を前提とする。
前提環境
- OS
CentOS Stream 9 - 言語
Python 3.11 - Webサーバー
Nginx - アプリケーションサーバー
uWSGI - フレームワーク
Django 5.2 LTS - データベース
PostgreSQL 16 - ドメイン
example.com - 作業ユーザー
vpsuser - uWSGI実行ユーザー
sweb - Djangoプロジェクト
/var/www/projs/sweb - Python仮想環境
/var/www/venvs/sweb - Django設定モジュール
config - Djangoアプリケーション
tblog
この記事で扱うこと
- PostgreSQL 16の初期化とローカル接続だけを許可する設定。
- SCRAM認証を使ったデータベースユーザーの作成。
- Psycopg 3とDjangoのデータベース接続設定。
- 秘密情報、HTTPS、静的ファイルに関する本番設定。
- マイグレーションからNginx・uWSGI経由の起動確認までの流れ。
作業時の注意点
- PostgreSQLの公開範囲
Djangoと同じVPSで動かす場合、5432番ポートを外部へ開放せず、待受け先と認証対象をローカルホストに限定する。 - 秘密情報
データベースのパスワードとSECRET_KEYをsettings.pyやGit管理下のファイルへ書かない。 - マイグレーション
本番サーバーで新しいマイグレーションを作らず、開発環境で作成・確認したファイルを適用する。 - HTTPS判定
NginxがHTTPSを終端する構成では、転送したスキームをDjangoが正しく判定できないとリダイレクトループになる。 - 静的ファイル
STATIC_ROOTとNginxのaliasを同じ実ディレクトリへ対応させ、収集時はumask 0027と権限確認によってNginxからの書込みを禁止する。
実施内容
PostgreSQLのインストールと初期化
-
PostgreSQL 16をインストール
Django 5.2はPostgreSQL 14以降に対応している。CentOS Stream 9で利用可能なストリームを確認し、PostgreSQL 16のサーバープロファイルをインストールする。$ sudo dnf module list postgresql $ sudo dnf module install -y postgresql:16/server $ psql --version -
データベースクラスタを初期化
postgresql-setup --initdbは新規構築時だけ実行する。既存データがある環境で初期化をやり直さない。$ sudo postgresql-setup --initdb -
ローカル接続とSCRAM認証を設定
/var/lib/pgsql/data/postgresql.confで待受け先と、新しく設定するパスワードの保存方式を指定する。$ sudoedit /var/lib/pgsql/data/postgresql.conf listen_addresses = 'localhost' password_encryption = 'scram-sha-256'/var/lib/pgsql/data/pg_hba.confには、対象データベースとユーザーを限定したルールを追加する。
pg_hba.confは上から順に最初に一致したルールが使われるため、ローカルホスト全体を対象にした広いhostルールより前へ配置する。$ sudoedit /var/lib/pgsql/data/pg_hba.conf # TYPE DATABASE USER ADDRESS METHOD host exampledb appuser 127.0.0.1/32 scram-sha-256 host exampledb appuser ::1/128 scram-sha-256 -
PostgreSQLを起動
$ sudo systemctl enable --now postgresql $ sudo systemctl status postgresql --no-pager
データベースとユーザーの作成
-
アプリケーション専用ユーザーを作成
PostgreSQLの管理ユーザーでpsqlを開き、appuserとexampledbを作成する。パスワードはURLセーフなランダム値を生成してパスワード管理ツールへ保存し、後述の環境ファイルにも同じ値を設定する。$ python3.11 -c 'import secrets; print(secrets.token_urlsafe(48))'\passwordを使うと、生成したパスワードをSQLやシェルの履歴へ残さず対話的に設定できる。$ sudo -u postgres psql postgres=# SHOW password_encryption; postgres=# CREATE ROLE appuser LOGIN; postgres=# \password appuser postgres=# CREATE DATABASE exampledb OWNER appuser ENCODING 'UTF8'; postgres=# \q -
ローカル接続を確認
パスワードをコマンドラインへ直接書かず、プロンプトから入力する。$ psql --host=127.0.0.1 --dbname=exampledb --username=appuser --password exampledb=> \conninfo exampledb=> \q -
外部へ公開されていないことを確認
PostgreSQLが127.0.0.1または::1だけで待ち受け、firewalldで5432番ポートを追加していないことを確認する。0.0.0.0:5432や[::]:5432が表示される場合は設定を見直す。$ sudo ss -lntp | grep 5432 $ sudo firewall-cmd --list-ports
Psycopg 3のインストール
-
仮想環境へPostgreSQLドライバーを追加
Django 5.2ではPsycopg 3が推奨されている。本番環境ではシステムのlibpqやlibsslの更新を反映できるローカルビルドが推奨されているため、開発パッケージを追加してC実装をインストールする。psycopg2とは混在させない。$ sudo dnf install -y postgresql-devel $ source /var/www/venvs/sweb/bin/activate $ python -m pip install "psycopg[c]>=3.1.8" $ python -m pip show psycopg再構築できるよう、実際に採用したバージョンは依存関係ファイルへ固定してGitで管理する。
本番用の秘密情報を分離
-
環境ファイルを作成
uWSGIを実行するswebユーザーと、前記事でswebグループへ追加したvpsuserだけが読み取れるよう、/etc/sweb.envの所有者と権限を制限する。$ sudo install -o root -g sweb -m 640 /dev/null /etc/sweb.env $ sudoedit /etc/sweb.envDJANGO_SECRET_KEY用には、データベースパスワードとは別のURLセーフなランダム値を生成する。$ /var/www/venvs/sweb/bin/python -c 'import secrets; print(secrets.token_urlsafe(64))'次の値は例示用の文字列ではなく、生成した値へ置き換える。単一引用符で囲むことで、systemdと管理コマンド用のシェルの両方で同じファイルを安全に読み込める。
DJANGO_SECRET_KEY='REPLACE_WITH_A_LONG_RANDOM_VALUE' DJANGO_DB_NAME=exampledb DJANGO_DB_USER=appuser DJANGO_DB_PASSWORD='REPLACE_WITH_THE_DATABASE_PASSWORD' -
uWSGIサービスから環境ファイルを読み込む
前記事で作成したsweb.serviceを編集し、任意ファイルを意味する先頭の-を外して、環境ファイルを必須にする。環境ファイル自体はGitへ追加しない。$ sudoedit /etc/systemd/system/sweb.serviceEnvironmentFile=/etc/sweb.env$ sudo systemctl daemon-reload -
管理コマンド用のシェルへ読み込む
マイグレーションなどを実行する間だけ現在のシェルへ読み込み、作業後に環境変数を削除する。$ set -a $ source /etc/sweb.env $ set +a
settings.pyの本番設定
-
PostgreSQLと公開ドメインを設定
/var/www/projs/sweb/config/settings.pyでは秘密情報を環境変数から読み込む。DEBUGを無効化し、許可するホストとオリジンを具体的に指定する。import os SECRET_KEY = os.environ["DJANGO_SECRET_KEY"] DEBUG = False ALLOWED_HOSTS = ["example.com"] CSRF_TRUSTED_ORIGINS = [ "https://example.com", ] DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", "NAME": os.environ["DJANGO_DB_NAME"], "USER": os.environ["DJANGO_DB_USER"], "PASSWORD": os.environ["DJANGO_DB_PASSWORD"], "HOST": "127.0.0.1", "PORT": "5432", } } -
HTTPS関連の設定
NginxがHTTP_X_FORWARDED_PROTOを必ず上書きし、uWSGIがループバックアドレスだけで待ち受ける構成であることを前提に、Djangoへ信頼するヘッダーを設定する。SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") SECURE_SSL_REDIRECT = True SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True -
静的ファイルの出力先を設定
STATIC_URL = "/static/" STATIC_ROOT = Path("/var/www/static/sweb")startprojectで作成したsettings.pyでは、ファイル先頭でPathがインポートされている。独自の設定ファイルへ移す場合はfrom pathlib import Pathも引き継ぐ。MEDIA_ROOTへ保存するアップロードファイルは信頼できない入力となる。利用する場合は静的ファイルと分け、実行されないNginx設定やオブジェクトストレージを別途設計する。
Nginxの転送ヘッダーと静的ファイル設定
-
静的ファイル用ディレクトリを作成
ソースコードの親ディレクトリはNginxから参照させず、collectstaticの出力先を分離する。所有者のvpsuserが更新し、nginxグループは読み取りと通過だけを行う。setgidによって配下にもnginxグループを引き継ぎ、収集時はumask 0027でグループの書込みを禁止する。$ sudo install -d -o root -g root -m 0755 /var/www/static $ sudo install -d -o vpsuser -g nginx -m 2750 /var/www/static/sweb $ sudo restorecon -Rv /var/www/static -
HTTPSのserverコンテキストを確認
Djangoへ転送するスキームをNginx側で上書きし、STATIC_ROOTへ収集したファイルをNginxから直接配信する。location / { include /etc/nginx/uwsgi_params; uwsgi_param HTTP_X_FORWARDED_PROTO $scheme; uwsgi_pass 127.0.0.1:8001; } location /static/ { alias /var/www/static/sweb/; }location /static/とaliasの末尾の/を対応させる。Nginxの実行ユーザーにはstatic配下の読み取り権限と、親ディレクトリを通過する権限が必要になる。
uWSGIは127.0.0.1:8001だけで待ち受け、8001番ポートをfirewalldやVPS側のパケットフィルターで外部へ公開しない。 -
Nginxの構文を確認
$ sudo nginx -t $ sudo systemctl reload nginx
マイグレーションと静的ファイルの収集
-
マイグレーションファイルの不足を確認
マイグレーションファイルは開発環境で作成してGitへ追加する。本番環境では、モデル変更に対応する未作成ファイルがないことを確認する。$ cd /var/www/projs/sweb $ /var/www/venvs/sweb/bin/python manage.py makemigrations --check --dry-run -
データベースへ反映
適用内容を確認し、必要に応じてバックアップを取得してからマイグレーションを実行する。$ /var/www/venvs/sweb/bin/python manage.py migrate --plan $ /var/www/venvs/sweb/bin/python manage.py migrate -
Django管理ユーザーを作成
createsuperuserで作るのはPostgreSQLの管理ユーザーではなく、Django管理サイトへログインするユーザーとなる。$ /var/www/venvs/sweb/bin/python manage.py createsuperuser -
静的ファイルを収集
$ umask 0027 $ /var/www/venvs/sweb/bin/python manage.py collectstatic --noinput $ sudo chown -R vpsuser:nginx /var/www/static/sweb $ sudo find /var/www/static/sweb -type d -exec chmod 2750 {} + $ sudo find /var/www/static/sweb -type f -exec chmod 0640 {} + $ sudo find /var/www/static/sweb -perm /0022 -print最後の
findで何も表示されないことを確認する。これにより、Nginxは静的ファイルを読み取れるが書き換えられない。
本番環境の起動確認
-
DjangoとNginxの事前診断
check --deployの警告は機械的に無視せず、採用した構成に応じて一つずつ確認する。runserverは本番公開には使用しない。$ /var/www/venvs/sweb/bin/python manage.py check --deploy $ /var/www/venvs/sweb/bin/python manage.py migrate --check $ sudo nginx -t -
サービスを再起動
$ sudo systemctl restart postgresql $ sudo systemctl restart sweb $ sudo systemctl restart nginx $ sudo systemctl enable postgresql sweb nginx -
サービスとuWSGIの待受け先を確認
uWSGIが外部アドレスではなく127.0.0.1:8001で待ち受けていることを確認する。$ sudo systemctl status postgresql sweb nginx --no-pager $ sudo ss -lntp | grep 8001 -
HTTPS経由で確認
https://example.com/が正常応答し、http://example.com/がHTTPSへリダイレクトされることを確認する。管理画面を利用する場合は、ブラウザからhttps://example.com/admin/login/を開いてログインも確認する。$ curl -I https://example.com/ $ curl -I http://example.com/ -
ログを確認
502エラーではuWSGIサービスと8001番ポートの待受け、500エラーではDjangoとデータベース接続、静的ファイルの404ではNginxのaliasと権限を順に確認する。$ sudo journalctl -u postgresql -n 50 --no-pager $ sudo journalctl -u sweb -n 50 --no-pager $ sudo journalctl -u nginx -n 50 --no-pager $ sudo tail -n 50 /var/log/nginx/error.log -
作業用の環境変数を削除
$ unset DJANGO_SECRET_KEY DJANGO_DB_NAME DJANGO_DB_USER DJANGO_DB_PASSWORD
まとめ
- Django 5.2からPostgreSQLへ接続する場合は、対応するPostgreSQL 14以降とPsycopg 3を使用する。
- 同一VPS上のPostgreSQLはローカルホストだけで待ち受け、SCRAM認証を使い、5432番ポートを外部へ公開しない。
SECRET_KEYとデータベースパスワードは/etc/sweb.envへ分離し、Git管理下のソースコードへ書かない。- 本番では
DEBUG=False、公開ホスト、CSRF対象オリジン、HTTPS判定、CookieのSecure属性を設定する。 - マイグレーションは事前に内容を確認して適用し、静的ファイルはソースコードの外へ
collectstaticした後にNginxのaliasから配信する。 - 最後に
check --deploy、Nginxの構文、各サービス、uWSGIの待受け先、HTTPS応答、ログを順に確認する。
参考文献
- Red Hat Documentation, Configuring and using PostgreSQL
- PostgreSQL, Linux downloads(Red Hat family)
- Django 5.2 Documentation, Databases
- Psycopg 3 Documentation, Installation
- PostgreSQL 16 Documentation, The pg_hba.conf File
- PostgreSQL 16 Documentation, Password Authentication
- Django 5.2 Documentation, Deployment checklist
- Django 5.2 Documentation, Settings
- Django 5.2 Documentation, How to deploy static files
- Nginx Documentation, Module ngx_http_core_module:alias
- Nginx Documentation, Module ngx_http_uwsgi_module