.htaccess は、Apache の設定をディレクトリごとに書くファイルです。 置いたディレクトリとその下に効き、リクエストのたびに読み込まれるので、書き換えると再起動なしですぐ反映されます。いちばんよく使う「https 化と www 統一を1回のリダイレクトで行う」書き方はこれです。

RewriteEngine On
RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteCond %{HTTP_HOST} ^(?:www\.)?(.+)$ [NC]
RewriteRule ^ https://www.%1%{REQUEST_URI} [R=301,L]

この4行を置いて、4通りのアクセスを curl で確かめた結果です。どこから来ても、1回の 301 で https://www.example.com/ に着きます。

アクセス 結果 Location
http://example.com/new.html 301 https://www.example.com/new.html
http://www.example.com/new.html 301 https://www.example.com/new.html
https://example.com/new.html 301 https://www.example.com/new.html
https://www.example.com/new.html 200 ―

確認環境:Ubuntu 24.04 の Apache 2.4.58(apt で入れたもの)で、この記事の .htaccess をすべて置き、curl 8 の -I(ヘッダーだけ表示)で結果を確かめました。https は自己署名の証明書、ロードバランサーは Apache の mod_proxy を前に置いて再現しています。nginx の対応表は nginx 1.24.0 で同じ動きになることを確かめました。仕様は Apache・nginx の公式ドキュメントで確かめています(確認日 2026年10月13日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「.htaccessの書き方|リダイレクト・https化・アクセス制限のコピペ集」

目的別コピペ集(早見表)

目的 使うもの 節
http → https RewriteCond %{HTTPS} off https化
www あり/なしに統一 RewriteCond %{HTTP_HOST} www統一
1ページだけ 301 Redirect 301 か RewriteRule ページ単位
ディレクトリごと移動 RedirectMatch 301 ディレクトリ移動
末尾スラッシュを付ける/消す RewriteCond %{REQUEST_FILENAME} 末尾スラッシュ
index.html を URL から消す RewriteCond %{THE_REQUEST} index.html
IP で制限 Require ip IP制限
パスワードをかける AuthType Basic Basic認証
見せたくないファイルを隠す <FilesMatch> + Require all denied ファイル
ブラウザのキャッシュ ExpiresByType か Header set Cache-Control キャッシュ
圧縮 AddOutputFilterByType DEFLATE 圧縮

書く前に知っておくこと

.htaccess が効く条件

.htaccess は、メインの設定(httpd.conf・apache2.conf など)の AllowOverride で許されたものだけが効きます。 Apache 2.4 では AllowOverride の既定値が None で、何も書かないと .htaccess は読まれません(Apache 2.4 の「Upgrading to 2.4 from 2.2」より)。

# メインの設定(サーバー管理者が書く)
<Directory /var/www/html>
    AllowOverride All
    Require all granted
</Directory>
AllowOverride の値 .htaccess で使えるもの(主なもの)
None 何も使えない。.htaccess を読まない
All .htaccess で使えるもの全部
AuthConfig AuthType・AuthName・AuthUserFile・Require など認証
FileInfo RewriteEngine・RewriteRule・Redirect・ErrorDocument・Header など
Indexes DirectoryIndex・ExpiresByType など
Limit Order・Allow・Deny(2.2 の書き方)

手元で AllowOverride を変えて試した結果です。

メインの設定 .htaccess の中身 結果
AllowOverride None でたらめな語 TestMe 200(.htaccess が読まれていない)
AllowOverride AuthConfig RewriteEngine On 500。ログに RewriteEngine not allowed here
AllowOverride All RewriteEngine On 200

「書いたのに何も起きない」ときは、でたらめな1行を入れて 500 になるか見ます。 500 にならなければ、.htaccess は読まれていません。この確かめ方は Apache の .htaccess のチュートリアルにも載っています。

エラーになったらログを見る

.htaccess の書き間違いは、そのディレクトリ全体が 500 Internal Server Error になります。 原因はエラーログ(Ubuntu は /var/log/apache2/error.log、RHEL 系は /var/log/httpd/error_log)に出ます。手元で出たログです。

書いたもの エラーログ([core:alert] の後ろ)
RewriteEngin On(つづり間違い) Invalid command 'RewriteEngin', perhaps misspelled or defined by a module not included in the server configuration
mod_rewrite が無効なのに RewriteEngine On Invalid command 'RewriteEngine', perhaps misspelled ...
[R=301 L](カンマを忘れた) RewriteRule: bad flag delimiters
AllowOverride で許されていない命令 RewriteEngine not allowed here
2.2 の Order(mod_access_compat が無効) Invalid command 'Order', perhaps misspelled ...
sudo tail -f /var/log/apache2/error.log      # Ubuntu・Debian
sudo tail -f /var/log/httpd/error_log        # RHEL・AlmaLinux など
sudo a2enmod rewrite headers expires         # Ubuntu でモジュールを有効にする
sudo systemctl reload apache2

<IfModule mod_rewrite.c> で囲むと、モジュールが無いときに 500 にならず、黙って何もしなくなります。 手元で mod_rewrite を無効にして試すと、囲んだリダイレクトは 404 のまま、エラーログにも何も出ませんでした。「リダイレクトが効かない」原因が見えにくくなるので、モジュールがあると分かっている環境では囲まないほうが調べやすくなります。

確かめ方:curl -I

ブラウザは 301 を覚える(キャッシュする)ので、確かめるときは curl を使います。

curl -I http://example.com/old.html                 # ヘッダーだけ見る
curl -sIL http://example.com/ | grep -iE '^(HTTP|location)'   # 転送をたどって全部見る
curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' http://example.com/

curl の使い方は、関連記事「curlコマンドの使い方|よく使うオプションとAPIの確認方法」で扱っています。

https化

RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
アクセス 結果
http://example.com/new.html 301 → https://example.com/new.html
http://example.com/new.html?a=1&b=2 301 → https://example.com/new.html?a=1&b=2
https://example.com/new.html 200

クエリ文字列(?a=1&b=2)は、何も書かなくても引き継がれます。 %{REQUEST_URI} には ? より後ろは含まれませんが、mod_rewrite が元のクエリ文字列を付け直します(公式ドキュメントの QSD フラグの説明より)。手元でも ?a=1&b=2 が1回だけ付きました。

部品 意味
RewriteEngine On mod_rewrite を使う。.htaccess ごとに1回
RewriteCond %{HTTPS} off 条件:https でないとき
RewriteRule ^ 転送先 ^ はすべてのパスにマッチする
%{HTTP_HOST} ブラウザが送ったホスト名(Host ヘッダー)
[R=301,L] 301 で転送し、ここで処理を止める

HSTS を付ける

https に移ったら、ブラウザに「次から https で来て」と伝える HSTS(Strict-Transport-Security)のヘッダーを付けることが多いです。 https のときだけ付ける書き方です。

Header always set Strict-Transport-Security "max-age=31536000" "expr=%{HTTPS} == 'on'"

手元では、https の応答にだけ Strict-Transport-Security: max-age=31536000 が付き、http の応答には付きませんでした。HSTS はブラウザが max-age の秒数だけ覚えるので、https で問題なく見られることを確かめてから付けます。

wwwあり・なしの統一

https 化と www の統一は、1回のリダイレクトにまとめます。 別々に書くと「http → https → www」の2回の転送になります。冒頭の「www ありに統一」に対して、「www なしに統一」はこう書きます。

RewriteEngine On
RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteCond %{HTTP_HOST} ^(?:www\.)?(.+)$ [NC]
RewriteRule ^ https://%1%{REQUEST_URI} [R=301,L]
アクセス 結果
http://example.com/new.html 301 → https://example.com/new.html
http://www.example.com/new.html 301 → https://example.com/new.html
https://example.com/new.html 200
https://www.example.com/new.html 301 → https://example.com/new.html

3つ目の RewriteCond は、ホスト名から www. を除いた部分を %1 に取り出すためのものです。 %1 は「最後にマッチした RewriteCond のかっこの中身」、$1 は「RewriteRule のかっこの中身」です。[OR] は「この条件か、次の条件のどちらか」で、付けないと条件はすべて AND(全部満たす)になります。

ページ単位の301

1ページだけを移すなら、Redirect 301 の1行で書けます。

Redirect 301 /old.html /new.html
# mod_rewrite で書く場合(パスの先頭の / は付けない)
RewriteEngine On
RewriteRule ^old\.html$ /new.html [R=301,L]
RewriteRule ^old2\.html$ /new.html [R=301,L,QSD]
アクセス 結果
/old.html 301 → https://example.com/new.html
/old.html?x=1 301 → https://example.com/new.html?x=1(クエリ文字列は残る)
/old2.html?x=1(QSD あり) 301 → https://example.com/new.html(クエリ文字列を消す)

.htaccess の RewriteRule は、パスの先頭の / を取った形でマッチします。 ^/old\.html$ と書くとマッチしません。Redirect のほうは /old.html のように / から書きます。転送先を /new.html のように相対で書くと、Apache がアクセスされたホスト名を付けて https://example.com/new.html にします。

Redirect は「前方一致」

Redirect は、指定したパスで始まる URL をまとめて転送します。 ただし、パスの区切り(/)単位で比べます(mod_alias のドキュメントより)。

Redirect 301 /blog /news
アクセス 結果
/blog 301 → /news
/blog/post1 301 → /news/post1(後ろが付く)
/blogger 404(転送されない)

1ページだけを転送したいのに、その下のページまで転送されることがあります。完全一致にしたいときは RedirectMatch 301 ^/blog$ /news か RewriteRule ^blog$ /news [R=301,L] を使います。

ステータスコードの既定値は 302

書き方 返ったコード
Redirect /c.html /new.html(コードなし) 302
RewriteRule ^a\.html$ /new.html [R,L](R だけ) 302
[R=301] / Redirect 301 301
[R=308] 308(メソッドと本文を変えない恒久的な転送)
RewriteRule ^gone\.html$ - [G] 410 Gone(削除したページ)

恒久的に移したページは、301 を明示します。 書かないと 302(一時的な転送)になります。301・302・308 の違いは、関連記事「HTTPステータスコード 一覧|全コードの原因と対処・curl実例つき」で扱っています。

Redirect と RewriteRule を混ぜない

同じ .htaccess に Redirect と RewriteRule を混ぜると、書いた順番どおりに動かないことがあります。 同じ /old.html に両方を書いて試した結果です。

RewriteEngine On
RewriteRule ^old\.html$ /from-rewrite.html [R=301,L]
Redirect 301 /old.html /from-redirect.html

手元の 2.4.58 では Location: http://example.com/from-rewrite.html になり、RewriteRule が効きました。Redirect は mod_alias、RewriteRule は mod_rewrite という別のモジュールで、処理の順番は書いた順ではなくモジュールで決まります。どちらかに揃えておくと、読み違いが減ります。

個別の 301 は、まとめの転送より前に書く

RewriteRule は上から順に評価され、[L] で止まります。 全体の https 化を先に書くと、個別の 301 は1回目の転送では使われません。

RewriteEngine On
RewriteRule ^(.*)$ https://example.com/$1 [R=301,L]     # ← これが先にマッチする
RewriteRule ^old\.html$ /new.html [R=301,L]

この順だと http://example.com/old.html は https://example.com/old.html に転送され、そこからもう1回 /new.html に転送されます(2回の転送)。個別のページの 301 を上に書き、転送先を https:// から書くと、1回で着きます。

ディレクトリの移動・ドメインの移動

# /old-dir/ の下を /new-dir/ の下へ
RedirectMatch 301 ^/old-dir/(.*)$ /new-dir/$1
# mod_rewrite で書く場合
RewriteEngine On
RewriteRule ^old-dir/(.*)$ /new-dir/$1 [R=301,L]
アクセス 結果
/old-dir/page.html 301 → /new-dir/page.html
/old-dir/page.html?q=1 301 → /new-dir/page.html?q=1
/old-dir/ 301 → /new-dir/

ドメインを移すときは、移す前のドメインの .htaccess に書きます。

RewriteEngine On
RewriteCond %{HTTP_HOST} ^(www\.)?old-example\.com$ [NC]
RewriteRule ^ https://www.example.com%{REQUEST_URI} [R=301,L]

http://old-example.com/a/b.html?z=9 が https://www.example.com/a/b.html?z=9 に転送されました。パスとクエリ文字列はそのまま引き継がれます。

末尾スラッシュを付ける・消す

# 付ける:ファイルでないもの(/about)を /about/ へ
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !/$
RewriteRule ^(.*)$ /$1/ [R=301,L]
# 消す:ディレクトリでないもの(/about/)を /about へ
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.+)/$ /$1 [R=301,L]
ルール アクセス 結果
付ける /about 301 → /about/
付ける /new.html(ファイルがある) 200(そのまま)
消す /about/ 301 → /about
消す /about/?p=2 301 → /about?p=2
消す /blog/(ディレクトリがある) 200(そのまま)
条件 意味
-f そのパスにファイルがある
-d そのパスにディレクトリがある
!-f・!-d 無い

