Python の Web フレームワークである FastAPI は、手軽に高性能な API を構築できることから人気が高まっています。
しかし、安価な共用レンタルサーバーで FastAPI を公開しようとすると想像以上にハードルが高く、多くの落とし穴にはまります。
私もよく使っているWordpress用のVPSとして有名なのが「カラフルボックス (ColorfulBox)」なんですが、このサーバ機能(cPanel)でPython Appを動作させることができるのですが、これが意外と難しいというかクセがあるんですよね。
元々そんな構成での動作を想定してないと言われてしまえば、それでおしまいなんですが、やはり個人開発者としては”どうにかする”というのが醍醐味ですよね。
ということで、今回はカラフルボックスの共用サーバー環境に FastAPI をデプロイするまでに遭遇したエラーや設定ミスをすべてまとめました。
同じように挑戦する方の参考になれば幸いです。
環境概要
- サーバー: ColorfulBox 共用サーバー (cPanel 環境 / LiteSpeed + Passenger)
- アプリケーション: FastAPI (Python 3.11)
- デプロイ方法: GitHub Actions からサーバーに SSH で git pull → Python 依存アップデート → アプリ起動
- 公開ポート: FastAPI はローカルでポート 8080 に起動し、Web サーバーがリバースプロキシとして /api/ パスや /docs 等を中継する
ColorfulBox の「Python アプリケーション」機能は Passenger (mod_passenger) を使い、passenger_wsgi.py をエントリーポイントとして Python アプリを起動します。
FastAPI を通常どおり uvicorn で起動するときとは違う点が多く、uvicorn が起動しない、.htaccess のリライトルールで API にアクセスできない などのトラブルが発生しました。
1. 初期設定と基本構成

まずは cPanel の「Setup Python App」で Python アプリを作成し、仮想環境と起動スクリプトを準備します。
FastAPI は WSGI アプリではなく ASGI アプリなので、Passenger だけでは起動できません。
代わりに uvicorn をバックグラウンドで起動し、Passenger は単なる「ブートストラップ」的な挨拶を返すだけにします。

| 項目 | 入力例 | 説明 |
|---|---|---|
| Application root | public_html/DOMAIN/app Fast APIのmain.pyがある場所 | FastAPI プロジェクトのルートディレクトリ。ColorfulBox/cPanel のホームディレクトリからのパスを指定します。お使いの環境に合わせてパスを読み替えてください。 |
| Application URL | DOMAIN ドメイン移行は任意 | 事前に設定しておいたアプリケーション用サブドメインを選択します。 |
| Application startup file | passenger_wsgi.py | WSGI エントリーポイントが定義されているファイル名を入力します。今回の構成では passenger_wsgi.py です。 |
| Application Entry point | application | passenger_wsgi.py 内で定義した WSGI callable 名を入力します。サンプルでは application 関数を返すように定義してあります。 |
passenger_wsgi.py の最終的な形は次のようになりました。
"""WSGI entrypoint for cPanel/Passenger.
Uvicorn は別プロセスで起動するため、このファイルは単に生存確認用のレスポンスを返します。
"""
def application(environ, start_response):
start_response("200 OK", [("Content-Type", "text/plain")])
return [b"passenger ok"]当初はこの passenger_wsgi.py 内で uvicorn を起動しようとしましたが、デプロイ時に旧プロセスを止められなかったり、2 重起動してポート競合を起こすなど問題が多かったため、最終的には GitHub Actions 側で起動・停止を制御 する方式に落ち着きました。
環境変数の設定(必要に応じて)
- OPENAI_API_KEY や SUPABASE_URL など、FastAPI アプリが必要とする設定を「Environment variables」に追加してください。
- cPanel の変数名と値を入力するフォームから追加できます。
2. .htaccess の落とし穴
WordPress やフロントエンドアプリと同居させるために、public_html/DOMAIN/ 直下に React の SPA や静的ファイルが置かれていました。
そのため、.htaccess で以下のようなルールを設定していました。
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
# 静的ファイルが存在する場合はそのまま配信
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
# それ以外のリクエストは uvicorn へ転送
RewriteRule ^api/(.*)$ http://127.0.0.1:8080/api/$1 [P,L]
# SPA 用にその他のパスは /index.html へ
RewriteRule ^ /index.html [L]
</IfModule>ここでつまずいた点は以下です。
- RewriteRule ^ /index.html が API エンドポイント /docs などを上書きしてしまい、FastAPI のドキュメントが 404 になる。API へのパスより前に SPA のリダイレクトを書いてしまうと必ず /index.html が返ってしまいました。
- LiteSpeed + mod_rewrite では大文字小文字や先頭スラッシュの扱いが Apache と微妙に異なり、意図したとおりに動かないことがある。今回は /api/ というプレフィックスを決め打ちし、他は静的ファイルと見なすようにしました。
結論としては、API 用のパスを必ず最上位にマッチさせることと、SPA のリダイレクトはそれ以外のパスだけに適用することが重要です。
3. uvicorn が起動しない!
最初は passenger_wsgi.py 内で subprocess.Popen を使って uvicorn を起動するコードを採用しました。
以下のような内容です。
from pathlib import Path
import subprocess, os, socket
BASE_DIR = Path(__file__).resolve().parent
BACKEND_DIR = BASE_DIR / "backend"
TMP_DIR = BASE_DIR / "tmp"
UVICORN = "/home/user/virtualenv/…/bin/uvicorn"
HOST, PORT = "127.0.0.1", 8080
# 既にリッスンしているか確認し、していなければ uvicorn を起動
if not is_listening(HOST, PORT):
TMP_DIR.mkdir(parents=True, exist_ok=True)
p = subprocess.Popen([UVICORN, "main:app", "--host", HOST, "--port", str(PORT)],
cwd=str(BACKEND_DIR),
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
start_new_session=True)
(TMP_DIR / "uvicorn.pid").write_text(str(p.pid))この方法は一見うまく動きますが、デプロイのたびに Passenger がプロセスを fork するため、古い uvicorn が残ったまま新しい uvicorn が起動してポートが重複するケースが頻発しました。
また、開発中に手作業で kill する必要があり、CI/CD の自動化から遠ざかっていました。
解決策: デプロイ時にだけ uvicorn を起動
最終的に、uvicorn の起動・停止はすべて GitHub Actions 上のスクリプトで処理し、Passenger は「起動していることを前提」に単に 200 OK を返すだけにしました。こうすることで:
- デプロイ時に確実に古いプロセスを停止できる。
- 起動コマンドやポート設定を Actions で一元管理できる。
- Passenger のプロセス管理と uvicorn のプロセス管理が分離できる。
後述する deploy.yml を参照してください。
4. GitHub Actions での自動デプロイとプロセス管理
デプロイは GitHub の main ブランチへ push したときに自動実行されます。

