2026年6月16日

2026年6月16日

WordPress REST APIが動かないときの対処法|404・401・403・rest_no_routeの原因別

WordPressのREST API(/wp-json/)が動かないときは、返ってくるステータスコードとエラーコードで原因の場所がおおよそ分かります。この記事では、まず症状を切り分け、次に「URLが届かない」「認証情報が届かない」「コードやプラグインが止めている」「エンドポイントが登録されていない」の順に直し方を説明します。

まず症状を切り分ける

example.com は自分のドメインに置き換えます。-i を付けると、ステータスコードとヘッダーも表示されます。

# REST API のインデックス
curl -i https://example.com/wp-json/

# パーマリンクの設定に関係なく使える形式
curl -i "https://example.com/?rest_route=/"

# 投稿を1件取得
curl -i "https://example.com/wp-json/wp/v2/posts?per_page=1"
結果 考えられる原因 見る章
/wp-json/ が404(WordPressではなくWebサーバーのエラーページ)、?rest_route=/ は動く パーマリンクが「基本」、またはWebサーバーのリライト設定がない URLが届かない
JSONで rest_no_route が返る そのURLとメソッドに一致するエンドポイントが登録されていない エンドポイントが登録されていない
認証付きのリクエストだけ401(rest_not_logged_in など) Authorizationヘッダーが PHP に届いていない、認証情報の誤り 認証情報が届かない
403で、レスポンスがJSONではなくHTML WAFやセキュリティ機能が止めている コードやプラグインが止めている
403や401で、JSONのエラーコードが rest_ で始まる WordPress内のコードやプラグインが止めている、権限が足りない コードやプラグインが止めている

WP-CLIが使える場合は、登録されているルートの一覧も確認できます。

wp eval 'foreach ( array_keys( rest_get_server()->get_routes() ) as $route ) { echo $route, PHP_EOL; }' | grep '/wp/v2/'

URLが届かない(/wp-json/ が404)

/wp-json/ というURLは、WordPressのリライト(URLの書き換え)で処理されます。

  • パーマリンクが「基本」の場合:/wp-json/ は使えませんが、REST API自体は ?rest_route=/wp/v2/posts の形式で動きます。WordPress本体やブロックエディターは、この形式を自動で使います。外部のプログラムから /wp-json/ で呼びたい場合は、「設定」→「パーマリンク」で「投稿名」などに変更します。
  • パーマリンクを変えたのに404になる場合:「設定」→「パーマリンク」を開いて、何も変えずに「変更を保存」を押すと、リライトのルールが作り直されます。WP-CLIなら wp rewrite flush です。

それでも直らない場合は、Webサーバーの設定を確認します。

  • Apache:WordPressのルートの .htaccess に # BEGIN WordPress から # END WordPress までのブロックがあるか、サーバー側で .htaccess が有効か(AllowOverride の設定)、mod_rewrite が有効かを確認します。
  • Nginx:.htaccess は使われません。サイトの設定に次の try_files が必要です。
location / {
    try_files $uri $uri/ /index.php?$args;
}

認証情報が届かない(認証付きで401)

アプリケーションパスワードで認証する

外部のプログラムからREST APIで投稿などを操作するときは、アプリケーションパスワード(WordPress 5.6以降の標準機能)を使うのが手軽です。「ユーザー」→「プロフィール」の「アプリケーションパスワード」の欄で、名前を入れて新しいパスワードを発行します。

# ログインできるか確認(成功すると自分のユーザー情報が返る)
curl -i -u "admin:xxxx xxxx xxxx xxxx xxxx xxxx" \
  https://example.com/wp-json/wp/v2/users/me

# 下書きを1件作る
curl -s -u "admin:xxxx xxxx xxxx xxxx xxxx xxxx" \
  -X POST https://example.com/wp-json/wp/v2/posts \
  -H "Content-Type: application/json" \
  -d '{"title":"テスト投稿","status":"draft"}'

admin はユーザー名、xxxx … は発行されたアプリケーションパスワード(ログイン用のパスワードではありません)です。アプリケーションパスワードは、サイトがHTTPSで動いている場合(または環境の種類が local の場合)にだけ使えます。プロフィール画面に欄が表示されないときは、HTTPSになっているか、セキュリティ系のプラグインが機能を無効にしていないかを確認してください。

Apache:Authorizationヘッダーが PHP に届かない

正しいパスワードなのに401になる場合、Webサーバーが Authorization ヘッダーを PHP に渡していないことがあります。PHPをCGIやFastCGIで動かしているApacheで起きやすい問題です。

