WordPress 向け .htaccess の設定=セキュリティ、圧縮、キャッシュの最適化

WordPress における .htaccess ファイルの必要性=パフォーマンス最適化やセキュリティ強化を図る

日本の Web サイトの約 58% が WordPress を使っているとされる(2025年1月現在)が、Web サーバの設定ファイルである .htaccess を適切かつ有効に設置しているサイトはそれほど多くはないはずだ。

WordPress の使い方を学ぶ

今回は WordPress のセキュリティ強化とパフォーマンス最適化につながる .htaccess の設定例をいくつか紹介したいと思う。なお、一般的な WordPress の基本については、専門書を少なくとも一冊購入しておくことをお勧めしたい。

作業前に必ずバックアップを取る

.htaccess ファイルは Apache サーバの設定ファイルであり、ディレクトリごとの設定を行うために使用される。

誤った記述があったり個々のホスティング環境などによっては、サイトの動作に支障をきたすケースがあるため、変更を適用する前に必ずバックアップを取ることが重要だ。また、テーマやプラグインに依存する場合や、メンテナンスなどを考慮して調整が必要な場合もある。
新しいファイルを適用した後や WordPress 関連のアップデート後には、サイトが正しく動作するか確認しよう。問題が発生した場合はすぐに元に戻せる状況下で慎重に作業を進めると安心だ。

WordPress 向け .htaccess カスタマイズの実例

セキュリティやキャッシュ制御の最適化を目指す

下記、少し長いコードだが、一番下にそれぞれのスニペットについての解説を用意したので参考にされたい。また、再度の確認になるが、既存の .htaccess ファイルのバックアップを忘れないこと。不具合が生じた場合は、変更を一つずつ適用して確認していくと良い。なお、サーバ環境や要件によっては追加の調整が必要な場合もある。トライアンドエラーを繰り返すことで、最終的に自分にあった設定が見つかるはずだ。

# (1) 画像ファイルへのアクセスを制御
<IfModule mod_setenvif.c>
    <IfModule mod_headers.c>
        <FilesMatch "\.(avifs?|bmp|cur|gif|ico|jpe?g|jxl|a?png|svgz?|webp)$">
            SetEnvIf Origin ":" IS_CORS
            Header set Access-Control-Allow-Origin "*" env=IS_CORS
        </FilesMatch>
    </IfModule>
</IfModule>

# (2) Webフォントファイルへのクロスオリジンリクエストを許可
<IfModule mod_headers.c>
    <FilesMatch "\.(eot|otf|tt[cf]|woff2?)$">
        Header set Access-Control-Allow-Origin "*"
    </FilesMatch>
</IfModule>

# (3) MultiViews オプションを無効化
Options -MultiViews

# (4) デフォルトの文字エンコーディングを指定
AddDefaultCharset utf-8

# (5) HTTPS への強制リダイレクトとセキュリティ設定
<IfModule mod_rewrite.c>
    # URL 書き換え機能の有効化
    RewriteEngine On
    RewriteCond %{HTTPS} !=on
    # 証明書発行サービスで使用されるディレクトリを除外
    RewriteCond %{REQUEST_URI} !^/\.well-known/acme-challenge/
    # HTTP から HTTPS への恒久的(301)リダイレクト
    RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
    # 危険な HTTP メソッドのブロック
    RewriteCond %{REQUEST_METHOD} ^(TRACE|TRACK)
    RewriteRule .* - [F]
</IfModule>

# (6) ディレクトリ一覧表示の無効化
<IfModule mod_autoindex.c>
    Options -Indexes
</IfModule>

# (7) 隠しファイルやディレクトリへのアクセスを制御
<IfModule mod_rewrite.c>
    RewriteEngine On
# .well-known ディレクトリの除外
    RewriteCond %{REQUEST_URI} "!(^|/)\.well-known/([^./]+./?)+$" [NC]
    RewriteCond %{SCRIPT_FILENAME} -d [OR]
    RewriteCond %{SCRIPT_FILENAME} -f
    RewriteRule "(^|/)\." - [F]