実在するディレクトリは、何も書かなくても Apache がスラッシュを付けます。 .htaccess が無い状態で /blog(ディレクトリ)にアクセスすると、301 → /blog/ になりました。これは mod_dir の DirectorySlash(既定で On)の動きです。「消す」ルールで実在するディレクトリを除いているのは、この動きとぶつかって転送が繰り返されるのを防ぐためです。

index.html を URL から消す

RewriteEngine On
RewriteCond %{THE_REQUEST} \s/+(.*/)?index\.html[\s?] [NC]
RewriteRule ^ /%1 [R=301,L]
アクセス 結果
/index.html 301 → /
/blog/index.html 301 → /blog/
/blog/index.html?a=1 301 → /blog/?a=1
/・/blog/ 200(中身は index.html)

%{THE_REQUEST} は、ブラウザが送ったリクエストの1行目(GET /index.html HTTP/1.1)です。 / にアクセスしたときに Apache が中で index.html を読み込む動きとは関係なく、「ブラウザが index.html と書いてきたとき」だけを転送します。

手元の 2.4.58 では、RewriteRule ^(.*/)?index\.html$ /$1 [R=301,L] という単純な書き方でも / が 200 になり、繰り返しは起きませんでした。DirectoryIndex を変えている場合や、他のルールと組み合わせる場合に備えて、THE_REQUEST を見る書き方がよく使われます。