WordPressが作る .htaccess の # BEGIN WordPress のブロックには、この対策として次の行が含まれています。.htaccess を手で書き換えて消していないか確認してください。

RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

それでも届かない場合は、# BEGIN WordPress より前に、次のどちらかを追加します。

# Apache 2.4.13 以降(AllowOverride AuthConfig が必要)
CGIPassAuth On

# または mod_setenvif を使う方法
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1

WordPressは、HTTP_AUTHORIZATION(または REDIRECT_HTTP_AUTHORIZATION)にBasic認証の値が入っていれば、そこからユーザー名とパスワードを取り出して認証に使います。

Nginx:fastcgi のパラメーターを確認する

NginxはPHP-FPMに、リクエストのヘッダーを HTTP_ で始まるパラメーターとして渡すのが既定(fastcgi_pass_request_headers on)なので、通常は HTTP_AUTHORIZATION も届きます。届いていない場合は、PHPの location に次の1行を追加して、明示的に渡します。

location ~ \.php$ {
    # ... 既存の fastcgi の設定 ...
    fastcgi_param HTTP_AUTHORIZATION $http_authorization;
}

Nginxの手前にさらにリバースプロキシやCDNがある場合は、そこで Authorization ヘッダーが削られていないかも確認します。設定を変えたら sudo nginx -t で構文を確認し、sudo systemctl reload nginx で反映します。

ヘッダーが届いているかを確かめる

一時的に、次のファイルを check-auth.php としてWordPressのルートに置き、認証付きでアクセスすると、PHPから見えているかが分かります。確認が終わったらすぐに削除してください。

<?php
// 確認が終わったら削除する(値そのものは表示しない)
header( 'Content-Type: text/plain' );
echo 'HTTP_AUTHORIZATION: ', isset( $_SERVER['HTTP_AUTHORIZATION'] ) ? 'present' : 'missing', "\n";
echo 'REDIRECT_HTTP_AUTHORIZATION: ', isset( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ) ? 'present' : 'missing', "\n";
echo 'PHP_AUTH_USER: ', isset( $_SERVER['PHP_AUTH_USER'] ) ? 'present' : 'missing', "\n";
curl -u "admin:xxxx xxxx xxxx xxxx xxxx xxxx" https://example.com/check-auth.php

すべて missing なら、Webサーバーの設定でヘッダーが落ちています。

コードやプラグインが止めている(401・403)

REST APIを無効化するコードを探す

テーマやプラグインに、次のようにすべてのリクエストにエラーを返すコードがあると、REST APIは使えません。ブロックエディターもREST APIを使うため、投稿の保存ができなくなります。

add_filter( 'rest_authentication_errors', fn() => new WP_Error( 'rest_disabled', 'REST API disabled' ) );

WordPressのファイル内を検索して、rest_authentication_errors や rest_enabled(古いフィルター)を使っている箇所を確認します。

grep -rn "rest_authentication_errors\|rest_enabled" wp-content/themes wp-content/plugins wp-content/mu-plugins

セキュリティ系のプラグインには、「REST APIを制限する」「ログインしていない人のREST APIを無効にする」といった設定があることがあります。エラーの code に出ている文字列でプラグインのファイルを検索すると、どこで止めているかを探しやすくなります。レスポンスがJSONではなくHTMLの403ページなら、WordPressより手前のWAF(レンタルサーバーのWAFやCloudflareなど)がブロックしているので、そのログを確認します。

ログインしていない人のアクセスを制限する正しい書き方

REST APIを制限したい場合は、全部を止めるのではなく、ログインしていない人だけを止めます。開発者向けハンドブック(REST API Handbook)のFAQで紹介されている形は次のとおりです。

add_filter( 'rest_authentication_errors', function ( $result ) {
	// ほかの処理がすでに認証の成否を判定していれば、その結果をそのまま返す
	if ( true === $result || is_wp_error( $result ) ) {
		return $result;
	}
	if ( ! is_user_logged_in() ) {
		return new WP_Error( 'rest_not_logged_in', 'ログインが必要です。', array( 'status' => 401 ) );
	}
	return $result;
} );

古い記事には、rest_get_url_prefix() が wp-json で始まらない場合だけ止める、という例があります。rest_get_url_prefix() は(フィルターで変えていなければ)常に wp-json を返すので、この条件は成り立たず、制限は何も効きません。