</IfModule>

# (8) 重要なファイルタイプへのアクセス制限
<IfModule mod_authz_core.c>
    <FilesMatch "(^#.*#|\.(bak|conf|dist|fla|in[ci]|log|orig|psd|sh|sql|sw[op])|~)$">
        Require all denied
    </FilesMatch>
</IfModule>

# (9) X-Powered-By ヘッダを削除
<IfModule mod_headers.c>
    Header unset X-Powered-By
    Header always unset X-Powered-By
</IfModule>

# (10) エラーページのサーバ署名を非表示
ServerSignature Off

# (11) ETag ヘッダを削除
<IfModule mod_headers.c>
    Header unset ETag
</IfModule>

# (12) ETag 生成機能の完全無効化
FileETag None

# (13) 重要な設定ファイルの保護
<FilesMatch "^\.">
    Require all denied
</FilesMatch>

# (14) 重要なファイルへのアクセス制限
<FilesMatch "^.*(wp-config\.php|php\.ini|error_log|readme\.(html|txt)|license\.(txt|html)|changelog\.(txt|html)|wp-config-sample\.php|\.(htaccess|htpasswd|ini|phps|fla|psd|log|sh|cmd|exe|bat|csh))$">
    Require all denied
</FilesMatch>

# (15) XML-RPC の無効化
<Files xmlrpc.php>
    Require all denied
</Files>

# (16) uploads ディレクトリの保護(PHP などの実行制限)
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{REQUEST_URI} ^/wp-content/uploads/ [NC]
    RewriteCond %{REQUEST_URI} \.(?:php|phtml|php3|php4|php5|php7|php8|phps|phar|cgi|pl|py|asp|aspx|jsp)$ [NC]
    RewriteRule .* - [F,L]
</IfModule>

# (17) 包括的なセキュリティヘッダーの設定
<IfModule mod_headers.c>
    # A: Content Security Policy (CSP) の設定
    Header always set Content-Security-Policy "upgrade-insecure-requests"
    # B: クロスサイトスクリプティング(XSS)フィルターの有効化
    Header always set X-XSS-Protection "1; mode=block"
    # C: リファラー情報の送信制御
    Header always set Referrer-Policy "no-referrer-when-downgrade"
    # D: 証明書の透過性を強制
    Header always set Expect-CT "max-age=7776000, enforce"
    # E: クリックジャッキング攻撃対策
    Header always append X-Frame-Options "SAMEORIGIN"
    # F: ブラウザ機能の使用制限
    Header always set Permissions-Policy "geolocation=(); midi=();notifications=();push=();sync-xhr=();accelerometer=(); gyroscope=(); magnetometer=(); payment=(); camera=(); microphone=();usb=(); xr=();speaker=(self);vibrate=();fullscreen=(self);"
    # G: ブラウザに HTTPS の使用を強制
    Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" env=HTTPS
</IfModule>

# (18) ブラウザキャッシュ期間の最適化
<IfModule mod_expires.c>
    # キャッシュの有効化
    ExpiresActive On
    # デフォルトの有効期限(1ヶ月)
    ExpiresDefault "access plus 1 month"
    # メディアファイル
    ExpiresByType image/jpeg "access plus 1 year"
    ExpiresByType image/gif "access plus 1 year"
    ExpiresByType image/png "access plus 1 year"
    ExpiresByType image/webp "access plus 1 year"
    ExpiresByType image/avif "access plus 1 year"
    ExpiresByType image/svg+xml "access plus 1 year"
    ExpiresByType image/x-icon "access plus 1 year"
    # ウェブフォント
    ExpiresByType font/ttf "access plus 1 year"
    ExpiresByType font/otf "access plus 1 year"
    ExpiresByType font/woff "access plus 1 year"
    ExpiresByType font/woff2 "access plus 1 year"
    # WebAssembly, CSS, JavaScript
    ExpiresByType application/wasm "access plus 1 year"
    ExpiresByType application/javascript "access plus 1 year"
    ExpiresByType text/css "access plus 1 year"
    # データファイル
    ExpiresByType application/pdf "access plus 1 month"
    ExpiresByType application/json "access plus 0 seconds"
    ExpiresByType application/xml "access plus 0 seconds"
