さくらのレンタルサーバで Litestar を CGI / mod_wsgi で動かした記録
TL;DR: suexec 付き共用サーバーでも、pyenv + a2wsgi を組み合わせれば Litestar (ASGI) を CGI / mod_wsgi で動かせる。非同期や WebSocket は諦めて、小規模 API に割り切ると幸せ。
Qiita 用に、試行錯誤の結果と設定例をまとめました。~/www/ をルートにしたシンプルな構成で進めます。
前提と環境
- さくらインターネット共用サーバー (FreeBSD 13.x、suexec 有効)
- Python 3.12.6 を pyenv でユーザー領域にインストール
- 依存ライブラリ:
litestar,a2wsgi,sniffio - ドキュメントルート:
~/www/
/home/enujii/
├── www/
│ ├── .htaccess
│ ├── app.py
│ └── index.cgi
└── log/
Step 1. pyenv + OpenSSL で Python 3.12.6 を用意
共有サーバーの Python は古いので、自前でビルド。
opensslのリリースは下記を参考に。
mkdir -p ~/local/src && cd ~/local/src
curl -L https://github.com/openssl/openssl/releases/download/openssl-3.6.0/openssl-3.6.0.tar.gz -o openssl-3.6.0.tar.gz
tar xvfpz openssl-3.6.0.tar.gz
git clone https://github.com/pyenv/pyenv.git ~/.pyenv
cat <<'EOF' >> ~/.bashrc
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
export TMPDIR=$HOME/tmp
eval "$(pyenv init -)"
EOF
source ~/.bashrc
mkdir -p ~/local/openssl/3.6.0 && cd ~/local/src/openssl-3.6.0
./Configure --prefix=$HOME/local/openssl/3.6.0
make install_sw
CONFIGURE_OPTS="--with-openssl=$HOME/local/openssl/3.6.0" \
PY_UNSUPPORTED_OPENSSL_BUILD=static \
TMPDIR="$HOME/tmp" \
pyenv install 3.12.6
pyenv global 3.12.6
pip install --upgrade pip
pip install litestar a2wsgi sniffio
Step 2. Litestar アプリ (~/www/app.py)
from litestar import Litestar, get
@get("/", sync_to_thread=False)
def hello_world() -> dict[str, str]:
return {"message": "Hello, Litestar on Sakura CGI!"}
@get("/api", sync_to_thread=False)
def api() -> dict[str, str]:
return {"message": "Hello, API on Sakura CGI!"}
app = Litestar(route_handlers=[hello_world, api])
Step 3. CGI エントリ (~/www/index.cgi)
#!/home/enujii/.pyenv/versions/3.12.6/bin/python
"""CGI entry point wrapping Litestar via a2wsgi."""
from a2wsgi import ASGIMiddleware
from wsgiref.handlers import CGIHandler
from app import app
wsgi_app = ASGIMiddleware(app)
if __name__ == "__main__":
CGIHandler().run(wsgi_app)
Step 4. .htaccess 設定
# ~/www/.htaccess
Options +ExecCGI
AddHandler cgi-script .cgi .py
DirectoryIndex index.cgi index.html index.htm
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.cgi/$1 [QSA,L]
<FilesMatch "\.py$">
Require all denied
</FilesMatch>
サブディレクトリで使う場合は RewriteRule を相対パス (index.cgi/$1) に。@docs/hosting/sakura-cgi/src.htaccess 参照。
Step 5. パーミッション (suexec 対策)
chmod 755 ~/www ~/www/src
chmod 755 ~/www/index.cgi
chmod 644 ~/www/.htaccess ~/www/app.py
Home 配下 / 所有者本人 / 755 (ディレクトリ・CGI) が必須。shim (~/.pyenv/shims/python) は使わないこと。
Step 6. ハマりポイント & 解決
| エラー | 原因 | 解決 |
|---|---|---|
Request exceeded the limit of 10 internal redirects |
.htaccess の Rewrite が /index.cgi のような絶対パス |
ルート・サブディレクトリとも index.cgi/$1 に変更 |
suexec policy violation |
shebang がホーム外 / 700 パーミッション / shim 利用 |
#!/home/.../python に固定、chmod 755 ~/www & index.cgi
|
ModuleNotFoundError: sniffio |
Litestar 依存不足 | pip install sniffio |
DeprecationWarning: cgitb + Response header name '<!--'
|
cgitb が HTML を吐いてヘッダー壊す |
cgitb を削除し、ログでスタックトレース確認 |
Step 7. デバッグ Tips
- エラーログはコントロールパネルから取得(
~/log/error_logには無い, 0時にサーバー反映っぽい)。 -
tail -fが使えないので、アクセス直後にログを再ダウンロード。 - suexec の詳細は「スクリプトログ」で確認。
- GitHub リリースアセットは
curl -Lを忘れず(0バイト tar の罠)。
Step 8. まとめ
- pyenv + OpenSSL で最新 Python を用意。
- Litestar をシンプルな JSON API として実装し、
sync_to_thread=Falseで警告回避。 -
a2wsgiで ASGI → WSGI に変換して CGI 経由で実行。 -
.htaccess/ 権限 / shebang を suexec の要件に合わせる。 - エラーログはコントロールパネルで確認。
実際にハマったところ & 感想
-
~/www/src/に置いたままでは動かなかった: suexec が~/www/index.cgiを期待しているため、サブディレクトリ経由の Rewrite が想定以上に複雑化。何度も Rewrite → リダイレクト地獄に陥り、最終的に「ルート直下に集約する」方針に落ち着いた。 -
ログがすぐには見つからない:
~/log/error_logには何も出ず、コントロールパネルで毎回ダウンロードする必要があることに気付くまで時間を浪費。 - suexec policy violation の連発で心が折れかけた。shebang / パーミッション / 所有者 / ディレクトリ階層がすべて揃って初めて実行される、と体で覚える結果に。
- 失敗を繰り返したことで、共用サーバーの制約を丁寧に一つずつ潰す大切さ (そして大変さ) を痛感した。
基本的に「できるだけシンプルに」「ルート直下で完結させる」ことが成功への近道でした。
付録: Apache + mod_wsgi + a2wsgi で Litestar を動かす場合
a2wsgi を WSGI スクリプトで使えば、mod_wsgi しかない環境でも Litestar を動かせます。ただし非同期の利点は失われるため、小規模 API のみ推奨。
# wsgi.py
import os
import sys
sys.path.insert(0, os.path.dirname(__file__))
from a2wsgi import ASGIMiddleware
from app import app
wsgi_app = ASGIMiddleware(app)
application = wsgi_app
Apache 設定例:
WSGIDaemonProcess mylitestar python-home=/home/enujii/.pyenv/versions/3.12.6
WSGIProcessGroup mylitestar
WSGIScriptAlias / /home/enujii/www/wsgi.py
<Directory /home/enujii/www>
Require all granted
</Directory>
注意点
- 非同期 I/O や WebSocket は基本的に不可。
- ラッパーのオーバーヘッドでパフォーマンスは ASGI サーバーに劣る。
- 共有環境では mod_wsgi の設定を細かく変えられないことも多い。
代替案 (推奨)
Client → Apache / Nginx (リバースプロキシ) → ASGI サーバー (uvicorn / hypercorn) → Litestar
ASGI の強みをフルに活かすならこちらの構成を検討してください。a2wsgi + mod_wsgi は「どうしても WSGI 環境しかない」場合の最終手段です。