【GeminiとPythonアプリデプロイ】第2章 ApacheとFastCGIの深淵 – .htaccess設定の死闘と光明

Geminiと共に挑むXserver共用サーバーでのPython Flaskアプリケーションデプロイ記、第2章です。

前回(第1章)では、Geminiの提案でFlaskアプリケーション「テキストアナライザー」の骨格を作り上げ、FastCGIでの公開を目指してサーバー設定を開始しました。しかし、Shebangのパス問題、PythonモジュールのModuleNotFoundErrorの連鎖(flask, flup(実はflup6パッケージ)、実際には細かいエラーも多数、数々の初期トラブルをGeminiの助けを借りながら一つ一つ解決してきました。

Pythonスクリプト自体はエラーなくインポートされ、FastCGIサーバーとして起動する準備は整ったかに見えました。しかし、ブラウザ経由でアクセスすると、依然としてApacheのエラーログには AH01276: Cannot serve directory ... や、時には原因不明の404エラーが記録され、アプリケーションは姿を現しません。

今回は、このApacheとFastCGIの連携部分、特に .htaccess の設定に焦点を当て、なぜリクエストが正しくPythonアプリケーションに届かないのか、その原因究明と解決への試行錯誤を詳述します。


ApacheとFastCGIの壁:.htaccess 設定の迷宮

Pythonスクリプト (main.fcgi、元 app.fcgi デバッグ中の切り分けとして名前を変更) の準備は整ったはずなのに、ブラウザからのアクセスがうまくいかない。この問題の核心は、Apacheがリクエストを main.fcgi に正しくルーティングできていないことにありました。

問題点3:サブディレクトリの .htaccess が期待通りに機能しない (AH01276 再発)

Gemini:「.htaccess がApacheに読み込まれていることは確認済みですね(以前の500エラーテストより)。現在のエラー AH01276 は、main.fcgi がディレクトリのデフォルトハンドラとして認識されていないか、あるいは書き換えルールが機能していないことを示唆しています。Xserverが推奨するFastCGIのハンドラ名 (AddHandler で指定する値) と記述方法を再確認してください。」

これまでにXserverのサポートへ何度も問い合わせろと言われ続けていたので、無駄だと思いつつ問い合わせることに。
サポートからは「技術的な詳細はサポートできない」ものの、「AddHandler fcgid-script .fcgi は問題なさそう」という曖昧なコメントを得ました。そこでサブディレクトリ (/apps/text-analyzer/) に設置した .htaccess の内容を調整し続けました。

Apache

# /apps/text-analyzer/.htaccess の試行錯誤例
Options +ExecCGI -Indexes
RewriteEngine On

# ハンドラ設定の試行
AddHandler fcgid-script .fcgi 
# <Files "main.fcgi">
#   SetHandler fcgid-script
# </Files>

# DirectoryIndexの試行
DirectoryIndex main.fcgi

# 書き換えルールの試行
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# RewriteCond %{REQUEST_URI} !/main\.fcgi$ # この行の有無やフラグでエラーが出たりした
RewriteRule ^(.*)$ main.fcgi/$1 [L,QSA]

しかし、ブラウザで /apps/text-analyzer/ にアクセスすると AH01276 が、/apps/text-analyzer/main.fcgi に直接アクセスすると 404 Not Found が表示されるという状況が続きました。ルートの .htaccess (WordPress用) を一時的に無効化しても、この404エラーは解消されませんでした。

ブレイクスルー:超シンプルな test.fcgi での動作確認

Gemini:「問題が複雑に絡み合っている可能性があります。一旦 main.fcgi (Flaskアプリ) から離れ、同じディレクトリに、非常にシンプルな内容の test.fcgi を作成し、同じ .htaccess 設定でこれが動作するか確認しましょう。これで、FastCGI実行環境の基本的な部分が機能しているか切り分けられます。」

本来ならもっと早くにやるべきだと思ってはいたものの、Geminiのデバッグ力を試すために言われるがままにこれまでやってきましたが、この提案に基づき、以下の test.fcgi を作成しました。

Python

#!/home/takelab/take-lab.com/public_html/apps/text-analyzer/venv/bin/python 
# (あるいは /home/takelab/.local/share/uv/python/cpython-3.13.3-.../python3.13 など、最終的に動作したShebang)
# -*- coding: utf-8 -*-
import sys
import os
# (sys.path設定もtest.fcgiにも記述)
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
python_version_short = f"python{sys.version_info.major}.{sys.version_info.minor}"
venv_site_packages = os.path.join(BASE_DIR, 'venv', 'lib', python_version_short, 'site-packages')
if os.path.isdir(venv_site_packages):
    if venv_site_packages not in sys.path:
        sys.path.insert(0, venv_site_packages)
if BASE_DIR not in sys.path:
    sys.path.insert(0, BASE_DIR)

def simple_app(environ, start_response):
    status = '200 OK'
    headers = [('Content-type', 'text/plain; charset=utf-8')]
    start_response(status, headers)
    return [b"Hello from test.fcgi! This is a simple FastCGI test."]

try:
    from flup.server.fcgi import WSGIServer
    WSGIServer(simple_app).run()
except Exception as e:
    # エラーを stderr に出力 (Apacheエラーログへの記録を期待)
    import traceback
    sys.stderr.write(f"Error in test.fcgi: {e}\n{traceback.format_exc()}\n")
    # FastCGIとして最低限のエラー応答
    print("Content-Type: text/plain; charset=utf-8")
    print("Status: 500 Internal Server Error")
    print()
    print("Error in test.fcgi script.")
    sys.exit(1)

そして、/apps/text-analyzer/.htaccessRewriteRule のターゲットを test.fcgi に変更。

Apache

# /apps/text-analyzer/.htaccess (test.fcgi用)
Options +ExecCGI -Indexes
RewriteEngine On
AddHandler fcgid-script .fcgi 
DirectoryIndex test.fcgi # DirectoryIndexもtest.fcgiに
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ test.fcgi/$1 [L,QSA] #ターゲットをtest.fcgiに

すると、https://take-lab.com/apps/text-analyzer/test.fcgi および https://take-lab.com/apps/text-analyzer/ の両方で、「Hello from test.fcgi! This is a simple FastCGI test.」 と表示されました! これは大きな前進です。基本的なFastCGIの実行、.htaccess によるルーティング(DirectoryIndexRewriteRule の両方)、Shebang、Python仮想環境の認識、flup の動作が、このシンプルなケースでは問題ないことが確認できました。

問題点4:本番スクリプト main.fcgi の内容が原因?

test.fcgi が動作するということは、問題は main.fcgi のファイル名ではなく、その内容にある可能性が浮上しました。
Gemini:「main.fcgi の内容を一時的に test.fcgi と全く同じにしてみてください。それで動作すれば、元の main.fcgi のコード(Flaskアプリをロードする部分)に、Apacheが実行前にリクエストを404やAH01276として処理してしまう何らかの原因があることになります。」

指示通り、cp test.fcgi main.fcgi を実行し、main.fcgi の内容を test.fcgi と同一にしたところ、https://take-lab.com/apps/text-analyzer/main.fcgi (および .htaccess のターゲットを main.fcgi に戻した上での /apps/text-analyzer/ へのアクセス) で、見事「Hello from test.fcgi!…」が表示されました!

これで、ファイル名や基本的なFastCGI設定ではなく、Flaskアプリケーションをロードする main.fcgiスクリプト内容そのものが、Apacheの不可解な挙動(実行前の404やAH01276エラー)を引き起こしていたことがほぼ確定しました。

第3章へ続く。。