</IfModule>

# (19) キャッシュ制御の強化とスニッフィング防止
<IfModule mod_headers.c>
    # A: MIME タイプのスニッフィング防止
    Header set X-Content-Type-Options "nosniff"
    # B: 静的リソースに長期間キャッシュを設定
    <FilesMatch "\.(ico|jpg|jpeg|png|gif|webp|avif|svg|js|css|swf|pdf|ttf|otf|woff|woff2|wasm)$">
        Header set Cache-Control "public, max-age=31536000, immutable"
    # C: 動的リソースのキャッシュ無効化
    </FilesMatch>
    <FilesMatch "\.(html|htm|php)$">
        Header set Cache-Control "private, no-store, no-cache, must-revalidate, max-age=0"
        # 古い HTTP/1.0 クライアントとの互換性確保
        Header set Pragma "no-cache"
    </FilesMatch>
</IfModule>

# (20) Gzip 圧縮の有効化
<IfModule mod_deflate.c>
    # 圧縮を有効にする MIME タイプ
    AddType application/x-font-woff .woff
    AddType application/x-font-woff2 .woff2
    AddType image/svg+xml .svg
    AddType application/json .json
    AddType application/ld+json .jsonld
    AddType application/manifest+json .webmanifest
    # 圧縮レベルとバッファサイズの最適化
    DeflateCompressionLevel 6
    DeflateBufferSize 8192
    DeflateMinLength 256
    # 圧縮を有効にする MIME タイプ
    AddOutputFilterByType DEFLATE text/plain
    AddOutputFilterByType DEFLATE text/html
    AddOutputFilterByType DEFLATE text/xml
    AddOutputFilterByType DEFLATE text/css
    AddOutputFilterByType DEFLATE application/javascript
    AddOutputFilterByType DEFLATE text/calendar
    AddOutputFilterByType DEFLATE application/xml
    AddOutputFilterByType DEFLATE application/xhtml+xml
    AddOutputFilterByType DEFLATE application/rss+xml
    AddOutputFilterByType DEFLATE application/atom+xml
    AddOutputFilterByType DEFLATE application/json
    AddOutputFilterByType DEFLATE application/ld+json
    AddOutputFilterByType DEFLATE application/manifest+json
    # プロキシサーバへの対応
    BrowserMatch ^Mozilla/4 gzip-only-text/html
    BrowserMatch ^Mozilla/4\.0[678] no-gzip
    BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
    # 圧縮対象外のファイル
    SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png|webp|avif|ico|zip|rar|7z|gz|bz2|mp3|mp4|mov|pdf|docx|xlsx)$ no-gzip
    # エラー時の圧縮制御
    SetEnvIfNoCase Request_URI \.(html|htm|xml|json|jsonld)$ no-gzip-if-error
</IfModule>

# (21) WordPress の基本リライトルール
# BEGIN WordPress
# "BEGIN WordPress" から "END WordPress" までのディレクティブ (行) は
# 動的に生成され、WordPress フィルターによってのみ修正が可能。
# これらのマーカー間にあるディレクティブへのいかなる変更も上書きされる。
<IfModule mod_rewrite.c>
RewriteEngine On
    # 特定の認証ヘッダーを維持
    RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
    # リライトルールのベースディレクトリを設定
    RewriteBase /
    RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule . /index.php [L]
</IfModule>
# END WordPress

各コード・スニペットについての説明

(1) 特定の画像ファイルへの異なるドメインからのリクエスト(クロスオリジンリクエスト)の制御(CORS 制限)。この例では Access-Control-Allow-Origin "*" によって CORS を全許可(*)しており、全ての画像ファイルにオリジン間のアクセス許可が適用される。CDN やマルチドメインを使用するなど、アクセスを許可するオリジン(ドメイン)が明確になっている場合は、*(ワイルドカード)の代わりに特定のドメインを指定すると良いだろう。

