概要
VPS上にPython、Django、uWSGIを導入し、NginxからDjangoアプリケーションを呼び出すための設定を整理する。
Djangoのrunserverは開発用のサーバーであり、本番運用には使用しない。
この記事では、Nginxが受け取ったHTTP/HTTPSリクエストをuWSGIプロトコルでuWSGIへ渡し、uWSGIがDjangoのWSGIアプリケーションを実行する構成を作る。
前提環境
- OS
CentOS Stream 9 - 言語
Python 3.11 - Webサーバー
Nginx - アプリケーションサーバー
uWSGI 2.0系 - フレームワーク
Django 5.2 LTS - ドメイン
example.com - 作業ユーザー
vpsuser - uWSGI実行ユーザー
sweb(ログイン不可の専用システムユーザー) - Djangoプロジェクト
/var/www/projs/sweb - Python仮想環境
/var/www/venvs/sweb
この記事で扱うこと
- Python 3.11とビルド環境のインストール。
venvを使った仮想環境とDjangoプロジェクトの作成。- 管理ユーザーとuWSGI専用ユーザーの権限分離。
- Nginx、uWSGI、Djangoがリクエストを受け渡す仕組み。
- systemdによるuWSGIの起動とログ確認。
- loopback接続とSELinuxを考慮したNginx連携。
作業時の注意点
- Pythonの実行元
仮想環境を有効化する前はpython3.11を明示し、有効化した後は仮想環境内のpythonとpipを使用する。 - uWSGIプロトコル
socket = 127.0.0.1:8001はHTTPではなくuWSGIプロトコルで待ち受けるため、8001番ポートへcurlで直接アクセスしない。 - 待受け範囲
uWSGIはloopbackだけで待ち受け、8001番ポートをVPSのパケットフィルターやfirewalldへ追加しない。 - プロセス管理
uWSGIをデーモン化せずsystemdの管理下でフォアグラウンド実行し、ログはjournaldで確認する。 - 実行権限
管理作業はvpsuser、uWSGIの実行はログインできないswebユーザーに分ける。配置作業ではumask 0027を設定し、swebにはsudo権限とソースコードの書込み権限を与えない。 - 秘密情報
パスワードやSECRET_KEYをsystemdのunitファイルへ直接書かない。本番用の秘密情報は次の記事で/etc/sweb.envへ分離する。
実施内容
Pythonとビルド環境のインストール
-
Python 3.11、pip、開発ファイル、Cコンパイラをインストール
CentOS Stream 9の無印pythonやpython3は別のバージョンを指す場合があるため、ここではpython3.11を明示する。python3.11-develとgccは、uWSGIを仮想環境へインストールするときのビルドに使用する。$ sudo dnf install -y python3.11 python3.11-pip python3.11-devel gcc $ python3.11 --version $ python3.11 -m pip --version
仮想環境とDjango・uWSGIのインストール
-
uWSGI専用の実行ユーザーを作成
管理ユーザーとアプリケーションの実行権限を分けるため、ログインできないswebシステムユーザーを作成する。vpsuserは、次の記事でswebグループの環境ファイルを読み取れるよう同じグループへ追加する。$ sudo useradd --system --user-group --home-dir /nonexistent --shell /sbin/nologin sweb $ sudo usermod -aG sweb vpsuser $ getent passwd sweb追加した補助グループは次回ログイン時から反映される。作業中のSSH接続を安全に残したまま別のターミナルから再接続し、
idの結果にswebが含まれることを確認する。$ id -
プロジェクト用ディレクトリを作成
アプリケーションのソースコードとPython仮想環境を分けて配置する。所有者を管理用のvpsuser、グループを実行用のswebとし、setgidによって配下にもグループを引き継ぐ。umask 0027によって、同じグループのswebには読取り・実行だけを許可する。$ umask 0027 $ sudo install -d -o vpsuser -g sweb -m 2750 /var/www/projs/sweb $ sudo install -d -o vpsuser -g sweb -m 2750 /var/www/venvs -
Python 3.11で仮想環境を作成
システム領域へsudo pip installせず、プロジェクト専用の仮想環境へPythonパッケージを導入する。$ python3.11 -m venv /var/www/venvs/sweb $ source /var/www/venvs/sweb/bin/activate $ python --version $ python -m pip install --upgrade pip -
DjangoとuWSGIをインストール
Djangoは5.2系、uWSGIは2.0系の範囲で更新を受け取れるように指定する。Django 5.2はLTSで、Python 3.11をサポートしている。$ python -m pip install "Django>=5.2,<5.3" "uWSGI>=2.0,<2.1" $ python -m django --version $ uwsgi --version検証済みの環境を再構築できるよう、実際に採用したバージョンは依存関係ファイルへ固定して管理する。
Djangoプロジェクトとアプリケーションの作成
-
Djangoプロジェクトを作成
configは設定ファイルを置くDjangoプロジェクトパッケージ、tblogはブログ機能を実装するアプリケーションとする。$ source /var/www/venvs/sweb/bin/activate $ cd /var/www/projs/sweb $ django-admin startproject config . $ python manage.py startapp tblog作成後の主な構成は次のようになる。
/var/www/projs/sweb/ ├── config/ │ ├── settings.py │ └── wsgi.py ├── tblog/ └── manage.py -
アプリケーションを登録
/var/www/projs/sweb/config/settings.pyのINSTALLED_APPSへ作成したアプリケーションを追加する。INSTALLED_APPS = [ "tblog.apps.TblogConfig", "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", ] -
公開ホストの最低限の設定
同じsettings.pyで開発モードを無効にし、Nginxで公開するホスト名を限定する。SECRET_KEYやデータベースなどの本番用設定は次の記事で環境変数へ分離する。DEBUG = False ALLOWED_HOSTS = ["example.com"]$ python manage.py check -
ソースコードと仮想環境の権限を確認
配置後にグループ・その他ユーザーの書込み権限を除去する。続くfindで何も表示されなければ、実行ユーザーswebはソースコードと仮想環境を書き換えられない。$ chmod -R g-w,o-rwx /var/www/projs/sweb /var/www/venvs/sweb $ find /var/www/projs/sweb /var/www/venvs/sweb -perm /0022 -print
uWSGI設定ファイルの作成
-
Nginx、uWSGI、Djangoの役割
リクエストは次の順序で渡される。Nginxは外部とのHTTP/HTTPS通信を担当し、uWSGIはDjangoのconfig.wsgi.applicationをワーカープロセスで実行する。ブラウザ ──HTTP/HTTPS──> Nginx ──uWSGIプロトコル──> uWSGI ──WSGI──> Django -
uwsgi.iniを作成
/var/www/projs/sweb/uwsgi.iniを次の内容で作成する。[uwsgi] chdir = /var/www/projs/sweb module = config.wsgi:application home = /var/www/venvs/sweb master = true processes = 2 threads = 2 single-interpreter = true socket = 127.0.0.1:8001 need-app = true vacuum = true die-on-term = truesocketはuWSGIプロトコル用となる。127.0.0.1へ限定することで、同じVPS上のNginxからだけ接続できる。元の構築記録にあったchmod-socket = 666は使用せず、uWSGIをdaemonizeまたはlogtoでバックグラウンド化もしない。
systemdサービスの作成
-
sweb.serviceを作成
/etc/systemd/system/sweb.serviceを次の内容で作成する。uWSGIはログインできない専用のswebユーザーとして起動し、異常終了時だけsystemdが再起動する。[Unit] Description=uWSGI service for sweb After=network.target [Service] Type=simple User=sweb Group=sweb WorkingDirectory=/var/www/projs/sweb EnvironmentFile=-/etc/sweb.env ExecStart=/var/www/venvs/sweb/bin/uwsgi --ini /var/www/projs/sweb/uwsgi.ini Restart=on-failure RestartSec=5s KillSignal=SIGTERM TimeoutStopSec=30s UMask=0027 NoNewPrivileges=true PrivateTmp=true [Install] WantedBy=multi-user.targetEnvironmentFileのパスの先頭にある-は、/etc/sweb.envが未作成でもサービスの起動を失敗させない指定となる。次の記事で秘密情報を保存した後は、同じファイルをサービスから読み込める。 -
unitファイルを検証して起動
uWSGIをフォアグラウンドで動かすため、標準出力と標準エラーはjournaldへ記録される。$ sudo systemd-analyze verify /etc/systemd/system/sweb.service $ sudo systemctl daemon-reload $ sudo systemctl enable --now sweb $ sudo systemctl status sweb --no-pager $ sudo journalctl -u sweb -n 50 --no-pager -
待受け先を確認
127.0.0.1:8001だけが表示されることを確認する。0.0.0.0:8001や[::]:8001で待ち受けている場合は、uwsgi.iniを見直す。$ sudo ss -lntp | grep ':8001' $ sudo firewall-cmd --list-all8001番ポートは外部公開しないため、
firewalldやVPS側のパケットフィルターへ追加する必要はない。
NginxとSELinuxの設定
-
NginxからuWSGIへ転送
前の記事で作成した/etc/nginx/conf.d/example.com.confのうち、HTTPS用serverブロックにあるlocation /を次の内容へ置き換える。新しいserverブロックを重複して作成しない。location / { include /etc/nginx/uwsgi_params; uwsgi_param HTTP_X_FORWARDED_PROTO $scheme; uwsgi_pass 127.0.0.1:8001; }/etc/nginx/uwsgi_paramsは、リクエストメソッドやパスなどをuWSGIへ渡すためのパラメーターを定義している。HTTP_X_FORWARDED_PROTOは、Nginxが受け取った通信のスキームで毎回上書きする。 -
SELinuxでNginxの接続を許可
CentOS Stream 9でSELinuxが有効な場合、Nginxがloopback上のuWSGIへ接続できるよう、Red HatのNginxリバースプロキシ手順で案内されているbooleanを有効にする。$ getenforce $ sudo setsebool -P httpd_can_network_connect 1 $ getsebool httpd_can_network_connectSELinuxを無効化して回避せず、拒否された場合は
journalctlや監査ログで原因を確認する。
設定確認と起動確認
-
Django、systemd、Nginxの設定を確認
Nginxは構文確認に成功してから再読込みする。check --deployで表示されるHTTPS、Cookie、データベースなどの警告は、次の記事の本番設定で解消してから公開する。$ cd /var/www/projs/sweb $ /var/www/venvs/sweb/bin/python manage.py check $ /var/www/venvs/sweb/bin/python manage.py check --deploy $ sudo systemctl is-active sweb $ sudo nginx -t $ sudo systemctl reload nginx -
Nginx経由でDjangoの応答を確認
--resolveで接続先だけをloopbackへ固定し、ドメイン名とTLSのSNIはexample.comのまま確認する。実際の構築では、取得した証明書のドメインへ置き換える。$ curl -I --resolve example.com:443:127.0.0.1 https://example.com/admin/login/Djangoの応答として
200またはログイン画面への302が返れば、Nginx、uWSGI、Djangoの連携を確認できる。502 Bad Gatewayの場合は、systemctl status sweb、journalctl -u sweb、8001番ポートの待受け、SELinuxの拒否を順に確認する。
まとめ
- Djangoの本番構成では、Nginxが外部通信、uWSGIがWSGIアプリケーションの実行を担当する。
- Pythonパッケージはプロジェクト専用の仮想環境へ導入し、uWSGIも同じPython 3.11環境で動かす。
- uWSGIは
127.0.0.1:8001だけで待ち受け、外部向けのファイアウォールへ8001番ポートを追加しない。 - uWSGIをsystemdの管理下でフォアグラウンド実行すると、起動・停止・再起動とログ確認を一元化できる。
- 管理ユーザーとuWSGI実行ユーザーを分けると、アプリケーションから管理者権限やソースコードの書込み権限を切り離せる。
- Nginxからloopbackへ接続する構成でも、SELinuxの
httpd_can_network_connectを確認し、設定変更後はDjango、systemd、Nginxをそれぞれ検証する。
参考文献
- Red Hat, Installing and using Python
- Python 3.11 Documentation, venv — Creation of virtual environments
- Django Documentation, Django 5.2 release notes
- Django Documentation, How to use Django with uWSGI
- Django Documentation, Deployment checklist
- uWSGI 2.0 Documentation, Quickstart for Python/WSGI applications
- uWSGI 2.0 Documentation, Systemd
- NGINX Documentation, Module ngx_http_uwsgi_module
- Red Hat, Deploying web servers and reverse proxies:Configuring NGINX as a reverse proxy
- systemd Documentation, systemd.exec