CI のジョブはサーバーに SSH してリポジトリを更新し、依存パッケージをインストールし、必要なら uvicorn を再起動します。以下が最終的な deploy.yml のスクリプトです(一部省略)。
name: Deploy to ColorfulBox via git pull
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Prepare SSH key
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_KEY }}" > ~/.ssh/id_xxxxx
chmod 600 ~/.ssh/id_xxxxx
ssh-keyscan -p "${{ secrets.SSH_PORT }}" "${{ secrets.SSH_HOST }}" >> ~/.ssh/known_hosts
- name: Deploy on server
run: |
ssh -p "${{ secrets.SSH_PORT }}" "${{ secrets.SSH_USER }}"@"${{ secrets.SSH_HOST }}" << 'EOF'
set -e
APP_DIR=/home/USER/public_html/DOMAIN/app/
PIDFILE=$APP_DIR/tmp/uvicorn.pid
cd "$APP_DIR"
git fetch origin
git reset --hard origin/main
git clean -fd
# Python 依存の更新
VENV_BIN=/home/USER/virtualenv/public_html/DOMAIN/app/3.11/bin
"$VENV_BIN/pip" install --upgrade pip
"$VENV_BIN/pip" install -r requirements.txt
# 既存 uvicorn プロセスの停止
stop_pid() {
_pid="$1"
if [ -n "$_pid" ] && kill -0 "$_pid" 2>/dev/null; then
kill "$_pid" || true
sleep 1
if kill -0 "$_pid" 2>/dev/null; then
kill -9 "$_pid" || true
fi
fi
}
if [ -f "$PIDFILE" ]; then
PID=$(cat "$PIDFILE" || true)
stop_pid "$PID"
rm -f "$PIDFILE"
else
PIDS=$(pgrep -f "uvicorn main:app.*--host 127\.0\.0\.1.*--port 8080" || true)
if [ -n "$PIDS" ]; then
for PID in $PIDS; do
stop_pid "$PID"
done
fi
fi
# uvicorn をバックグラウンドで起動
mkdir -p "$APP_DIR/tmp"
cd "$APP_DIR/backend"
nohup "$VENV_BIN/python" -m uvicorn main:app --host 127.0.0.1 --port 8080 \
>> "$APP_DIR/tmp/uvicorn.out.log" 2>> "$APP_DIR/tmp/uvicorn.err.log" &
echo $! > "$PIDFILE"
# 起動確認 (ローカル) – 最大 40 秒待つ
for i in $(seq 1 40); do
if curl -fsS http://127.0.0.1:8080/health >/dev/null; then
echo "uvicorn health ok"
break
fi
echo "uvicorn not ready (attempt $i)"
sleep 1
done
# 公開エンドポイントの確認 – Web サーバー側のプロキシ設定によっては時間がかかる
for i in $(seq 1 20); do
if curl -fsS https://DOMAIN/health >/dev/null; then
echo "public health ok"
break
fi
echo "public health not ready (attempt $i)"
sleep 2
done
EOF主なポイントは次のとおりです。
- git reset –hard + git clean -fd でサーバー側の作業ディレクトリをリセットしています。これは乗っ取り対策と不要ファイルの残存による衝突を防ぐために重要です。
- 依存パッケージは常に pip install -r requirements.txt で更新します。ColorfulBox の仮想環境は Python バージョン毎に異なるパスにあるため VENV_BIN をハードコードしています。
- プロセス管理は PIDFILE があればそれを優先し、ない場合は pgrep で uvicorn main:app を検索して全て kill します。これで二重起動やゾンビ化したプロセスを排除します。
- nohup で uvicorn をバックグラウンド起動し、PID を書き出します。標準出力・標準エラーは tmp ディレクトリにログとして保存し、デバッグに利用できます。
- 起動直後に /health エンドポイントへ curl して正常に起動しているか確認し、さらに公開 URL 経由でも確認しています。
これにより、デプロイ後は常に新しい uvicorn プロセスが 8080 で待ち受けており、Passenger がリクエストを適切に転送してくれます。
5. よく踏んだその他の地雷
Passenger と環境変数の自動書き換え
ColorfulBox の cPanel では、「Setup Python App」で設定した環境変数が自動的に .htaccess や passenger_wsgi.py に埋め込まれます。
知らないうちにエントリポイントや環境変数が書き換えられてしまい、「変更したはずのファイルが元に戻っている」という現象がありました。
重要な設定ファイルは Git 管理下に置き、cPanel からは編集しないようにすると安心です。
Git の強制上書き
deploy.yml で git pull を行っていた際、サーバー側で手作業で編集したファイル (passenger_wsgi.py など) が競合して merge に失敗し、自動デプロイが止まってしまうことがありました。
そのため、git reset –hard origin/main で常にリモートを優先するようにしています。
サーバー側を手動編集する場合は必ずローカルリポジトリに戻しましょう。
cron で uvicorn を監視する必要はない
当初は uvicorn を永続化させるために cron で定期的に curl /health を叩いて、生存確認に失敗したら再起動するバッチスクリプトを検討しました。
しかし、Passenger は各リクエストごとに passenger_wsgi.py をロードし直すため、ロードのタイミングで uvicorn が停止していれば再起動が必要になってしまいます。
GitHub Actions での起動をしっかり行うことで cron の監視が不要になりました。
万が一 uvicorn が落ちたら WordPress から 503 が返るため、手動で再デプロイすれば解決します。
6. まとめと教訓
ColorfulBox の共用サーバー上で FastAPI を稼働させるまでに、数々の罠にはまりました。まとめると次のような教訓があります。
- Passenger では ASGI を直接扱えないので、uvicorn などでプロセスを起動する必要がある。
- .htaccess のリライトルールは API ルートを最優先し、SPA 用リダイレクトは後ろに書く。
- passenger_wsgi.py で uvicorn を起動すると複数プロセスが残るため、デプロイ側で起動・停止を制御する方法が安定する。
- GitHub Actions を使う場合は git reset –hard + git clean で競合を防ぎ、依存更新とプロセス再起動を一連の作業に組み込む。
- 共用サーバーでは cPanel の自動書き換えや環境変数の扱いに注意し、重要ファイルは Git 管理下で上書きを防ぐ。
この記事が同じような構成で FastAPI をホスティングしたい人の助けとなれば幸いです。
カラフルボックスに限らず、Passenger 環境で ASGI アプリを動かす際には「誰がどこで uvicorn を起動し、どこで停止するのか」を整理することが成功の鍵です。


コメント