一部のエンドポイント(たとえば問い合わせフォームのプラグインが使うもの)だけはログインしていない人にも許可したい場合は、リクエストのルートを受け取れる rest_pre_dispatch フィルターで判定するほうが書きやすくなります。

add_filter( 'rest_pre_dispatch', function ( $result, $server, $request ) {
	if ( ! empty( $result ) || is_user_logged_in() ) {
		return $result;
	}
	// ログインしていなくても使わせるルートの先頭部分
	$allowed = array( '/contact-form-7/v1/' );
	foreach ( $allowed as $prefix ) {
		if ( str_starts_with( $request->get_route(), $prefix ) ) {
			return $result;
		}
	}
	return new WP_Error( 'rest_not_logged_in', 'ログインが必要です。', array( 'status' => 401 ) );
}, 10, 3 );

$allowed の値は例です。実際に使うプラグインのルートは、上のWP-CLIのコマンドで表示したルート一覧で確認してください。str_starts_with() はPHP 8.0以降の関数です(2026年10月時点のWordPressの推奨環境はPHP 8.3以降)。

エンドポイントが登録されていない(rest_no_route)

カスタム投稿タイプをREST APIに出す

カスタム投稿タイプは、register_post_type() で show_in_rest を true にしないと、REST APIにもブロックエディターにも出てきません。

add_action( 'init', function () {
	register_post_type( 'product', array(
		'label'        => '商品',
		'public'       => true,
		'show_in_rest' => true,       // REST API とブロックエディターに対応させる
		'rest_base'    => 'products', // /wp-json/wp/v2/products になる
		'supports'     => array( 'title', 'editor', 'thumbnail', 'custom-fields' ),
	) );

	// カスタムフィールドを REST API の meta に出す(投稿タイプに custom-fields のサポートが必要)
	register_post_meta( 'product', 'price', array(
		'show_in_rest'  => true,
		'single'        => true,
		'type'          => 'number',
		'auth_callback' => function () {
			return current_user_can( 'edit_posts' );
		},
	) );
} );

プラグインが登録した投稿タイプなら、そのプラグインの設定に「REST APIで表示」のような項目がないか確認します。登録後は curl https://example.com/wp-json/wp/v2/products で一覧が返るかを確かめます。

独自のエンドポイントを作る場合

自分でエンドポイントを作るときは、rest_api_init アクションの中で register_rest_route() を呼びます。permission_callback は必須で、省略するとWordPressが注意(_doing_it_wrong)を出します。

add_action( 'rest_api_init', function () {
	register_rest_route( 'myplugin/v1', '/items/(?P<id>\d+)', array(
		'methods'             => WP_REST_Server::READABLE, // GET
		'callback'            => 'myplugin_rest_get_item',
		'permission_callback' => function () {
			return current_user_can( 'read' );
		},
		'args'                => array(
			'id' => array(
				'validate_callback' => function ( $value ) {
					return is_numeric( $value );
				},
				'sanitize_callback' => 'absint',
				'required'          => true,
			),
		),
	) );
} );

function myplugin_rest_get_item( WP_REST_Request $request ) {
	$post = get_post( $request->get_param( 'id' ) );
	if ( ! $post instanceof WP_Post ) {
		return new WP_Error( 'myplugin_not_found', '見つかりません。', array( 'status' => 404 ) );
	}
	return rest_ensure_response( array(
		'id'    => $post->ID,
		'title' => get_the_title( $post ),
	) );
}

rest_no_route になる場合は、URLの名前空間とルート(myplugin/v1 と /items/123)、HTTPメソッド(GETかPOSTか)が登録内容と一致しているかを確認します。誰でも読める公開データを返すときだけ、permission_callback に '__return_true' を指定します。

まとめ

  • まず /wp-json/ と ?rest_route=/ の両方を curl -i で試し、ステータスコードとエラーコードで原因を切り分ける
  • パーマリンクが「基本」でもREST APIは ?rest_route= で動く。/wp-json/ が404ならパーマリンクの保存し直しと、Apacheの .htaccess・Nginxの try_files を確認する
  • 認証付きで401なら、アプリケーションパスワード(HTTPSが必要)と、Authorizationヘッダーが PHP に届いているかを確認する
  • REST APIの制限は、ログインしていない人だけを止める書き方にする。rest_get_url_prefix() で判定する例は効かない
  • カスタム投稿タイプは show_in_rest、独自エンドポイントは register_rest_route() と permission_callback を確認する

関連記事:

お気軽にご相談ください

お見積りへ お問い合わせへ
▲