IP アドレスで制限する(Require ip)

# この IP からだけ見られる
Require ip 192.0.2.0/24 203.0.113.10
# この IP だけ拒否する
<RequireAll>
    Require all granted
    Require not ip 198.51.100.7
</RequireAll>
書いたもの(127.0.0.1 からアクセス) 結果
Require ip 192.0.2.0/24 203.0.113.10 403
Require ip 127.0.0.1 192.0.2.0/24 200
<RequireAll> + Require not ip 127.0.0.1 403

管理画面だけを制限するときは、そのディレクトリに .htaccess を置きます。 /admin/.htaccess に Require ip 192.0.2.0/24 を置くと、/admin/ は 403、ほかのページは 200 でした。

書き方 意味
Require ip 192.0.2.10 1つの IP
Require ip 192.0.2.0/24 範囲(CIDR)
Require ip 192.0.2 前方一致(192.0.2.で始まる)
Require ip 2001:db8::/32 IPv6
Require not ip ... 否定。<RequireAll> の中で使う
Require all granted / denied 全員許可 / 全員拒否

ロードバランサー・CDN の後ろでは mod_remoteip が要る

ロードバランサーや CDN の後ろにあるサーバーでは、Apache から見た接続元はロードバランサーの IP です。 手元で、接続元 127.0.0.1 を「ロードバランサー」とみなして試した結果です。