(2) 指定された Web フォントファイル(eot, otf, ttf, ttc, woff, woff2)に対して、クロスオリジンリクエストがあった場合、すべてのオリジンからのアクセスを許可するように設定。CDN などから Web フォントを読み込む場合に必要だ。(1)と同様に CORS を全許可(*)しているが、必要に応じて特定ドメインに制限することができる。例えば、Header set Access-Control-Allow-Origin "https://example.com" のようにすれば、特定のオリジンのみを許可できる。自サイトで完結する場合は自サイトのドメインに制限したほうがベターだ。

(3) MultiViews 機能を - (マイナス記号) によって無効化。ファイル名と拡張子が厳密に一致する場合のみファイルを提供する。コンテンツネゴシエーション機能の一つで、セキュリティやパフォーマンス向上に貢献するケースがある。

(4) デフォルト文字セットとして、標準的な utf-8 の文字コードを指定。一貫した文字エンコーディングの提供によって文字化け防止につながる。サーバやアプリケーション側で他の設定が指定されていないことを確認しよう。

(5) HTTPS リダイレクトと不要なリクエストの拒否。ここでは、HTTPS リダイレクトの対象から Let's Encrypt などの SSL 証明書発行や自動更新で使用されるディレクトリ .well-known/acme-challenge/ を除外している。また、リクエストメソッドが TRACE(サーバへのデバッグリクエスト)、TRACK(Microsoft 製のデバッグメソッド)、OPTIONS(サーバがサポートする HTTP メソッドの確認)のいずれかの場合、403 Forbidden エラーを返す。いずれもセキュリティ向上につながる。なお、OPTIONS メソッドは、必要に応じて拒否の解除を視野に入れたい(Update: 外観テーマのサイトエディターを使う場合などにエラーを起こすため、上記例でも OPTIONS メソッドを解除した)。

(6) ディレクトリ内のファイル一覧を表示させないことで、サイト構造を隠すことができる。セキュリティ対策として必須といえる。

(7) 重要なファイルであるドットファイル(.htaccess、.git、.env などの隠しファイル)およびドットディレクトリへの直接のアクセスを防ぐ。.well-known ディレクトリへのアクセスは妨げないことで、Let's Encrypt などの SSL/TLS 認証チャレンジを許可している。

(8) バックアップファイルや設定ファイル、ログファイルなど、特定の拡張子を持つファイルへのアクセスを制限する。公開すべきでない重要ファイルの漏洩や改ざんリスクを軽減。正規表現で不要なものは削除を検討しても良い。

(9) X-Powered-By ヘッダーの削除によって、サーバで使用されている技術情報(PHP のバージョン)などを隠す。攻撃者にヒントを与えないことでセキュリティリスクを軽減できる。

(10) エラーページに表示されるサーバ署名の無効化。サーバの種類やバージョン情報を隠すことでセキュリティ強化につながる。

(11) ETag (Entity Tag) HTTP レスポンスヘッダーを削除する。

(12) ETag ヘッダを生成しないように設定して、不要な ETag による処理のオーバーヘッドを削減できる。パフォーマンス最適化のためには、キャッシュ制御を他のヘッダー(Expires ヘッダー や Last-Modified など)で適切に制御する必要がある。

(13) 隠しファイル(ドットで始まるファイル)へのアクセスを拒否する。重要なファイルの保護につながるが、証明書認証に .well-known ディレクトリが必要な場合、別途アクセスを許可する設定が必要。

(14) サイトに影響を与える重要なファイル(wp-config.php や php.ini)へのアクセスを防ぐ。情報漏洩や不正アクセスのリスクを低減できる。必要に応じて正規表現を調整してバランスを取ろう。

