Apacheの前段にNginxリバースプロキシサーバーを設置
はじめに
昨年、お客様のとあるWebサイトのサーバーをRocky Linux 8に移行する機会がありました。
このサイトは、複数の古いサブWebサイトが稼働しており、SSL化したときに内部リンクに http://~ が残っていることで「Mixed Contents」が発生したり、外部リンクの参照が正しくないことで、表示が崩れたりしていました。
しかし、古いWebサイトを修正する担当者はいないようで、それならサーバー側で対処しよう、ということで、同一サーバーホスト内で、Apacheの前段にNginxリバースプロキシサーバーを設置して、Nginxでコンテンツを強制的に書き換えることにしました。
ちなみに、「Nginxでコンテンツを強制的に書き換える」のであれば、ApacheをやめてNginxに置き換える方法もありますが、WordPress で .htaccess を使いたかったので、Nginx+Apacheの併用となりました。
ここでは、Apacheの前段にNginxリバースプロキシサーバーを設置する際のNginx, Apache, WordPressの設定と注意点を記載します。
※Nginx, Apache, WordPressのインストール方法と基本的な設定、SSL証明書などのSSL関連の設定は完了しているものとし、ここには記載しませんので、ご了承ください。
以下の記事がとても参考になりました。
ありがとうございます。
・nginx でコンテキストパス未対応サービスをリバースプロキシさせてみた(proxy_redirect + sub_filter)
https://qiita.com/k1tajima/items/732ec6694ecb3e928533
サーバー構成
サーバー構成はこんな感じ。
WebサイトのURLは https://example.jp/ とします。
OSはRHEL 8互換, Nginxのバージョンは 1.20, Apacheのバージョンは 2.4.x を想定しています。
※設定手順には記載しませんが、外部からのHTTP(TCP/80)は、NginxでHTTPS(TCP/443)にリダイレクトします。
サーバーの設定
Apacheの設定
Apacheは、グローバル設定 /etc/httpd/conf/httpd.conf で、リッスンポートをデフォルトの 80 から 8080 に変更します。
※80 は Nginx で使用するため。
#Listen 80 Listen 8080
VirtualHost Configで、ポート 8080 を指定します。
#<VirtualHost *:80> <VirtualHost *:8080> DocumentRoot /var/www/example.jp/html ServerName example.jp ... </VirtualHost>
※複数のVirtualHostを使用するときは、8081, 8082 と、ポート番号を追加していくとよいでしょう。
リッスンポートを変更したので、Configの文法チェックを行ったうえで、Apacheを再起動して反映します。
# apachectl configtest # systemctl restart httpd
Nginxの設定
VirtualHost Configで、リバースプロキシの設定を行います。
upstream backend { server 127.0.0.1:8080; } server { listen 443 ssl http2; server_name example.jp; root /var/www/example.jp/html; # Proxy location / { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host $http_host; proxy_set_header X-Forwarded-Server $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Host $host; proxy_set_header Accept-Encoding ""; sub_filter_once off; proxy_pass http://backend; sub_filter 'http://example.jp/' 'https://example.jp/'; sub_filter 'http://ajax.googleapis.com/' 'https://ajax.googleapis.com/'; sub_filter 'http://code.jquery.com/' 'https://code.jquery.com/'; sub_filter 'http://cdnjs.cloudflare.com/' 'https://cdnjs.cloudflare.com/'; sub_filter 'http://fonts.googleapis.com/' 'https://fonts.googleapis.com/'; sub_filter 'http://maps.google.com/' 'https://maps.google.com/'; sub_filter 'http://platform.twitter.com/' 'https://platform.twitter.com/'; ...
Configの文法チェックを行ったうえで、Nginxをreloadして反映します。
# nginx -t # systemctl reload nginx
Nginxの設定について、説明します。
upstream backend
バックエンドのApache 127.0.0.1:8080 へのリクエスト転送先を定義します。
のちの設定 proxy_pass http://backend;
で、http://127.0.0.1:8080/ に転送することになります。
proxy_set_header
リクエスト転送先で必要となるヘッダー情報を追加します。
proxy_set_header X-Forwarded-Proto $scheme;
もともとのリクエストのプロトコルが https であったことを伝えます。
これは、WordPressの処理で役に立ちます(後述します)。
proxy_set_header Host $host;
とすることで、HTTPのHostヘッダーが example.jp としてリクエストが転送されます。
これにより、Apacheでは、ServerName example.jp として設定したVirtualHostで処理されます。
proxy_set_header Accept-Encoding "";
レスポンスがgzipで圧縮されていると、sub_filterによるコンテンツ書き換えができないため、Accept-Encodingとして空を指定することで、圧縮されないようにします。
sub_filter_once off;
sub_filterによるコンテンツ書き換えが1度だけではなく、複数回適用されるようにします。
sub_filter 'http://example.jp/' 'https://example.jp/';
レスポンスコンテンツ内の http://example.jp/ を https://example.jp/ を書き換えます。
その他、今回は、Google APIやTwitter API, CDNなどの外部参照で不具合が生じていたため http から https に書き替えました。
WordPressの設定
WordPressのサイトURLは、https://example.jp/ と設定します。
※サイトURLを http:// とすると、自動生成されるリンクも http:// となってしまうので、https:// としましょう。
また、wp-config.php では、先頭のほう(<?php のすぐ下など)に、以下の設定を追加します。
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === "https") { $_SERVER['HTTPS'] = 'on'; $_SERVER['REQUEST_SCHEME'] = 'https'; }
これは、サイトURLが https:// となっているのに、WordPressが処理するプロトコルが http:// となっていると、WordPressは強制的に https へのリダイレクトを試み、それによりリダイレクトループが発生してしまうので、それを防ぐための設定です。
Nginxの設定で、
proxy_set_header X-Forwarded-Proto $scheme;
と指定したことが、ここで生きてきます。
※この方法は、以前一緒に仕事をした方に教えてもらいました。
※「WordPressは強制的に https へのリダイレクトを試みる」については、wp-login.php にプログラム記述があるので、興味がある方は見てみるとよいでしょう。
以上でサーバーの設定は完了となります。
動作確認と調査
動作確認をしてみて、うまくいかない場合は、Nginxのアクセスログとエラーログ、Apacheのアクセスログとエラーログ、PHP-FPMのエラーログなどを確認するとよいでしょう。
また、以下のような、HTTPリクエストヘッダー情報をすべて出力するPHPを設置し、ブラウザやサーバー上のcurlコマンドなどからアクセスし、意図したとおりのヘッダー情報となっているかどうか、確認するとよいでしょう。
getallheaders.php
<?php foreach (getallheaders() as $name => $value) { echo "<p>"; echo "$name: $value"; echo "</p>\n"; } ?>
表示されるヘッダー情報は、例えばこんな感じです。
Cookie: wordpress_test_cookie=xxx; wp_lang=ja Sec-Fetch-User: ?1 Sec-Fetch-Site: none Sec-Fetch-Mode: navigate Sec-Fetch-Dest: document Upgrade-Insecure-Requests: 1 Dnt: 1 Accept-Language: ja,en-US;q=0.7,en;q=0.3 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36 Connection: close Host: example.jp X-Forwarded-Proto: https X-Forwarded-Server: example.jp X-Forwarded-Host: example.jp X-Forwarded-For: <アクセス元IPアドレス> X-Real-Ip: <アクセス元IPアドレス>
余談
Nginxの sub_filter によるコンテンツ書き替えはとても強力な機能で、担当者がいなくなった古いWebサイトの配信に役立ちます。
ただし、メンテナンスされていない古いPHPサイト、WordPressサイトは、脆弱性がそのまま放置されている可能性が高く、非常に危険でもあります。
攻撃者にとっては、Webサイトが更新されているかどうかは関係ない、といいますか、更新されていないほうが、攻撃しやすいでしょう。
メンテナンスできない古いPHPサイト、WordPressサイトは、静的HTML化してしまえば、PHP/CGIプログラムは動作しなくなりますから、無用な攻撃を受ける可能性が大きく減るので、検討してみてはいかがでしょうか。
WebサイトのHTML化は、wgetコマンドを使用してクロール、ダウンロードするのが簡単です。
(参考)
・SSHログインできないサーバーからのWebサイト移行
https://inaba-serverdesign.jp/blog/20140401/website_server_migration_with_wget.html
おわりに
Apacheの前段にNginxリバースプロキシサーバーを設置する際のNginx, Apache, WordPressの設定と注意点を記載しました。
1台のサーバーホストでNginxとApacheを併用するのは特殊な状況だと思いますが、今回のように、役に立つ場面もあるでしょう。
(関連記事)
・Nginxのリバースプロキシ設定、転送先がCloudFrontの場合
https://inaba-serverdesign.jp/blog/20210407/nginx-reverse-proxy-backend-cloudfront.html