メインの設定 送ったヘッダー Require ip 203.0.113.5 の結果
mod_remoteip なし X-Forwarded-For: 203.0.113.5 403(127.0.0.1 として判定)
mod_remoteip あり X-Forwarded-For: 203.0.113.5 200
mod_remoteip あり X-Forwarded-For: 198.51.100.7 403
mod_remoteip あり なし 403
# メインの設定に書く(.htaccess には書けない)
RemoteIPHeader X-Forwarded-For
RemoteIPInternalProxy 10.0.0.0/8

RemoteIPInternalProxy には、信頼するロードバランサーの IP だけを書きます。 ここに書いていない接続元から来た X-Forwarded-For は使われません。誰でも送れるヘッダーなので、範囲を広げすぎると IP 制限をすり抜けられます。

Basic 認証

# パスワードファイルを作る(-c は新規作成。2人目からは付けない。-B は bcrypt)
sudo htpasswd -cB /etc/apache2/.htpasswd admin
sudo htpasswd -B /etc/apache2/.htpasswd user2
AuthType Basic
AuthName "Restricted Area"
AuthUserFile /etc/apache2/.htpasswd
Require valid-user
アクセス 結果
ユーザー名なし 401。WWW-Authenticate: Basic realm="Restricted Area"
パスワード間違い 401
正しいユーザー名とパスワード 200

