WordPress の .htaccess で、開いてよい .php だけを通し、探りを fail2ban で遮断する(Web サイト担当者向け)
篠原 隆司
アフィリエイト広告を利用しています
このページの内容が役に立ったら X (旧twitter) でフォローして頂けると励みになります
挨拶や報告は無しで大丈夫です
さくらのVPSのCentOS Stream 9で、WordPress のサイトに届く探りを、.htaccess で拒んで fail2ban で遮断した覚書です。
その前の記事と前の記事で、サーバー管理者が、サーバー全体とサイトごとの設定をしました。今回は、Web サイトの担当者の立場で、サイトの .htaccess に、開いてよい .php だけを通して目印を付ける設定と、許可する拡張子を外す設定を書きます。
記事の中の IP アドレス・ドメインなどは、実際のものから書き換えています。
なお本番環境で利用される場合はここにある内容だけを鵜呑みにせずセキュリティ専門家に相談されることをお勧めします。
環境
| 実施日 | 2026-11-09 |
| サーバ | さくらのVPS 2G
|
| OS | CentOS Stream 9 |
cat /etc/redhat-release
CentOS Stream release 9
経緯と解決策
経緯について
WordPress のサイトには、WordPress には無い .php を次々に試すアクセスが届きます。プラグインの穴を狙った URL や、置かれているかもしれない管理用のスクリプトなどです。.env などはサーバー全体の設定で、wp-config.php や xmlrpc.php などはサイトごとの設定(サーバー管理者が書く VirtualHost)で拒めます。ただ、どの .php を外から開くかはサイトによって違うので、それらの設定では拒めません。
何もしなければ、無い .php へのアクセスも WordPress まで届き、404 を返すためだけに PHP と WordPress が動きます。
解決案を考える
WordPress で、外から .php の URL として開くものは限られます。そこで、開いてよい .php をホワイトリストにして、それ以外を .htaccess で拒みます。拒むのは Apache の段なので、PHP も WordPress も動きません。
拒むときに、fail2ban 向けの目印(中:f2b_mid)を付けます。目印の付いたアクセスは、サーバー管理者が用意した fail2ban 向けのファイルに書かれ、fail2ban が数えて遮断します。
サイトの担当者がするのは、目印を付けるところまでです。何回で遮断するかは、サーバー管理者が決めています。
あわせて、サーバー全体で許可している拡張子のうち、このサイトでは出さないものがあれば、.htaccess で拒みます。
ホワイトリストに残る wp-login.php と管理画面(/wp-admin/)は、Basic 認証や IP アドレスの制限で、別に守っておきます。
事前確認
設定を書く前に、サーバー管理者の設定が入っているかと、サブディレクトリの .htaccess を確かめます。
例のサイトは example.com で、ファイルは /home/example_com/public_html/ にあるとします。
サーバー管理者の設定が入っているか
サーバー全体の設定が入っていれば /.git/config が、サイトごとの設定が入っていれば wp-config.php が、403 で拒まれます。
サーバーに SSH でログインして、サーバーから打ちます。サーバー自身からのアクセスは、fail2ban に数えられません。自分の PC から打つと、/.git/config は強に当たり、1 回で 1 日遮断されます。
歓迎しない bot を拒んでいるサーバーでは、curl の名乗りも拒まれて 403 になることがあるので、-A でブラウザに似た名乗りを付けます。
UA='Mozilla/5.0 (probe test)'
curl -s -A "$UA" -o /dev/null -w '%{http_code}\n' https://example.com/.git/config
curl -s -A "$UA" -o /dev/null -w '%{http_code}\n' https://example.com/wp-config.php
どちらも 403 と出れば、済んでいます。目印の名前(強:f2b_strong、中:f2b_mid、弱:f2b_weak)は、サーバー管理者に確かめておきます。
403 にならないときは、サーバー管理者の設定が入っていません。サーバー管理者に、前の 2 本の記事の設定を入れてもらいます。入れられないサーバーで、この記事の .htaccess だけを入れると、拒むだけになり、fail2ban には数えられません。
ほかの .htaccess
サブディレクトリに、RewriteEngine On を書いた .htaccess があると、そのディレクトリの下には、ドキュメントルートの .htaccess の書き換えが効きません。どこにあるかを見ておきます。
find /home/example_com/public_html -name .htaccess -not -path '*/wp-content/*' | xargs grep -il 'RewriteEngine on'
ドキュメントルートのほかに出てきたディレクトリ(サブディレクトリに置いた別の WordPress など)は、この記事の設定の外になります(補足説明の「サブディレクトリの .htaccess」)。
今回行うこと
はじめに、このサイトで外から開いてよい .php を決めます。
そのあと、.htaccess に、それ以外を拒んで目印を付ける設定と、許可する拡張子を外す設定を書きます。
最後に、試しのアクセスで、拒まれることと、サイトと管理画面が今までどおり動くことを確かめます。
ホワイトリストを決める
WordPress の本体で、外から開く .php は次のとおりです。これを基本にします。
| URL | 何か |
|---|---|
/index.php | 入口。固定リンクの URL も中でここに回る |
/wp-login.php | ログイン |
/wp-cron.php | 予約投稿などの定期処理 |
/wp-comments-post.php | コメントの投稿 |
/wp-admin/ の下 | 管理画面と admin-ajax.php |
/wp-includes/js/tinymce/wp-tinymce.php | 旧エディター |
/wp-trackback.php・/wp-signup.php・/wp-activate.php・/wp-links-opml.php・/wp-mail.php | トラックバック・会員登録・メールでの投稿など。使っていなくても、WordPress が自分で返すので通しておく |
/xmlrpc.php | 使うサイトだけ(Jetpack など)。サーバー管理者に、サイトごとの設定で通してもらったうえで足す |
WordPress の外に置いた PHP(問い合わせのフォームなど)や、プラグインが外から開かせる .php があれば、このサイト独自のものとして足します(探し方は、補足説明の「開いてよい .php の探し方」)。
ホワイトリストに無い .php は、中で拒みます(強・中・弱の決め方は、サーバー全体の設定の記事の「強・中・弱を決める」)。
.htaccess に書く
控えを取ってから、ドキュメントルートの .htaccess を開きます。
cp -p /home/example_com/public_html/.htaccess /home/example_com/public_html/.htaccess.20261109
vi /home/example_com/public_html/.htaccess
# BEGIN WordPress の行より前に、次を足します。
# BEGIN fail2ban
# 拒むときに、fail2ban 向けの目印を付ける(中:f2b_mid)
<IfModule mod_rewrite.c>
RewriteEngine On
# 中:ホワイトリストに無い .php
RewriteCond %{REQUEST_URI} \.php$
RewriteCond %{REQUEST_URI} !^/(?:index|wp-login|wp-cron|wp-comments-post|wp-trackback|wp-signup|wp-activate|wp-links-opml|wp-mail)\.php$
RewriteCond %{REQUEST_URI} !^/wp-admin/
RewriteCond %{REQUEST_URI} !^/wp-includes/js/tinymce/wp-tinymce\.php$
# サイト独自のもの
RewriteCond %{REQUEST_URI} !^/tools/form\.php$
# xmlrpc.php を使うサイトだけ(サイトごとの設定で通してもらったうえで、行頭の # を消す)
#RewriteCond %{REQUEST_URI} !^/xmlrpc\.php$
RewriteRule ^ - [E=f2b_mid:1,F]
# サーバー全体で許可している拡張子のうち、このサイトでは出さないもの(目印は付けない)
#RewriteRule "\.(?:mp3|mp4|webm)$" - [NC,F]
</IfModule>
# END fail2ban
- [E=f2b_mid:1,F]:目印
f2b_midを付けて、403 で拒みます。目印が付いたアクセスは、サーバー管理者が用意した fail2ban 向けのファイルに書かれます - RewriteCond の ! の行:ホワイトリストです。サイト独自のものは、
/tools/form.phpと同じ形で 1 行ずつ足します - [NC,F]:許可する拡張子を外すときは、目印を付けずに 403 で拒みます(数えません)。外したい拡張子を
|で区切って並べ、行頭の#を消します。NCで、大文字と小文字を区別しません - 置く場所:
# BEGIN WordPressより前に置きます。WordPress の書き換えより先に判断するためです。# BEGIN WordPressから# END WordPressの間は、WordPress が書き換えることがあるので、その中には書きません。すでに自分で書いた書き換えのうち、古い.phpの URL を転送するものがあれば、そのあとに置きます(先に置くと、転送する前に拒まれます)。.phpの URL にかからない書き換えとは、どちらが先でもかまいません - 書かないもの:
.envなどはサーバー全体の設定で、wp-config.php・xmlrpc.php・アップロードの置き場の.phpなどはサイトごとの設定で拒んでいるので、ここには書きません。xmlrpc.phpを使うサイトでは、サーバー管理者にサイトごとの設定で通してもらい、ホワイトリストにも足します。許可する拡張子を足すことも、.htaccessではできません(サーバー管理者に頼みます)
.htaccess は保存した時点で効きます。Apache の再起動は要りません。書き損じがあるとサイト全体が 500 になるので、保存したらすぐにトップページを開いて確かめます。500 になったら、控えに戻します。
動きを確かめる
サーバーから、試しのアクセスを送ります。
UA='Mozilla/5.0 (probe test)'
for p in /f2b-test.php /wp-login.php /wp-cron.php /; do
printf '%-16s ' "$p"; curl -s -A "$UA" -o /dev/null -w '%{http_code}\n' "https://example.com$p"
done
/f2b-test.php 403
/wp-login.php 401
/wp-cron.php 200
/ 200
ホワイトリストに無い .php は 403 で拒まれ、WordPress の本体の URL は、これまでどおり返ります。/wp-login.php の 401 は、ログインに Basic 認証をかけている場合です(かけていなければ 200)。
fail2ban 向けのファイルに書かれたかは、サーバー管理者に見てもらいます(root で打ちます)。
tail -n 1 /var/log/httpd/fail2ban-mid_log
192.0.2.10 [09/Nov/2026:01:05:12 +0900] example.com "GET /f2b-test.php HTTP/1.1" 403
/f2b-test.php が、中のファイルに書かれています。192.0.2.10 はサーバー自身なので、fail2ban は数えず、遮断もしません。
管理画面への道が塞がっていないかも見ます。
UA='Mozilla/5.0 (probe test)'
curl -s -A "$UA" -o /dev/null -w '%{http_code} %{redirect_url}\n' https://example.com/wp-admin/
curl -s -A "$UA" -o /dev/null -w '%{http_code}\n' https://example.com/wp-admin/admin-ajax.php
302 https://example.com/wp-login.php?redirect_to=https%3A%2F%2Fexample.com%2Fwp-admin%2F&reauth=1
400
/wp-admin/ はログイン画面に回され、admin-ajax.php は WordPress が 400(何をするかの指定が無い)を返しています。どちらも WordPress まで届いているので、塞がっていません。
あわせて、ブラウザで、トップページ、記事のページ、管理画面へのログインとダッシュボードが、今までどおり開くかを見ます。開かないものがあれば、その URL をホワイトリストに足します。
遮断を解く
遮断を解くのは、サーバー管理者です。解き方は、サーバー全体の設定の記事の「遮断を解く」です。
サイトの利用者が遮断されたと分かったら、その人が開いた URL が、ホワイトリストから漏れていないかを確かめます。
元に戻す
.htaccess から、# BEGIN fail2ban から # END fail2ban までを消します。控えに戻してもかまいませんが、控えを取ったあとに WordPress やプラグインが .htaccess を書き換えていれば、その分も戻ってしまうので気をつけます。
cp -p /home/example_com/public_html/.htaccess.20261109 /home/example_com/public_html/.htaccess
補足説明
開いてよい .php の探し方
サイト独自の .php が分からないときは、アクセスログで、200 を返した .php の URL を数えると見つかります(ログの置き場は、サーバー管理者に確かめます)。
awk '$9 == 200 { p = $7; sub(/\?.*/, "", p); if (p ~ /\.php$/) print p }' /home/example_com/logs/*access_log | sort | uniq -c | sort -rn | head -n 30
2016 /wp-cron.php
742 /wp-admin/admin-ajax.php
38 /wp-admin/post.php
21 /wp-admin/load-styles.php
15 /wp-admin/admin.php
12 /wp-admin/load-scripts.php
9 /wp-login.php
7 /blog/wp-cron.php
6 /wp-admin/options-general.php
3 /blog/wp-admin/admin-ajax.php
2 /tools/form.php
1 /wp-includes/js/tinymce/wp-tinymce.php
この例では、WordPress の本体のもののほかに、/blog/ の下(サブディレクトリに置いた別の WordPress)と、/tools/form.php(WordPress の外に置いた PHP)があります。/tools/form.php をホワイトリストに足し、/blog/ は自分の .htaccess を持っていれば、そちらに書きます。
しばらく使われていない .php は、ログに出てきません。月に 1 回しか動かない処理などは、漏れていないかを別に確かめます。
xmlrpc.php を WordPress の側でも止める
xmlrpc.php は、外から投稿・操作するための古い仕組みです。今は REST API とアプリケーションパスワードに移っていて、使わないサイトがほとんどです。
Apache で拒んでいても、WordPress の側でも止めておくと、Apache で拒めないサーバー(レンタルサーバーなど)に移したときの守りになります。
<?php
// xmlrpc.php の、認証が要る操作(投稿など)を止める
add_filter( 'xmlrpc_enabled', '__return_false' );
?>
- 止まるもの:投稿など、ユーザー名とパスワードが要る操作です。
xmlrpc.phpを使ったパスワードの総当たりも効かなくなります - 止まらないもの:ピンバックのような、認証の要らない操作です。名前に反して、
xmlrpc.phpそのものを止めるものではありません(WordPress のソースのコメントにも、そう書かれています)。細かく止めるときは、xmlrpc_methodsのフィルターを使います - 置き場:テーマの
functions.phpに書きます。配られているテーマは、更新するとfunctions.phpが上書きされるので、子テーマか、自作の小さなプラグインに書きます - Jetpack など、xmlrpc.php を使うサイト:このフィルターは入れません。認証が要る操作が止まり、動かなくなります
先に拒まれているものには、目印が付かない
.htaccess の書き換えは、Apache の中では遅い順番で動きます。サーバー管理者の設定(サーバー全体の設定や、サイトごとの VirtualHost)や、Require で先に拒まれたアクセスは、.htaccess まで来ません。
- サーバー管理者の設定で拒んだもの:数えるかどうかは、そちらの設定で決まっています(許可する拡張子で拒んだものなどは数えません)。
.htaccessに同じ規則があっても、届かないだけで害はありません - Require で拒んだもの(
<Files>や<FilesMatch>のRequire all deniedなど):拒めてはいますが、目印が付かないので、fail2ban には数えられません。数えたいときは、その設定を書いた人(サーバー管理者やプラグイン)と、書き換えに任せてよいかを相談します
サブディレクトリの .htaccess
mod_rewrite の書き換えは、サブディレクトリの .htaccess に RewriteEngine On があると、そのディレクトリの下では、親の .htaccess の書き換えを引き継ぎません。サブディレクトリに別の WordPress を置いているときなどは、そのディレクトリの .htaccess にも、同じまとまりを書きます。ホワイトリストの URL は、そのディレクトリから始まる形(例:!^/blog/(?:index|wp-login|…)\.php$)に直します。
拡張子の無い URL は扱わない
/login や /admin のような拡張子の無い URL は、WordPress の固定リンクと見分けが付かないので、この記事では扱いません。無いページなら、WordPress が 404 を返します。
WordPress のプラグインとの違い
WordPress のセキュリティのプラグインにも、404 が続く接続元を止める機能があるものがあります。プラグインは WordPress が動いてから判断するので、止めるまでのアクセスにも PHP と WordPress が動きます。この記事のやり方は、Apache の段で拒み、遮断したあとは firewalld で止めるので、PHP も WordPress も動きません。
PHP 以外のサイト
Ruby や Python などのアプリを、Apache からプロキシで渡しているサイトでは、.htaccess が効きません。考え方は同じで、そのサイトで開いてよい URL を決め、それ以外に目印を付けて拒みます。書く場所は、サーバー管理者と相談して、そのサイトの VirtualHost にします(サイトごとの設定の記事)。
まとめ
- WordPress で外から開く
.phpは限られるので、ホワイトリストにして、それ以外を.htaccessで拒む - 拒むときに、fail2ban 向けの目印(中)を付ける。何回で遮断するかは、サーバー管理者が決める
- ホワイトリストは WordPress の本体のものを基本にし、サイト独自のものを足す
- 許可する拡張子は、
.htaccessで外せる(目印は付けない)。足すのはサーバー管理者に頼む Requireで先に拒んでいるものや、自分の.htaccessを持つサブディレクトリには、この設定が効かない
.php は、サイトの機能でも 403 になり、何度か開くと遮断されます。プラグインを足したときは、そのプラグインが外から開かせる .php が無いかを確かめてください。サーバー管理者が行う設定は、次の記事で扱っています。
このページの内容が役に立ったら X (旧twitter) でフォローして頂けると励みになります
本ページの内容は可能な限り正確な情報を提供するよう努めていますが、内容の正確性・最新性・安全性を保証するものではありません。本情報を利用して生じたいかなる損害についても、当方は一切の責任を負いません。実施にあたっては必ずご自身の判断と自己責任にてお願いいたします。