2026年5月20日

2026年5月20日

wp-config.phpへの外部アクセスをブロックする方法(Apache 2.4・Nginx・ログ監視)

wp-config.php には、データベースの接続情報や認証用キーが書かれています。PHP として実行されるファイルなので、通常はブラウザーで開いても中身は表示されず、空のページが返ります。それでもアクセス自体を拒否しておくのは、次のような場合に中身が漏れるのを防ぐためです。

  • サーバーの設定変更や障害で PHP が動かなくなり、.php ファイルがテキストとして返される
  • 編集時に作られた wp-config.php.bak・wp-config.php~・.wp-config.php.swp などのコピーが残っていて、PHP として実行されずにそのまま返される

この記事では、現状の確認、Apache 2.4 と Nginx での拒否設定、ドキュメントルートの外への移動、アクセスログの確認と fail2ban による遮断を順に説明します。設定例は Apache 2.4.67、Nginx(stable)、fail2ban 1.1.0 で動作を確認しています(2026年10月時点)。

現状を確認する

まず、サーバー上に wp-config のコピーが残っていないかを確かめます。WordPress のディレクトリ(例: /var/www/html)で実行します。

# wp-config で始まるファイルと、ドットで始まるエディターの一時ファイルを探す
ls -la /var/www/html | grep -i 'wp-config'

wp-config.php と wp-config-sample.php 以外のファイル(.bak、.old、~ で終わるもの、.swp など)があれば、中身を確認したうえでドキュメントルートの外に移すか削除します。

次に、外からどう見えるかを確かめます。example.com は自分のドメインに置き換えます。

for f in wp-config.php wp-config.php.bak wp-config.bak wp-config-sample.php readme.html license.txt .git/config .env; do
  printf '%-24s %s\n' "$f" "$(curl -s -o /dev/null -w '%{http_code}' "https://example.com/$f")"
done

設定前の wp-config.php は、PHP が実行されて 200(中身は空)になるのが普通です。設定後は 403 になります。存在しないファイルは WordPress の 404 ページが返るため、404 か 403 であれば問題ありません。200 が返ったファイルは中身を確認してください。

Apache 2.4 で拒否する(.htaccess)

WordPress のルートにある .htaccess の、# BEGIN WordPress の行より前に書きます。WordPress のブロックの中に書くと、パーマリンク設定の保存などで書き換えられます。

# wp-config.php と、そのバックアップ・コピー(wp-config.php.bak など)を拒否
<FilesMatch "^(wp-config(-sample)?\.php.*|wp-config\.(bak|old|orig|save|txt))$">
    Require all denied
</FilesMatch>

# バージョンが分かる同梱ファイルを拒否(任意)
<FilesMatch "^(readme\.html|license\.txt)$">
    Require all denied
</FilesMatch>

# .git や .env などのドットファイルを拒否(証明書発行で使う .well-known は除く)
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteRule "(^|/)\.(?!well-known/)" - [F]
</IfModule>