AuthUserFile はサーバー上のフルパスで書き、公開ディレクトリの外に置きます。 公開ディレクトリに置くと、ファイルそのものを取得されるおそれがあります。AuthName の引用符は半角の " で書きます。全角の “ ” で書くと、手元では 500 になり、ログに AuthName takes one argument, the authentication realm (e.g. "Members Only") と出ました。Basic 認証はパスワードを暗号化せずに送るので、https と組み合わせます。

社内からは認証なし、社外からはパスワード、という書き方もできます。

AuthType Basic
AuthName "Restricted Area"
AuthUserFile /etc/apache2/.htpasswd
<RequireAny>
    Require ip 192.0.2.0/24
    Require valid-user
</RequireAny>

<RequireAny> は「どれか1つを満たせば許可」です。手元では、許可した IP から認証なしで 200 になりました。nginx で同じことをする手順は、関連記事「nginx Basic認証の設定手順|htpasswd作成からエラー対処まで」で扱っています。

見せたくないファイルを隠す

<FilesMatch "^\.(env|git)|\.(ini|log|sh|sql|bak)$">
    Require all denied
</FilesMatch>

# .git ディレクトリは 404 にする(存在を知らせない)
RedirectMatch 404 /\.git
アクセス 結果
/.env 403
/dump.sql 403
/.git/config 404
/new.html 200

.htaccess 自体が見えないかも確かめます。 Ubuntu の Apache は、最初から入っている apache2.conf に <FilesMatch "^\.ht"> Require all denied があり、.htaccess と .htpasswd は 403 になります。手元でこの行を書かない最小の設定で動かすと、/.htaccess が 200 で中身ごと返りました。 自分で設定を作ったサーバーでは、curl -I https://example.com/.htaccess で 403 になるかを見ておきます。

キャッシュの設定

画像・CSS・JS をブラウザにためておく期間を決めます。 mod_expires で書く方法と、mod_headers で Cache-Control を直接書く方法があります。

<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresByType image/png  "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/html  "access plus 0 seconds"
</IfModule>
ファイル 返ったヘッダー
/img/logo.png Cache-Control: max-age=31536000、Expires:(1年後の日時)
/new.html Cache-Control: max-age=0、Expires:(今の日時)
# Cache-Control を直接書く場合
<FilesMatch "\.(png|jpe?g|webp|svg|css|js|woff2)$">
    Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
<FilesMatch "\.html$">
    Header set Cache-Control "no-cache"
</FilesMatch>

こちらは画像に Cache-Control: public, max-age=31536000, immutable、HTML に Cache-Control: no-cache が付きました。1年ためる設定は、ファイル名に版やハッシュを入れて(app.3f2a1c.css など)、中身を変えたら名前も変わる場合に使います。 名前が同じまま中身を変えると、ブラウザは前の中身を使い続けます。

圧縮(gzip)

<IfModule mod_deflate.c>
    AddOutputFilterByType DEFLATE text/html text/plain text/css text/javascript application/javascript application/json image/svg+xml