(15) XML-RPC へのアクセス制御。DDoS 攻撃やブルートフォース攻撃など不正ログイン試行の脆弱性対策として有効。なお、Jetpack や一部のプラグイン、遠隔作業(リモート投稿)などによっては、無効にすることを含めてバランスを取る必要がある。

(16) アップロードディレクトリでの PHP や Perl ファイルなどの実行を禁止。悪意のあるスクリプトなどによるリモートコード攻撃防止、マルウェア対策として有効。特定のファイルタイプのみ許可すると、より強固になる。

(17)
・A: Content-Security-Policy (CSP) の適切な設定によって、クロスサイトスクリプティング (XSS) などの攻撃を防ぐことができる。Web ページが読み込み可能なリソース(スクリプト、スタイル、画像など)のソースを制限。上記の "upgrade-insecure-requests" は、CSP のディレクティブの一つで、「ページ内のすべての HTTP URL を HTTPS にアップグレードする」ようブラウザに指示を出す(HTTPS への自動アップグレードを強制)。可能であれば、リソースの制限(default-src や script-src など)の追加を検討したい。なお、使用しているプラグインやテーマに応じて調整が必要になる場合があり、動作確認が必要となる。
・B: X-XSS-Protection ヘッダーを設定して、XSS(クロスサイトスクリプティング)攻撃を検知した際にページの読み込みをブロックする。古いブラウザ向に設置が推奨される。
・C: Referrer-Policy ヘッダーによって、リクエストに Referer ヘッダを含めるかどうかを制御。"no-referrer-when-downgrade" は、「HTTPS から HTTP へのリクエストの場合には Referer ヘッダを送信しない」ように指示する。
・D: Certificate Transparency を強制することで、不正な SSL 証明書の使用を防止。enforce を指定することで、証明書の透過性ログに記録されていない証明書を使用しているサイトへの接続をブラウザが拒否。ここでは90日間の有効期限を設定している。
・E: X-Frame-Options ヘッダーを設定して、フレーム表示を制限。クリックジャッキング攻撃を防ぐ。ここでは、同じオリジン(ドメイン)のページからのみ iframe での埋め込みを許可している。他の iframe 表示が不要な場合は DENY を検討。
・F: パーミッションのポリシーの設定は、位置情報、カメラ、マイクなどといった特定のブラウザ機能権限を制御するためのヘッダー。各ポリシーはセミコロンで区切る。この例では、スピーカーとフルスクリーンのみ同じオリジンに許可しており、プライバシーとセキュリティ強化を図っている。
・G: HTTP Strict Transport Security (HSTS) の有効化。max-age を2年(63072000秒)に設定。サブドメインも含めて HTTPS 通信の強制を行う。preload の追記で、ブラウザの HSTS preload リストへの登録を許可(オプション)。

(18) 各ファイルタイプごとに、効率的なキャッシュ有効期間を設定してパフォーマンス向上を目指そう。ブラウザの再リクエストを減らしてページ読み込み速度を向上させるため、静的アセット(画像、フォント、CSS、JS など)のキャッシュ期間は長く設定する。一方、動的コンテンツ(JSON、XML など)は即時更新させて、最新の状態を常に取得させるのが基本だ。

(19)
・A: MIME スニッフィング対策として、ブラウザがコンテンツタイプを誤って解釈するのを防ぐ。MIME タイプをスニッフィング(推測)しないようにすることで、クロスサイトスクリプティング(XSS)のリスクを低減させる。
・B: 静的リソースに対するキャッシュ制御の強化。ここでは immutable フラグを追加してブラウザの再検証を防止しており、パフォーマンスの向上が期待できる。
・C: 動的ファイルのキャッシュ制御。no-store と no-cache を組み合わせることで、ブラウザがキャッシュを保持しないように設定。HTML や PHP ファイルは private に設定する。古い HTTP/1.0 クライアントとの互換性を考慮して Pragma "no-cache" を追加。must-revalidate でキャッシュを再確認させる。なお、サーバ環境とモジュールの有効化状況を確認すると良いだろう。