この設定で、wp-config.php・wp-config.php.bak・wp-config.php~・wp-config.bak・wp-config-sample.php・.wp-config.php.swp・.git/config・.env が 403 になり、.well-known/acme-challenge/ 以下(Let's Encrypt の HTTP 認証で使う)と、wp-config-guide のような wp-config で始まる投稿のパーマリンクは通常どおり表示されることを確認しています。

書き方の注意点です。

  • Order Deny,Allow と Deny from all は Apache 2.2 の書き方です。Apache 2.4 では互換モジュール(mod_access_compat)がある場合だけ動き、新しい Require と混ぜると意図しない結果になることがあります。2.4 では Require all denied を使います。
  • .htaccess の Require を使うには、そのディレクトリで AllowOverride に AuthConfig(または All)が、RewriteRule には FileInfo(または All)が許可されている必要があります。AllowOverride None の環境では .htaccess 自体が読まれないため、下記のようにバーチャルホストの設定に書きます。
  • .htaccess と .htpasswd は、Debian・Ubuntu の Apache では既定の設定(<FilesMatch "^\.ht">)で拒否されています。上の例のドットファイルの規則でも拒否されます。

バーチャルホストの設定に書く場合

.htaccess が使えない、または使いたくない場合は、サイトの設定ファイル(Debian・Ubuntu なら /etc/apache2/sites-available/ の下)に書きます。

<Directory /var/www/html>
    <FilesMatch "^(wp-config(-sample)?\.php.*|wp-config\.(bak|old|orig|save|txt))$">
        Require all denied
    </FilesMatch>
</Directory>

設定を変えたら、文法を確かめてから再読み込みします。

sudo apache2ctl configtest && sudo systemctl reload apache2

Nginx で拒否する

Nginx は .htaccess を読まないため、server ブロックに書きます。正規表現の location は書いた順に評価され、最初に一致したものが使われます。PHP を処理する location ~ \.php$ より前に書いてください。

server {
    # ... 既存の設定 ...

    # wp-config.php とそのバックアップ・コピーを拒否
    location ~* ^/(wp-config(-sample)?\.php.*|wp-config\.(bak|old|orig|save|txt))$ {
        deny all;
    }

    # バージョンが分かる同梱ファイルを拒否(任意)
    location ~* ^/(readme\.html|license\.txt)$ {
        deny all;
    }

    # ドットファイルを拒否(.well-known は除く)
    location ~ /\.(?!well-known/) {
        deny all;
    }

    # この後に location / や location ~ \.php$ を置く
}

ドットファイルの規則を location ~ /\. とすると、.well-known も拒否され、Let's Encrypt の HTTP 認証(HTTP-01)による証明書の発行・更新が失敗します。(?!well-known/) で除外しています。

WordPress をサブディレクトリ(例: /blog/)に置いている場合は、1つ目と2つ目の ^/ を / にして、どの階層でも一致するようにします。

sudo nginx -t && sudo systemctl reload nginx

wp-config.php をドキュメントルートの外に移す

WordPress は、wp-config.php が WordPress のディレクトリに無い場合、1つ上のディレクトリを探します(ただし、その上のディレクトリに wp-settings.php がある場合、つまり別の WordPress がある場合は読みません)。ドキュメントルートが /var/www/html で、その中に WordPress がある場合は次のようにします。

# 移動する(コピーやバックアップをドキュメントルートの中に残さない)
sudo mv /var/www/html/wp-config.php /var/www/wp-config.php

# サイトが表示されることを確かめる
curl -s -o /dev/null -w '%{http_code}\n' https://example.com/

注意点です。

  • バックアップを取るときは、/var/www/html/wp-config.php.bak のようにドキュメントルートの中に作らないでください。PHP として実行されないため、中身がそのまま読めてしまいます。ドキュメントルートの外かローカルの PC に保存します。
  • レンタルサーバーなどで1つ上のディレクトリが別のサイトのドキュメントルートになっている場合は、移しても外から読める場所に置くことになります。上のディレクトリが公開されていないことを確かめてから移します。
  • 移動の手順でサイトが表示されなくなった場合は、元の場所に戻します。

パーミッション

wp-config.php は、PHP を実行するユーザーが読めれば十分です。WordPress の公式ドキュメント(Hardening WordPress)では、ほかのユーザーから読まれないように 440 または 400 にする例が示されています。PHP の実行ユーザー(例: www-data)とファイルの所有者が違う場合は、所有グループを PHP の実行ユーザーのグループにして 440 や 640 にします。

ls -l /var/www/wp-config.php
sudo chown deploy:www-data /var/www/wp-config.php   # deploy は自分の環境の所有者に置き換える
sudo chmod 440 /var/www/wp-config.php

パーミッションを変えたら、サイトと管理画面が表示されることを確かめてください。読めない設定にすると、データベース接続エラーになります。

アクセスログを確認する

リアルタイムで見る

sudo tail -f /var/log/apache2/access.log | grep --line-buffered 'wp-config'

Nginx の場合は /var/log/nginx/access.log に置き換えます(パスはディストリビューションや設定によって違います)。

過去24時間の探索を送信元 IP ごとに数える

grep と awk '{print $1}' だけで集計すると、ファイルに残っているすべての期間のログを数えてしまいます。過去24時間に絞るには、各行の日時([30/Oct/2026:10:15:00 +0900] の形)を UNIX 時刻に直して比べます。次の例は GNU awk(gawk)の mktime() を使います。gawk が無い場合は sudo apt install gawk で入れます。

sudo gawk -v since="$(date -d '24 hours ago' +%s)" '
BEGIN { split("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec", m, " "); for (i = 1; i <= 12; i++) mon[m[i]] = i }
$7 ~ /^\/wp-config/ {
  split(substr($4, 2), t, /[\/:]/)
  if (mktime(t[3] " " mon[t[2]] " " t[1] " " t[4] " " t[5] " " t[6]) >= since) print $1
}' /var/log/apache2/access.log | sort | uniq -c | sort -rn | head -20

Apache・Nginx の標準のログ形式(combined)を前提に、7番目の項目(リクエストのパス)が /wp-config で始まる行だけを数えます。日時はサーバーのタイムゾーンで解釈されるため、ログがサーバーと同じタイムゾーンで記録されている必要があります。ログが日付ごとに切り替わる設定では、日付をまたぐ時間帯は前日のファイル(access.log.1)も対象にします。

fail2ban で探索してきた IP を遮断する

wp-config を探しに来る IP アドレスを、fail2ban でしばらく遮断できます。fail2ban の導入と基本は関連記事を参照してください。

フィルターを /etc/fail2ban/filter.d/wp-config-probe.conf に作ります。

[Definition]
failregex = ^<HOST> [^"]*"(?:GET|POST|HEAD) /wp-config[^ "]* HTTP/
ignoreregex =

fail2ban はログの日時の部分を自分で読み取ってから正規表現を当てるため、日時の部分を正規表現に含めない書き方にしています。実際のログに一致するかは fail2ban-regex で確かめられます。

sudo fail2ban-regex /var/log/apache2/access.log /etc/fail2ban/filter.d/wp-config-probe.conf

次に、/etc/fail2ban/jail.d/wp-config-probe.local を作ります。

[wp-config-probe]
enabled  = true
port     = http,https
filter   = wp-config-probe
logpath  = /var/log/apache2/access.log
maxretry = 1
findtime = 1h
bantime  = 1d
ignoreip = 127.0.0.1/8 ::1

maxretry = 1 なので、1回でも wp-config を要求した IP は1日遮断されます。自分の IP から curl で確認すると自分も遮断されるため、確認に使う IP は ignoreip に足しておきます。Cloudflare などのプロキシ経由の場合、ログの送信元がプロキシの IP になるため、この設定のままでは使えません。

sudo fail2ban-client -t && sudo systemctl reload fail2ban
sudo fail2ban-client status wp-config-probe

まとめ

  • wp-config.php は通常は中身が表示されないが、PHP が止まったときや、.bak などのコピーが残っているときに漏れる。コピーが残っていないかを最初に確認する
  • Apache 2.4 では .htaccess(# BEGIN WordPress より前)かバーチャルホストの設定に Require all denied を書く。2.2 の Order・Deny は使わない
  • Nginx では deny all の location を PHP の location より前に書き、ドットファイルの規則では .well-known を除外する
  • wp-config.php は1つ上のディレクトリに移せる。バックアップをドキュメントルートの中に作らない
  • ログを過去24時間に絞るには日時を比べる。探索してくる IP は fail2ban で遮断できる

関連記事

お気軽にご相談ください

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