</IfModule>
リクエスト 返ったヘッダー
Accept-Encoding なし Content-Length: 10692(圧縮なし)、Vary: Accept-Encoding
Accept-Encoding: gzip Content-Encoding: gzip、Content-Length: 577

10,692 バイトの HTML が 577 バイトで送られました(同じ文が並ぶ試験用のファイルなので、縮み方は実際のページより大きく出ています)。PNG・JPEG・WebP はもともと圧縮されているので、対象に入れません。 Ubuntu の Apache は、mod_deflate を有効にすると deflate.conf も読み込まれ、text/html・text/css・text/javascript・application/javascript などが圧縮の対象になります。

curl -sI -H 'Accept-Encoding: gzip' https://example.com/ | grep -i content-encoding

リダイレクトが繰り返す原因

https 化したらブラウザに「リダイレクトが繰り返し行われました」と出る場合、多くはロードバランサーや CDN が https を受けて、Apache には http で渡している構成です。 Apache から見ると毎回 %{HTTPS} が off なので、毎回 https へ転送します。

flowchart LR
    B["ブラウザ<br/>https://example.com/"] -->|https| LB["ロードバランサー<br/>(ここで https を終える)"]
    LB -->|"http<br/>X-Forwarded-Proto: https"| A["Apache<br/>%{HTTPS} = off"]
    A -->|"301 https://example.com/"| B

手元で、前に置いた Apache(mod_proxy)を「https を受けて http で渡すロードバランサー」にして試した結果です。

.htaccess curl -L --max-redirs 10 の結果
RewriteCond %{HTTPS} off だけ 301 を10回たどって止まった。curl: (47) Maximum (10) redirects followed
X-Forwarded-Proto の条件を足す 200(転送0回)
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteCond %{HTTP:X-Forwarded-Proto} !https [NC]
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

%{HTTP:ヘッダー名} で、リクエストヘッダーの値を条件に使えます。 AWS の ALB・Cloudflare など多くのロードバランサー・CDN は、元の通信が https だったことを X-Forwarded-Proto: https で知らせます。直接 Apache にアクセスできる構成では、このヘッダーを外から偽って送れるので、Apache に届く通信をロードバランサーからだけに絞っておきます。

繰り返しの原因 確かめ方 直し方
ロードバランサー・CDN が http で渡している curl -sIL で同じ URL への 301 が続く X-Forwarded-Proto を条件に足す
CDN の SSL 設定が「Flexible」(CDN → サーバーが http) CDN の SSL/TLS の設定 サーバーにも証明書を入れ、CDN を「Full」系にする
www あり → なし、なし → ありの両方を書いた Location が交互に変わる どちらか1つにする
WordPress の「サイトアドレス」と .htaccess の向きが違う 管理画面の設定と Location を比べる どちらかに揃える
末尾スラッシュの「消す」ルールと実在するディレクトリ /dir ↔ /dir/ を往復する RewriteCond %{REQUEST_FILENAME} !-d を付ける
ブラウザが前の 301 を覚えている curl では正しい ブラウザのキャッシュを消す・シークレットウィンドウで見る

.htaccess の中で [L] を付けても、書き換えたあとにもう一度最初からルールが評価されます。 公式ドキュメントでは、.htaccess(ディレクトリの設定)では [L] は「今回の処理」を止めるだけで、書き換え後の URL がまた先頭から処理されることがあり、それを止めるには [END] を使う、と説明されています。外部への転送(R=301)はそこで応答を返すので、繰り返しはブラウザとの往復で起きます。中での書き換え(R なし)が繰り返すときは [END] を使います。

Apache 2.2 と 2.4 の書き方の違い

アクセス制限の書き方が変わりました。 2.2 の Order・Allow・Deny は、2.4 では Require に置き換わります(Apache の「Upgrading to 2.4 from 2.2」より)。

