// 01 — BACKGROUND
背景:mu-plugins に移行した独自ロガー
運営する5サイトでは、共通のオリジナル PHP 関数 uah_logging() を使ってアクセスログを出力している。
この関数は /var/www/html/templates/log.php に定義されており、
各サイトの WordPress テーマから呼び出していた。
保守性向上のため、呼び出し処理を mu-plugins/access-logger.php に集約した。
mu-plugins はプラグインの有効化操作なしに常時実行されるため、サイト横断の共通処理に都合がよい。
しかし、この移行後に WordPress の Android / iOS アプリが突然サイト情報を取得できなくなった。
// 02 — DIAGNOSIS
切り分けの流れ
まず REST API エンドポイントに直接アクセスして状態を確認する。
$ curl -I https://example.com/wp-json/wp/v2/ HTTP/2 403 vary: accept,content-type set-cookie: PHPSESSID=375a7a7c...; path=/ content-type: text/html; charset=UTF-8 server: Apache/2.4.58 (Ubuntu)
▸ ヘッダーを読む
このレスポンスには重要な手がかりが含まれている。
PHPSESSID が返っているということは、リクエストが Apache を通過し PHP まで到達している証拠だ。
Apache が遮断しているならセッション Cookie は絶対に返らない。
つまり 403 を返しているのは Apache ではなく WordPress(PHP)の内側である。
| レスポンスヘッダー | 意味 | 判断 |
|---|---|---|
| HTTP/2 403 | アクセス拒否 | ✗ 問題あり |
| PHPSESSID=... | PHP まで到達済み | → Apache は無関係 |
| content-type: text/html | JSON ではなく HTML を返している | ✗ WordPress が処理していない |
▸ プラグイン全無効化テスト
$ mv /var/www/html/wp-content/plugins \ /var/www/html/wp-content/plugins_disabled $ curl -I https://example.com/wp-json/wp/v2/ HTTP/2 403 # ← 変わらない → plugins は無関係
通常のプラグインを全無効化しても 403 が継続。
mu-plugins は plugins フォルダのリネームでは無効化されないため、
次の容疑者として mu-plugins を確認する。
$ mv /var/www/html/wp-content/mu-plugins \ /var/www/html/wp-content/mu-plugins-d $ curl -I https://example.com/wp-json/wp/v2/ HTTP/2 200 # ← 200 に変わった → 犯人確定 link: <https://example.com/index.php?rest_route=/>; rel="https://api.w.org/"
// 03 — FIX 1
修正①:access-logger.php に REST API スキップを追加
access-logger.php は template_redirect フックから log.php を
呼び出すシンプルな構造だった。log.php 内の処理が
REST API リクエストに対して 403 を返していたため、
REST API リクエスト時はロガーをスキップする条件を追加する。
<?php /** * Plugin Name: Access Logger */ // PATH_INFO 方式の REST リクエスト対応(wordpress-rs / iOS アプリ用) add_action('parse_request', function(&$wp) { $path_info = $_SERVER['PATH_INFO'] ?? ''; if ($path_info && strlen($path_info) > 1 && isset($wp->query_vars['rest_route']) && $wp->query_vars['rest_route'] === '/') { $wp->query_vars['rest_route'] = $path_info; } }, 1); // priority 1 = rest_api_loaded (priority 10) より前に実行 add_action('template_redirect', function() { my_access_log(); }); function my_access_log() { // REST API リクエストはスキップ(WordPress 定数チェック) if (defined('REST_REQUEST') && REST_REQUEST) { return; } // フォールバック:URL パスで直接チェック if (strpos($_SERVER['REQUEST_URI'], '/wp-json/') !== false) { return; } // PATH_INFO 方式の REST リクエストもスキップ if (!empty($_SERVER['PATH_INFO'])) { return; } $templates_path = "/var/www/html/"; $cnpath = $templates_path . "templates/"; $lgpath = $cnpath . "log.php"; include_once $lgpath; if (function_exists('uah_logging')): $uqid = uah_logging(); endif; }
REST_REQUEST 定数を true に設定する。
これが最も確実なチェック方法だが、parse_request フックより後に定義されるため、
フォールバックとして URI パターンチェックも併用している。
// 04 — FIX 2
修正②:.htaccess に wp-json リライトルールを追加
mu-plugins を元に戻して Android アプリから再登録を試みたが「アプリケーションパスワード非対応」エラーが継続した。
改めて /wp-json/ と index.php?rest_route=/ を比較確認する。
# /wp-json/ 経由 → ボディが空 $ curl -s https://example.com/wp-json/ | python3 -m json.tool | grep -A 5 "authentication" Expecting value: line 1 column 1 (char 0) # index.php?rest_route=/ 経由 → JSON が返る $ curl -s "https://example.com/index.php?rest_route=/" | python3 -m json.tool | grep -A 5 "authentication" "authentication": { "application-passwords": { "endpoints": { "authorization": "https://example.com/wp-admin/authorize-application.php"
/wp-json/ へのアクセスが空レスポンスを返す原因は、
.htaccess の wp-json リライトルールが欠落していたためだ。
パーマリンク設定の再保存などで .htaccess が上書きされた際に
wp-json のルールが消えることがある。
# BEGIN WordPress <IfModule mod_rewrite.c> RewriteEngine On RewriteBase / RewriteRule ^wp-json/(.*) index.php?rest_route=/$1 [QSA,L] ← この行を追加 RewriteRule ^index\.php$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.php [L] </IfModule> # END WordPress
.htaccess が自動再生成される。
その際 wp-json のリライトルールが消える場合があるため、保存後は必ず確認すること。
// 05 — FIX 3
修正③:iOS アプリ固有の PATH_INFO 問題
Android アプリは正常に動作するようになったが、iOS アプリ(iPad)では「サイトの情報が取得できません」エラーが継続した。 アクセスログを確認すると原因が明確になった。
"POST /index.php?rest_route=/wp/v2/users/me/application-passwords..." 201 988 "POST /xmlrpc.php" 200 777 "GET /index.php/wp/v2/users/me?rest_route=/&context=edit" 200 600113 "GET /index.php/wp/v2/settings?rest_route=/&context=view" 200 599739 "GET /index.php/jetpack/v4/site?rest_route=/" 200 600113
▸ 問題の構造
iOS アプリが使用する wordpress-rs ライブラリは、REST エンドポイントを
PATH_INFO 方式でリクエストする。
# Android(クエリパラメータ方式) GET /index.php?rest_route=/wp/v2/users/me&context=edit ^^^^^^^^^^^^^^^^^^^^^^^^^^^ WordPress が正しく読み取る # iOS / wordpress-rs(PATH_INFO 方式) GET /index.php/wp/v2/users/me?rest_route=/&context=edit ^^^^^^^^^^^^^^^ ^^^^^^^^^^^ PATH_INFO クエリパラメータ(rest_route=/) # WordPress は PATH_INFO を無視して rest_route=/ のみ読む # → ルートスキーマ全体(≈600KB)が返る
| Android アプリ | iOS アプリ(wordpress-rs) | |
|---|---|---|
| リクエスト方式 | クエリパラメータ | PATH_INFO |
| エンドポイント例 | ?rest_route=/wp/v2/users/me |
/index.php/wp/v2/users/me?rest_route=/ |
| WordPress の解釈 | ✓ 正しく解釈 | ✗ rest_route=/ のみ読む |
| レスポンス | ユーザー情報 JSON | ルートスキーマ 600KB |
▸ 対策:parse_request で PATH_INFO を優先
parse_request フック(priority 1)で PATH_INFO が存在する場合に
rest_route クエリ変数を上書きする。
この処理は修正①の access-logger.php に同時に組み込み済みである。
# PATH_INFO 方式で直接テスト(未認証 → 401 が正常) $ curl -s "https://example.com/index.php/wp/v2/users/me?rest_route=/&context=edit" \ | python3 -m json.tool | head -10 { "code": "rest_not_logged_in", "message": "現在ログインしていません。", "data": { "status": 401 } } # 修正前:ルートスキーマ 600KB が返っていた # 修正後:正しいエンドポイントに到達(401 = 認証待ち = 正常)
401 rest_not_logged_in は正常な動作。iOS アプリはアプリケーションパスワードを
Authorization ヘッダーに付けてリクエストするため、認証が通って 200 が返る。
// 06 — SUMMARY
修正の全体像
| # | 問題 | 原因 | 対策 |
|---|---|---|---|
| ① | REST API が 403 | mu-plugins の access-logger.php が include する log.php が遮断 | REST_REQUEST / PATH_INFO をスキップする条件を追加 |
| ② | アプリが「パスワード非対応」エラー | .htaccess の wp-json リライトルール欠落 | ^wp-json/(.*) → index.php?rest_route=/$1 を追記 |
| ③ | iOS アプリのみ情報取得エラー | wordpress-rs が PATH_INFO 方式でリクエスト、WordPress が無視 | parse_request で PATH_INFO を rest_route に反映 |
// LESSONS LEARNED
- mu-plugins は plugins フォルダの無効化でも常に実行される。REST API への影響を必ず考慮すること。
- PHPSESSID の有無でApache遮断か PHP内部エラーかを即座に切り分けられる。
- レスポンスサイズが 600KB なら「誤ったエンドポイント」のサイン。中身より先にサイズを見る。
- Android(クエリパラメータ)と iOS / wordpress-rs(PATH_INFO)でリクエスト方式が異なる。
- パーマリンク設定を保存し直すと wp-json リライトルールが消えることがある。
- mu-plugins に REST API に関係する処理を加えるときは、最初から
REST_REQUESTチェックを入れる習慣を。