.htaccess は、Apache HTTP Server がディレクトリごとに読む設定ファイルです。メインの設定ファイル(httpd.conf)を編集できない共用レンタルサーバーでも、リダイレクト、アクセス制限、キャッシュヘッダーなどを自分で設定できます。ファイルを置いたディレクトリとその下のすべてのディレクトリに効き、Apache は変更を次のリクエストから反映するので、再起動も要りません。

便利な反面、書き間違い 1 行でディレクトリ全体が 500 エラーになり、逆に書いたことが何も効かないこともあります。この記事では、そうした挙動を Apache 2.4.67(macOS 同梱版)を手元で起動して確かめた結果とあわせて説明します。検証は 2026-10-02 に、ドキュメントルートに .htaccess を置いて http.client でリクエストを送り、ステータスコードとエラーログを読む方法で行いました。

.htaccess が読まれる条件:AllowOverride

.htaccess を読むかどうか、読むならどのディレクティブを許すかは、メイン設定の AllowOverride で決まります。Apache 2.3.9 以降の既定値は None で、この場合 .htaccess は読まれません。中身が間違っていてもエラーにならず、ただ無視されます。

RewriteEngin On

RewriteEngine を 1 文字打ち間違えたこの 1 行は、AllowOverride None では 200 のまま何も起きず、AllowOverride All ではトップページも CSS も 500 になりました。エラーログには .htaccess: Invalid command 'RewriteEngin', perhaps misspelled or defined by a module not included in the server configuration と記録されます。500 が出たら、まずエラーログでファイル名と行の内容を確認します。エックスサーバーではサーバーパネルの「エラーログ」で確認できます。

AllowOverride には All と None のほかに、AuthConfig(認証)、FileInfo(リダイレクトや mod_rewrite、ヘッダー)、Indexes、Limit、Options といった分類があり、許可された分類のディレクティブだけを書けます。許可されていない分類のディレクティブがあると、構文が正しくても 500 です。

Options -Indexes

AllowOverride FileInfo のディレクトリでこの 1 行を置くと、500 になり、エラーログは Options not allowed here でした。VPS や自社サーバーで設定を作るときは、使うディレクティブの分類を AllowOverride に入れるか、そもそもメイン設定に書きます。

毎回のリクエストで読まれることのコスト

Apache の .htaccess チュートリアルは、メイン設定を編集できるなら .htaccess は使わないよう勧めています。理由は 2 つです。

1 つ目は性能です。AllowOverride が有効だと、Apache はリクエストのたびに、対象ファイルのあるディレクトリから上位のすべてのディレクトリで .htaccess を探して読みます。チュートリアルの例では、/www/htdocs/example のファイルへの 1 回のリクエストで、/.htaccess、/www/.htaccess、/www/htdocs/.htaccess、/www/htdocs/example/.htaccess の 4 つを確認します。ファイルが存在しなくても探す処理は発生します。

2 つ目は管理のしやすさです。設定がディレクトリのあちこちに散ると、どこで何が効いているのか追いにくくなります。メイン設定の <Directory> に書けば、起動時に一度だけ読まれ、構文の誤りも apachectl configtest で事前に見つかります。configtest は .htaccess を読まないので、.htaccess の誤りは実際にリクエストを送るまで分からない点にも注意してください。

つまり .htaccess は、メイン設定に手が届かない共用レンタルサーバーのための仕組みです。日本の個人サイトや中小企業のサイトの多くがこの環境で動いているので、今でも欠かせないファイルになっています。

レンタルサーバーでの置き場所と編集方法

エックスサーバーでは、サーバーパネルの「.htaccess編集」でドメインごとの public_html/.htaccess を直接編集できます。マニュアルによると、ファイルがなければ保存時に作成され、文字コードは UTF-8 で保存されます。サブディレクトリの .htaccess はこの画面では編集できず、ファイルマネージャか FTP を使います。同じマニュアルには、サーバーパネルの機能や WordPress が .htaccess に自動で書き込んでいる記述があるので、心当たりがなくても不用意に消さないようにという注意があります。エックスサーバーは nginx を併用していますが、Apache 用の .htaccess をそのまま使えると明記しています。

ロリポップ!の「.htaccess利用方法」は、Windows では先頭がドットのファイル名を扱いにくいので htaccess.txt などの名前で作り、FTP でアップロードしてから名前を変える手順を案内しています。アップロード後のパーミッションは 604 です。設定は置いたディレクトリとその下に効き、たとえば /hoge に置けば /hoge と /hoge/moge に適用され、/abc には適用されません。