やりたいこと 2.2 2.4
全員拒否 Order deny,allow + Deny from all Require all denied
全員許可 Order allow,deny + Allow from all Require all granted
この IP だけ許可 Order deny,allow + Deny from all + Allow from 192.0.2.0/24 Require ip 192.0.2.0/24
この IP だけ拒否 Order allow,deny + Allow from all + Deny from 198.51.100.7 <RequireAll> + Require all granted + Require not ip 198.51.100.7
認証か IP のどちらか Satisfy Any <RequireAny>
ドメインで許可 Allow from example.org Require host example.org

2.4 で 2.2 の書き方を使うには、mod_access_compat が要ります。 手元で試すと、mod_access_compat が無効だと Invalid command 'Order' で 500、有効にすると Allow from 127.0.0.1 で 200、Allow from 192.0.2.1 で 403 と、2.2 の書き方が動きました。公式ドキュメントでは、2.2 の書き方と 2.4 の書き方を混ぜるのは勧められていません。移すときはまとめて Require に書き換えます。

項目 2.2 2.4
AllowOverride の既定値 All None
アクセス制限 mod_authz_host の Order・Allow・Deny Require(2.2 の書き方は mod_access_compat)
RewriteRule のフラグ ― END・QSD などが使える
<If> での条件分岐 無い 使える

nginx での書き方の対応表

nginx には .htaccess がありません。 設定はすべて server ブロックに書き、nginx -t で確かめてから systemctl reload nginx で反映します。nginx 1.24.0 で、Apache と同じ結果になることを確かめた書き方です。

目的 Apache(.htaccess) nginx
https 化 + www 統一 RewriteCond %{HTTPS} off [OR] ... http 用 server に return 301 https://www.example.com$request_uri;
1ページの 301 Redirect 301 /old.html /new.html location = /old.html { return 301 /new.html$is_args$args; }
ディレクトリ移動 RedirectMatch 301 ^/old-dir/(.*)$ /new-dir/$1 rewrite ^/old-dir/(.*)$ /new-dir/$1 permanent;
IP 制限 Require ip 192.0.2.0/24 allow 192.0.2.0/24; deny all;
Basic 認証 AuthType Basic ... Require valid-user auth_basic "Restricted Area"; auth_basic_user_file ...;
ファイルを隠す <FilesMatch> + Require all denied location ~ /\.(env|git|ht) { deny all; }
キャッシュ ExpiresByType image/png "access plus 1 year" expires 1y;
圧縮 AddOutputFilterByType DEFLATE ... gzip on; gzip_types ...;
HSTS Header always set Strict-Transport-Security ... add_header Strict-Transport-Security "max-age=31536000" always;
404 ページ ErrorDocument 404 /404.html error_page 404 /404.html;
ロードバランサーの後ろの IP RemoteIPHeader X-Forwarded-For set_real_ip_from 10.0.0.0/8; real_ip_header X-Forwarded-For;
server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://www.example.com$request_uri;
}
server {
    listen 443 ssl;
    server_name example.com;
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    return 301 https://www.example.com$request_uri;
}
server {
    listen 443 ssl;
    server_name www.example.com;
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    root /var/www/html;
    index index.html;

    location = /old.html   { return 301 /new.html$is_args$args; }
    location ^~ /old-dir/  { rewrite ^/old-dir/(.*)$ /new-dir/$1 permanent; }
    location /admin/       { allow 192.0.2.0/24; deny all; }
    location ~ /\.(env|git|ht) { deny all; }
    location ~* \.(png|jpe?g|webp|svg|css|js|woff2)$ { expires 1y; }
}

nginx の return 301 /new.html; は、クエリ文字列を引き継ぎません。 手元で /old.html?x=1 にアクセスすると、$is_args$args を付けない書き方では Location: https://www.example.com/new.html になりました。Apache の Redirect は引き継ぐので、移すときに気づきにくい違いです。rewrite ... permanent はクエリ文字列を引き継ぎます。

location の優先順位は関連記事「nginx location書き方|優先順位とtry_filesをcurlで検証」、証明書の設定は「Let's Encrypt certbot自動更新|45日化とnginx設定手順」で扱っています。

よくある質問

Q. .htaccess を書き換えたら、再起動は要りますか? A. 要りません。.htaccess はリクエストのたびに読み込まれます。メインの設定(AllowOverride・LoadModule など)を変えたときは sudo systemctl reload apache2 で反映します。

