Apacheの前段にNginxリバースプロキシサーバーを設置

isdはじめに

昨年、お客様のとある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

isdサーバー構成

サーバー構成はこんな感じ。

WebサイトのURLは https://example.jp/ とします。
OSはRHEL 8互換, Nginxのバージョンは 1.20, Apacheのバージョンは 2.4.x を想定しています。

※設定手順には記載しませんが、外部からのHTTP(TCP/80)は、NginxでHTTPS(TCP/443)にリダイレクトします。

isdサーバーの設定

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 にプログラム記述があるので、興味がある方は見てみるとよいでしょう。

以上でサーバーの設定は完了となります。

isd動作確認と調査

動作確認をしてみて、うまくいかない場合は、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アドレス>

 

isd余談

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

isdおわりに

Apacheの前段にNginxリバースプロキシサーバーを設置する際のNginx, Apache, WordPressの設定と注意点を記載しました。
1台のサーバーホストでNginxとApacheを併用するのは特殊な状況だと思いますが、今回のように、役に立つ場面もあるでしょう。

(関連記事)
・Nginxのリバースプロキシ設定、転送先がCloudFrontの場合
https://inaba-serverdesign.jp/blog/20210407/nginx-reverse-proxy-backend-cloudfront.html
 

Follow me!