どちらのサーバーでも、編集前に今のファイルを手元に保存しておき、編集後はトップページと主要なページを開いて 500 になっていないか確かめます。.htaccess の誤りはサイト全体を止めるので、元に戻せる状態で触ることが一番の安全策です。

mod_rewrite の [L] と [END]:ループの実例

.htaccess で一番よく使われるのは mod_rewrite です。ここで誤解されやすいのが [L] フラグです。mod_rewrite のドキュメントにあるとおり、[L] は「この回の書き換え処理を終える」だけで、.htaccess(ディレクトリ単位の文脈)では、書き換え後の URL で内部リダイレクトが起き、ルールがもう一度最初から適用されます。

RewriteEngine On
RewriteRule ^(.*)$ app/$1 [L]

すべてのリクエストを app/ 以下へ書き換えるつもりのこのルールで /foo を開くと、500 になりました。/foo は /app/foo に、/app/foo は /app/app/foo に、と書き換えが続き、10 回の内部リダイレクトで打ち切られます。エラーログは AH00124: Request exceeded the limit of 10 internal redirects due to probable configuration error. です。

Apache 2.4 では [END] が使えます。[END] は書き換えを終え、内部リダイレクト後の再適用も止めます。ドキュメントも、ディレクトリ単位のルールにはほとんどの場合 [END] が向いていると書いています。

RewriteEngine On
RewriteRule ^(.*)$ app/$1 [END]

同じリクエストが 200 になり、app/foo の中身が返りました。[END] のない 2.2 系でも動く書き方にするなら、書き換え済みの URL を条件で除外します。

RewriteEngine On
RewriteCond %{REQUEST_URI} !^/app/
RewriteRule ^(.*)$ app/$1 [L]

WordPress が書き込む標準のブロックも、この考え方で作られています。

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress

存在しないパス /foo は /index.php に渡され、実在する app.css はそのまま返ります。2 回目の処理では先頭の RewriteRule ^index\.php$ - [L] が「書き換えない」で止めるので、ループしません。# BEGIN WordPress と # END WordPress の間は WordPress がパーマリンク設定の保存時に書き直すので、自分のルールはこの外側に書きます。

mod_rewrite まわりでは、ほかに 2 つの症状を確認しました。RewriteEngine On を書き忘れると、ルールはエラーにもならず黙って無視されます(RewriteRule ^old$ /new [R=301,L] だけを置いた場合、/old は 404 のままでした)。また、ディレクトリで Options FollowSymLinks と SymLinksIfOwnerMatch がどちらも無効だと、ルールに当たるリクエストはすべて 403 になり、エラーログに AH00670 が出ます。

Apache 2.2 の書き方と 2.4 の書き方

アクセス制限の書き方は、Apache 2.4 で変わりました。2.2 の Order / Deny / Allow は、2.4 では mod_access_compat が読み込まれているときだけ動く互換機能で、本来の書き方は mod_authz_core の Require です。

<Files "index.php">
Order deny,allow
Deny from all
</Files>

mod_access_compat なしの Apache では、このファイルだけでサイト全体が 500 になり、エラーログは Invalid command 'Order' でした。モジュールを読み込むと index.php は 403、ほかは 200 になります。ネットで見つかる古い記事の多くは 2.2 の書き方なので、コピーする前に確認が必要です。

レンタルサーバーの公式マニュアルにも 2.2 の書き方は残っています。ロリポップ!の「WordPressログインページの.htaccess編集方法」に載っている wp-login.php の制限は Order deny,allow、Deny from all、Allow from の形です。互換モジュールが有効なサーバーなら動きますが、Apache の移行ガイドは、Order などの古いディレクティブと Require を混ぜることは技術的には可能でも推奨しない(discouraged)と書き、互換モジュールは古いディレクティブだけの設定を移行するためのものだと説明しています。新しく書くルールは、たとえば次のように Require だけで書きます。

<Files "wp-login.php">
  Require ip 192.0.2.10
</Files>

HTTPS 化と www の統一:書き方を比べる

エックスサーバーのマニュアル「Webサイトの常時SSL化」は、次の 3 行を既存の設定を消さずに先頭へ追記するよう案内しています。独自 SSL を設定しただけでは https:// へ自動転送されないためです。

RewriteEngine On
RewriteCond %{HTTPS} !on
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

ZeroTool のジェネレーターが出力するのは RewriteCond %{HTTPS} off で、条件の向きが違うだけで意味は同じです。どちらも %{HTTPS} は Apache 自身が受けた接続を表すので、ロードバランサーや CDN が TLS を終端して Apache には HTTP で渡す構成では常に off になり、リダイレクトが止まらなくなります。その場合はプロキシ側で HTTPS 化するか、プロキシが付けるヘッダーを条件にします。