(20) Gzip 圧縮を有効にして、サイトの表示速度を向上させることができる。圧縮レベル6 が環境に適しているか(1 から 9 までの値を設定)、バッファサイズ 8192 がトラフィックパターンに最適かどうかなどの確認が必要だ。加えて、特定の CDN を使用している場合、Vary ヘッダーの動作確認をしよう。また、一部のレンタルサーバでは、独自の Gzip 圧縮設定があるケースもある。なお、サーバが HTTP/2 をサポートしている場合は、Gzip ではなく Brotli 圧縮(mod_brotli)を検討しても良いだろう。複雑な要素が多いが、WordPress には Gzip 圧縮プラグイン(WP Super Cache、W3 Total Cache、WP-Optimize、Autoptimize など)が多数用意されており、.htaccess を直接編集するよりも簡単で安全な場合が多い。プラグインに頼るのも悪くはないだろう。

(21) WordPress の基本リライトルール。# BEGIN WordPress から # END WordPress の部分は自動生成される。この WordPress 設定を手動で変更した場合、内容が上書きされる可能性がある。RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 部分は、特定の認証ヘッダーを維持するという意味だ。この標準ルール​は通常 .htaccess の一番最後に配置する。

追記および注意・免責事項

.htaccess は、最後に必ず改行を入れる必要がある。また、他のユーザが編集できないように、パーミッションは 644 や 604 に設定しよう。
なお、今回のサンプルコードは Apache 2.4 以降を想定している。例えば、古いバージョンであれば Require all denied の代わりに、Order deny,allow や Deny from all を使用する必要がある。
その他、個々のサーバ環境、ソフトウェアのバージョン、プラグインとの干渉などによって、一部の設定が動作しない場合がある点はご留意願いたい。変更後は必ずサイトの動作確認を行おう。
また、Apache サーバは .htaccess ファイルのルールを上から順番に処理することも覚えておきたい。つまり、CSS などと異なり、先に書かれたルールが後のルールよりも優先して適用される訳だ。対策として、より具体的なルールを上に、より一般的なルールを下に記述するといいだろう。一般的なレンタルサーバであれば、上記例のスニペットの順番でほぼ問題無いと思われる。
作業前にはバックアップを取っておき、Internal Server Error 500 といったエラーを叩いても、すぐに元に戻せるようにしておくと安心だ。

変更前後のパフォーマンスを確認しよう

PageSpeed Insights でパフォーマンスを確認

PageSpeed Insights などのツールを使って、ページのパフォーマンス遷移やエラーの有無を確認しておこう。読み込み速度については、適用前と適用後の比較を取ると分かりやすい。

.htaccess は強固なセキュリティ対策の第一歩

ここまで .htaccess の設定について解説してきたが、これらのスニペットはあくまで一例に過ぎず、個々の開発環境、目的、サーバの状況などを踏まえて調整して欲しい。ここで言及したルールは WordPress におけるセキュリティやパフォーマンス対策としてはごく一部の事項に過ぎない。必要に応じて、エラーページの設定やログイン画面への IP 制限、PHP のメモリ制限などを講じるとより効果的だ。また、WordPress 本体のみならず、テーマやプラグインなどを常に最新の状態に保つことも非常に大切である。アクティブにしていないテーマやプラグインなどは削除しておきたい。

バックアップやアップデートなどの基本が大切

2024年頃から AI 技術を悪用した脅威などが散見されており、サイバーリスクの予測が困難ななかにおいて、いくら万全を期しても 100% の完ぺきさなどは存在しない。バックアップを定期的に取ることや自動アップデートをオンにするなど、基本的な対策を講じることによってリスクの軽減を図り、いざというときに備えておきたい。

2025年1月現在、最適と思われる Htaccess ファイルの記述実例についてまとめてみた。ご質問やご提案などあれば、下記コメント欄またはコンタクトフォームをご利用いただきたい。

First posted on 2025年1月3日

Author: IT Memo TV - Tsugawa.TV  對川徹(Toru Tsugawa)


コメント