Q. .htaccess とメインの設定(httpd.conf)のどちらに書くのがよいですか? A. Apache のドキュメントでは、メインの設定を編集できるならそちらに書くことが勧められています。.htaccess を許すと、Apache はリクエストのたびにディレクトリごとに .htaccess を探して読みます。レンタルサーバーのように、メインの設定を触れない環境のための仕組みです。

Q. 301 にしたのに、直したあとも前の転送先に飛びます。 A. ブラウザが 301 を覚えています。curl で確かめるか、シークレットウィンドウで開きます。試している間は 302 にしておき、確かめてから 301 に変える方法もあります。

Q. .htaccess のファイル名を変えられますか? A. メインの設定の AccessFileName で変えられます。既定は .htaccess です。

Q. ファイルの文字コードや改行は? A. UTF-8(BOM なし)・改行 LF で保存します。BOM が付くと、1行目の命令名の前に見えない文字が入ります。手元で BOM 付きで保存すると 500 になり、ログは Invalid command '\xef\xbb\xbfRewriteEngine' でした。パーミッションは 644 が一般的です。関連記事「chmodの755と644の意味|用途別パーミッション早見表」で扱っています。

まとめ

  • .htaccess は AllowOverride で許されたときだけ効く。2.4 の既定値は None。効かないときは、でたらめな1行で 500 になるかを見る
  • https 化と www 統一は 1回の 301 にまとめる。クエリ文字列は何も書かなくても引き継がれる
  • Redirect と [R] の既定値は 302。恒久的な移動は 301 を明示する
  • Redirect はパスの区切り単位の前方一致。完全一致は RedirectMatch か RewriteRule
  • ロードバランサーの後ろでは X-Forwarded-Proto を条件に足さないと転送が繰り返す。IP 制限は mod_remoteip が要る
  • 2.4 のアクセス制限は Require。2.2 の Order・Allow・Deny は mod_access_compat が無いと 500
  • 確かめるときはブラウザではなく curl -I

参考・出典

確認日はいずれも 2026年10月13日です。

  • Apache HTTP Server「Apache HTTP Server Tutorial: .htaccess files」:https://httpd.apache.org/docs/2.4/howto/htaccess.html
  • Apache HTTP Server「Upgrading to 2.4 from 2.2」:https://httpd.apache.org/docs/2.4/upgrading.html
  • Apache HTTP Server「RewriteRule Flags」:https://httpd.apache.org/docs/2.4/rewrite/flags.html
  • Apache HTTP Server「mod_rewrite」:https://httpd.apache.org/docs/2.4/mod/mod_rewrite.html
  • Apache HTTP Server「mod_alias」:https://httpd.apache.org/docs/2.4/mod/mod_alias.html
  • Apache HTTP Server「Apache Core Features(AllowOverride・AccessFileName)」:https://httpd.apache.org/docs/2.4/mod/core.html
  • Apache HTTP Server「mod_authz_host」:https://httpd.apache.org/docs/2.4/mod/mod_authz_host.html
  • Apache HTTP Server「mod_remoteip」:https://httpd.apache.org/docs/2.4/mod/mod_remoteip.html
  • Apache HTTP Server「mod_expires」:https://httpd.apache.org/docs/2.4/mod/mod_expires.html
  • Apache HTTP Server「mod_deflate」:https://httpd.apache.org/docs/2.4/mod/mod_deflate.html
  • Apache HTTP Server「mod_dir(DirectorySlash)」:https://httpd.apache.org/docs/2.4/mod/mod_dir.html
  • nginx「Module ngx_http_rewrite_module」:https://nginx.org/en/docs/http/ngx_http_rewrite_module.html
  • nginx「Module ngx_http_access_module」:https://nginx.org/en/docs/http/ngx_http_access_module.html
  • nginx「Module ngx_http_realip_module」:https://nginx.org/en/docs/http/ngx_http_realip_module.html

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「.htaccessの書き方|リダイレクト・https化・アクセス制限のコピペ集」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?