www の統一では、ロリポップ!の「URLの書き換え」が RewriteCond %{HTTP_HOST} ^www\.hogemoge\.com と RewriteRule ^(.*) http://hogemoge.com/$1 [R=301,L] のように、ホスト名を直接書く例を載せています。転送先が http:// なので、HTTPS のサイトでそのまま使うと、https://www. → http:// → https:// と余分な往復が増えます。転送先は https:// に書き換えて使います。ジェネレーターの www 統一は転送先が常に https:// ですが、ホスト名を書かずに「www. で始まらないホストすべて」に www. を足すので、blog.example.jp のようなサブドメインまで www.blog.example.jp に転送します。サブドメインを使うサイトでは、ホスト名を書いた形のほうが安全です。

ZeroTool のジェネレーターの出力と、足りないもの

.htaccess ジェネレーターを初期状態のまま使うと、次のファイルが出力されます(HTTPS 強制、ディレクトリインデックス、ブラウザキャッシュ、セキュリティの最初の 3 項目が ON)。

# Force HTTPS
<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteCond %{HTTPS} off
  RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</IfModule>

# Directory Index
DirectoryIndex index.php index.html index.htm

# Browser Caching
<IfModule mod_expires.c>
  ExpiresActive On
  ExpiresByType image/jpeg "access plus 1 year"
  ExpiresByType image/png "access plus 1 year"
  ExpiresByType image/gif "access plus 1 year"
  ExpiresByType image/svg+xml "access plus 1 year"
  ExpiresByType image/webp "access plus 1 year"
  ExpiresByType text/css "access plus 1 month"
  ExpiresByType application/javascript "access plus 1 month"
  ExpiresByType text/javascript "access plus 1 month"
  ExpiresByType font/woff "access plus 1 year"
  ExpiresByType font/woff2 "access plus 1 year"
  ExpiresByType font/ttf "access plus 1 year"
  ExpiresByType application/x-font-ttf "access plus 1 year"
  ExpiresByType application/vnd.ms-fontobject "access plus 1 year"
</IfModule>

# Security
Options -Indexes
<Files ".htaccess">
  Require all denied
</Files>
<Files ".env">
  Require all denied
</Files>

Apache 2.4.67 に置いて HTTP でリクエストすると、/ は https://example.test/ への 301 になりました。/.env と /.htaccess は、リダイレクトより先にアクセス制御が働くので 403 です。HTTPS の部分を外して試すと、CSS には Cache-Control: max-age=2592000(1 か月)、TTF には max-age=31536000(1 年)が付き、インデックスファイルのないディレクトリは 403 でした。font/ttf と application/x-font-ttf の両方が並んでいるのは、Apache 2.4 同梱の mime.types が .ttf を font/ttf にしているからです。

エックスサーバーには、サーバーパネルの「ブラウザキャッシュ設定」もあります。マニュアルによると、この機能は対象の静的ファイルに最大 7 日間のキャッシュヘッダーを付け、.htaccess で Cache-Control や Expires を設定している場合はそちらが優先されます。ジェネレーターのキャッシュ設定を入れると、パネルの設定より長い期間になる点を承知しておいてください。

ジェネレーターの出力について、ほかに知っておくべき点です。

  • <Files ".htaccess"> のブロックは、Apache 2.4 同梱の httpd.conf にある <Files ".ht*"> と同じ働きです。重複しても害はありません。
  • .env のブロックは名前が完全に一致するファイルだけを守ります。.env.local や .env.production は配信されるので、こうしたファイルはドキュメントルートの外に置きます。
  • XSS・クリックジャッキング対策のヘッダーを ON にすると、X-XSS-Protection は "0" で出力されます。古いブラウザの XSS フィルターは悪用の余地があったため、OWASP の HTTP Headers Cheat Sheet は 0 にするか送らないよう勧めています。
  • カスタムリダイレクトは Redirect ディレクティブで 1 件だけです。Redirect は前方一致なので、/old-blog の転送は /old-blog/2024/post にも効きます。
  • Basic 認証、CORS、gzip 圧縮、エラーページの項目はありません。Basic 認証のパスワードファイルは htpasswd 生成ツールで作れます。エックスサーバーやロリポップ!では、サーバーパネルの「アクセス制限」から同じ設定ができます。

出力を貼り付けたら、curl -I http://example.jp/ で 301 と Location を、curl -I https://example.jp/style.css で Cache-Control を確かめます。500 が出たら、エラーログで原因の行を確認してから直してください。