はじめに
今回でこのシリーズも第四回目となりました。
UbuntuのインストールからFTP、ウェブサーバの導入にHTTPS化、静的サイトジェネレータの導入と続き、この記事ではリバースプロキシを使った静的コンテンツ専用サーバの作成を行います。
Nginxのリバースプロキシで更に静的コンテンツ専用サーバという形では、そこまで需要は無いかもしれませんが(小さなウェブサイトではサーバ費用を考えるとServiceWorkerの方が使い勝手が良いかも)、旧サイトで実験的に使っていたこともあるため、メモ代わりとして置いておきます。
これまでのシリーズは以下のとおりです。
- Ubuntu20.04のInstallおよびSSH、FTP設定【第一回】
- Nginxの導入および設定&セキュアなHTTPS化【第二回】
- Rust製SSG、Zolaの導入および各種設定【第三回】
- Nginxリバースプロキシで静的コンテンツ専用サーバを作成【第四回】
- https://www.pr1sm.com/web/reverse-proxy-nginx-static-cache-server/
- WordPress&MariaDBの導入および高速化設定【第五回】
- サイトの安全性を更に高めるWordPressセキュリティ設定【第六回】
なお、このシリーズではホスティングにVultrを使っています。
- Vultr
サーバのスペックおよび構成は以下の通りです。
$ cat /etc/lsb-release
- DISTRIB_ID=Ubuntu
- DISTRIB_RELEASE=20.04
- DISTRIB_CODENAME=focal
- DISTRIB_DESCRIPTION="Ubuntu 20.04.3 LTS"
Vultr : High Frequency Server
- 32GB MVMe
- 1CPU
- 1GB Memory
- Additional Features
- Virtual Private Clouds: ON
プライベートネットワークの作成
キャッシュサーバとメインサーバで接続を行うプライベートネットワークを利用するため、まずはメインサーバとは別のサーバを用意してください。
キャッシュサーバのドメインはstatic.example.com
として扱います。自身のサイトに用いるときは使うドメインに置き換え、サブドメイン(例ではstatic
)のDNSも事前に設定しておくことをおすすめします。
また、リバースプロキシにはNginxを使うため、キャッシュサーバにNginxが入っていない場合は導入を行い、更にHTTPSで接続する場合はサブドメインの証明書取得も行ってください。
Vultrでプライベートネットワークを構築するには、サーバの初期設定で もし既に存在するサーバに適用する場合には、Vultrコントロールパネルの すると初期設定項目が出てくるので、 すべての設定が終われば ただし、このままでは既存サーバがネットワーク内にまだ入っていません。そのため、コントロールパネルの 個別サーバ設定に移動したら そして これをメインサーバとキャッシュサーバどちらにも行ってください。 注意してほしいのが、選択したプライベートネットワークを両サーバに設定しても、この時点では相互に通信を行うことはまだできません。どちらにも追加の設定が必要となっています。 まずVultrの個別サーバ設定で そしてその中の 次にどちらかのサーバにログインしたら すると 記述が完了したら 適用後もう一度 上記設定を両サーバ共に行います。 どちらも設定が完了したら これにより、プライベートネットワークの構築が完了しました。Enable Virtual Private Clouds
にチェックを入れてプライベートネットワークをオンにします。既存のサーバにVPCを適用
Products
から+
ボタンを選択してView More Options
を選ぶと項目が増えるので、その中からAdd VPC Network
を選んでください。Location
は使っているサーバの場所を選択し、Configure IP Range
およびManage Routes
はRecommendになっているAuto-Assign
を選択します。VPC Network Name
に関しては自由に名前を付けてください。Add Network
を選択することでプライベートネットワークが構築されます。Products
内にあるServer
一覧にて接続を行いたいサーバを選択して個別サーバ設定に移動します。Settings
タブからIPv4
の項目が選択されていることをチェックし、下部にあるVPC Network
に選択タブがあるので、先程作成したプライベートネットワークを選びます。Attach VPC
を選択すると確認が表示されるので、もう一度Attach VPC
を選ぶことでそのサーバは再起動が行われ、その後は選択したVPC内のネットワークに入ることができます。VPCの個別設定
Settings
、IPv4
、VPC Network
の順に移動し、Address
とMAC Address
をどちらのサーバもメモしてください。Manage
を選択し、Virtual Private Clouds
のSubnet
にあるIPアドレス/数値
の数値
も控えておきます(数値は同じなので一つでOK)。$ ip a
でサーバのIPを確認します。1:lo:
や2: enp1s0:
、3: enp6s0:
といった情報が数行に渡って表示されます。enp1s0
やenp6s0
という表示は端末によって異なります。異なる場合はそちらを使ってください。lo
はローカルであり、次のenp1s0
はパブリックネットワークとして使われ、そしてenp6s0
はプライベートネットワークとして用いられます。enp6s0
にあるlink/ether
の部分がMACアドレスです。$ sudo vim /etc/netplan/10-enp6s0.yaml
を使ってnetplanの編集を行います。数値-*.yaml
を順番に読み込んでいくようになっています。10-enp6s0.yaml
network:
version: 2
renderer: networkd
ethernets:
enp6s0:
match:
macaddress: ここにMAC Address
mtu: 1450
dhcp4: no
addresses: [メモしたサーバのプライベートIPアドレス/数値]
$ sudo netplan apply
で設定を適用します。$ ip a
を実行し、enp6s0
の項目内にinet プライベートIPアドレス/数値
が表示されていれば、正しく設定ができています。$ ping 相手サーバのプライベートIPアドレス
を行い、どちらのサーバでも0% packetloss
が表示されていれば問題なく通信が行えています。
Nginxリバースプロキシの設定
次はNginxリバースプロキシの設定を行います。
基本的にほとんどキャッシュサーバ側の設定のみです。
- まずは
$ sudo vim /etc/nginx/nginx.conf
でnginxのコア設定を行います。
nginx.conf
## 省略 ##
# Proxy Cache Settings
# プロキシキャッシュのパスを指定し、階層レベルを1:2
# キャッシュゾーン名をst_cache、そのゾーンのメモリ使用を256MB、全キャッシュの最大量を10GB
# アクセスが360分無ければ該当キャッシュを削除
proxy_cache_path /var/cache/nginx/static levels=1:2 keys_zone=st_cache:256m max_size=10g inactive=360m;
# キャッシュ時に使用するキーを設定
proxy_cache_key "$scheme://$host$request_uri";
# プロキシサーバのリクエストヘッダに付与するフィールドを設定
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# キャッシュを効かせるために特定のリクエストヘッダを無効にする
proxy_ignore_headers X-Accel-Redirect X-Accel-Expires Cache-Control Expires Set-Cookie;
#/ Proxy Cache settings
# Openfilecache
# キャッシュサーバなのでopen_file_cacheを通常より多めに設定する
open_file_cache max=100000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
#/ Openfilecache
# 各種設定読み込み
include /etc/nginx/conf.d/*.conf;
- 次に
$ sudo vim /etc/nginx/conf.d/ドメイン名.conf
でサーバ個別設定を行います。
ドメイン名.conf
# IP直打ちのアクセスを排除
server {
listen 80 default_server;
listen [::]:80 default_server; # ipv6接続設定
# HTTPSを使わない場合はコメントアウト
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server; # ipv6接続設定
# 無意味なサーバネームを指定することで未定義のHostをフィルタリングする
server_name _;
# ステータスコード444はnginxがレスポンスを返さずに接続を閉じる
return 444;
}
# HTTPSを使わないときはコメントアウト
# HTTPでアクセスしてきたときにHTTPSにリダイレクトする
server {
listen 80;
listen [::]:80;
server_name static.example.com;
return 301 https://static.example.com$request_uri;
}
# リダイレクトを流される側(本体)の設定
server {
# 接続するポート番号を指定
listen 443 ssl http2;
listen [::]:443 ssl http2;
# URLに表示されるドメインまたはIPアドレス
server_name static.example.com;
# ウェブサイト表示時に参照されるディレクトリ
root /var/www/example.com/;
# indexページとして読み込むファイル(indexは各locationにも継承される)
index index.php index.html index.htm;
# サブドメイン直下に直接アクセスしてきたら本体ドメインへ流す
location / {
return 301 http://example.com$request_uri;
}
# 404ページの指定
#error_page 404 /404.html;
# location = /404.html {
#}
# 50*ページの指定
error_page 500 502 503 504 /50x.html;
location = /50x.html {
# 50x.htmlを参照するルートを指定
root /usr/share/nginx/html;
}
# 指定したファイルのみリバースプロキシを行う
location ~ \.(png|jpg|jpeg|gif|ico|css|js|webp|avif)$ {
# プロキシ機能のONOFF
proxy_buffering on;
# アクセス時にURLを上書きしない
proxy_redirect off;
# レスポンスを読み取るために使用されるバッファの数とサイズ
proxy_buffers 4 32k;
# 受信したレスポンスの最初の部分を読み取るために使用されるバッファのサイズ
proxy_buffer_size 32k;
# プロキシキャッシュに使用するゾーン名
proxy_cache st_cache;
# レスポンスパターンごとのキャッシュ時間(例では180分、5分、30分)
proxy_cache_valid 200 180m;
proxy_cache_valid 404 5m;
proxy_cache_valid any 30m;
# プロキシサーバとの接続を確立する際のタイムアウト時間
proxy_connect_timeout 10;
# プロキシサーバにリクエストを送信する際のタイムアウト時間
proxy_send_timeout 10;
# プロキシサーバからのレスポンスを読み取る際のタイムアウト時間
proxy_read_timeout 90;
# 指定したパラメータが発生した際に古いキャッシュを使用
proxy_cache_use_stale timeout invalid_header updating http_500 http_502 http_503 http_504;
# 同一のリクエストが同時間に複数あった場合、リクエストを一纏めにする
proxy_cache_lock on;
# proxy_cache_lockのタイムアウト時間
proxy_cache_lock_timeout 5s;
# キャッシュサーバが受け付けるURLを指定
proxy_pass https://メインサーバのプライベートIPアドレス;
}
# 画像ファイルはユーザーにキャッシュさせる(30日間)
location ~ .*\.(jpe?g|gif|png|ico|webp|avif)\ {
access_log off;
expires 30d;
}
# CSS JSファイルはユーザーにキャッシュさせる(7日間)
location ~ .*\.(css|js)\ {
access_log off;
expires 7d;
}
}
設定が完了したら
$ sudo service nginx restart
でNginxの再起動を行います。これにより、リバースプロキシの設定ができましたので、キャッシュサーバとして稼働することが可能になりました。
ただし、IP直打ち排除やContentSecurityPolicy、Cross-Origin-Resource-Policyなどのセキュリティヘッダを使っている場合にはエラーが発生する可能性があります。対策方法については後述します。
ファイルURLをキャッシュサーバに置き換える
キャッシュサーバの設定は完了しましたが、メインサーバ側でURLの変更を行う必要があります。
例えばhttps://example.com/main.css
というCSSファイルがある場合、これをhttps://static.example.com/main.css
のように、キャッシュサーバのサブドメインに置き換えなければなりません。
動的にページを作るウェブサイトであれば置き換えは比較的簡単ですが、静的コンテンツの場合は手動あるいはツール(Meryなど)を使って変換する必要があります。
このシリーズでも紹介したZolaのget_url
を使っている場合には{{ get_url(path='main.css', cachebust=true) | replace(from="https://", to="https://static.") }}
というようにreplace
を使うことで置き換えを行うことができます。
他にもWordPressの場合、function.php
に以下を記入することで自動的にコンテンツ内の要素がキャッシュサーバに置き換わります。
function.php
// 省略 //
// WordPressで利用する画像のURLをフックで変更する
// https://worklog.be/archives/3217
// キャッシュサーバ url切り替え
function url_replaces( $content ) {
$url1 = '/example.com\/wp-content\/uploads/';
$url2 = 'static.example.com/wp-content/uploads';
$content = preg_replace( $url1, $url2, $content );
return $content;
}
add_filter( 'post_thumbnail_html', 'url_replaces' ); //アイキャッチ画像のフィルターフック
add_filter( 'the_content', 'url_replaces' ); //コンテンツデータのフィルターフック
add_filter( 'widget_text', 'url_replaces' ); //テキストウィジェットのフィルターフック
ただし、上記の方法でも動的に作られない静的ファイルは手動で変換する必要があります。
エラー発生時
リバースプロキシを作成する際、Nginxにupstream prematurely closed connection while reading response header from upstream
というエラーが発生する場合があります。
今回の作成時にいくつかのエラーに遭遇したため、それら原因の対処方法を一部置いておきます。 メインサーバ側が これでIP直打ちに関連するエラーは発生しなくなりました。 ContentSecurityPolicyを使っている場合、キャッシュサーバからコンテンツを読み込む際に これはCSPの設定によって別ドメインからのコンテンツを排除していることが原因です。 例えば この場合には 設定が終われば 次にメインサーバ側の取得されるコンテンツに これによりキャッシュサーバはメインサーバのコンテンツを取得でき、メインサーバはキャッシュサーバのコンテンツを表示することができます。 キャッシュサーバを使う時、設定した項目に対してスペック(特にメモリ)が足りない場合があります。 メモリ不足であることが判明した場合には素直にマシンのスペックを上げるか、Nginxで割り当てした各項目の数値を下げる、もしくはメモリを多く食っているプロセスの停止や削除を行ってください。 今回の設定では、画像(jpg,gif,png,ico,webp,avif)とCSSとJSファイルに限定してキャッシュサーバはリバースプロキシを行っています。 そのため、上記に記載されていない拡張子をキャッシュサーバに要求した場合には、正しく取得できないのでエラーが発生します。 もし他の拡張子を追加する場合には、Nginxの設定でその拡張子を含めるようにしてください。メインサーバがIP直打ちを排除している
server_name _;
でのIP直打ちの排除を行っている場合はエラーが発生するため、変更を行う必要があります(キャッシュサーバがproxy_passで直接IPを指定して繋いでいるため)。$ sudo vim /etc/nginx/conf.d/ドメイン名.conf
で『メインサーバ』の編集を行います。ドメイン名.conf
# server_name _;を使うserverディレクションをコメントアウトする
# IP直打ち等のアクセスを排除
#server {
# listen 80 default_server;
# listen [::]:80 default_server; # ipv6接続設定
#
# listen 443 ssl http2 default_server;
# listen [::]:443 ssl http2 default_server; # ipv6接続設定
#
# # 無意味なサーバネームを指定することで未定義のHostをフィルタリングする
# server_name _;
# # ステータスコード444はnginxがレスポンスを返さずに接続を閉じる
# return 444;
#}
# 代わりにこちらを使う
# IP直打ちでのアクセスを排除
server {
listen 80;
listen [::]:80; # ipv6接続設定
listen 443 ssl http2;
listen [::]:443 ssl http2; # ipv6接続設定
# サーバのIPを指定する
server_name メインサーバのパブリックIPアドレス;
# ステータス444を返す
return 444;
}
# コメントアウトによりdefault_serverがどこにも設定されていないため、
# 本体となるserverディレクションにdefault_serverを付与しておくことを推奨
$ sudo service nginx restart
でNginxを再起動します。CSPを設定している
img-src
やscript-src
、style-src
などが原因でコンテンツが表示できなくなる場合があります。img-src
の場合、同じURLスキームとホスト、ポート番号およびキャッシュサーバを許可するには、img-src 'self' static.example.com;
を指定することでコンテンツを読み込むことが可能となります。script-src
に関してはstrict-dynamic
とnonce
を用いることでより安全な設定を行えますが、この方法では毎回動的にページを生成する必要があるため、キャッシュを使いたい場合や動的に生成されない静的ファイルで管理している場合には使うことができません。CORPやCOEPを設定している
Cross-Origin-Resource-Policy
やCross-Origin-Embedder-Policy
というセキュリティヘッダを使用している場合には、異なるオリジンを跨いでコンテンツを取得することはできません。Access-Control-Allow-Origin
を『キャッシュサーバ』に付与し、メインサーバではキャッシュサーバに取得されるコンテンツにcrossorigin="anonymous"
を付与する必要があります。$ sudo vim /etc/nginx/conf.d/ドメイン名.sh.conf
でヘッダの追加を行います。 ドメイン名.conf
で直接記述しても構いません。ドメイン名.sh.conf
## 省略 ##
# 自サイトのコンテンツを外部で使用させない(CORP)
add_header Cross-Origin-Resource-Policy "cross-origin" always;
# 許可するオリジンを指定
# Access-Control-Allow-Origin "*" はセキュリティ上良くないので記述しない
add_header Access-Control-Allow-Origin "https://メインサーバのドメイン" always;
# 埋め込むリソースに対しCORPを強制的に明示し許可していないリソースを読み込ませない(COEP)
add_header Cross-Origin-Embedder-Policy "require-corp" always;
## 省略 ##
$ sudo service nginx restart
でNginxを再起動します。crossorigin="anonymous"
を付与します。<img src="static.example.com/test.jpg" crossorigin="anonymous">
のようにします。<link rel="stylesheet" href="static.example.com/main.css" type="text/css" media="all" crossorigin="anonymous">
というように記述します。スペックが足りない
$ free -m
で現在のメモリ使用率を調べることができますが、この時freeがほとんど無い場合はメモリ不足が原因でNginxがクラッシュする可能性があります。リバースプロキシに割り当てされていない
次回はWordPressとMariaDBの導入および設定
今回の設定により、リバースプロキシによって静的ファイルを配信するキャッシュサーバの構築がなるべく簡単に行えます。
最初に指摘したように小さなウェブサイトではあまり需要は無いとは思われますが、それでもウェブサーバの応答速度向上やPageInsightsなどのスコア向上に少しでも役立てるかもしれません。
今回のようにサーバを分けてプライベートネットワークで接続する方法を使うと、例えばWordPressであればウェブサーバとデータベースサーバに分けて管理することも可能です(詳細は次回)。
ということで次回はWordPressおよびMariaDBの導入と設定について紹介します。
WordPressもMariaDBも当サイトでは既に使っていませんが、旧サイトでは使用していたため、こちらもメモ代わりとして使いつつ解説していきます。
参考資料
- How to Create a Vultr Virtual Private Cloud (VPC)
- How to Configure a Private Network on Ubuntu
- Ubuntu 20.04 LTSで固定IPアドレスを設定する方法【サーバー編】
- Ubuntu 20.04でIPアドレスを設定する
- Module ngx_http_proxy_module
- Module ngx_http_core_module
- NGINX コンテントのキャッシング
- キャッシュシステムのオリジンサーバアクセスの効率化と Apache Traffic Server
- nginx-WordPress リバースプロキシにおける バッファの最適化
- 【サンプル付き】Nginx のリバースプロキシでキャッシュして高速化する
- nginx proxyキャッシュまとめ
- Nginxで拡張子指定で静的ファイルをキャッシュするときの注意
- nginxで静的コンテンツをキャッシュ配信する方法
- 【実践】Nginx のリバースプロキシでファイルをキャッシュする方法
- nginx open_file_cacheで静的ファイルのcache
- 正規表現を学んでみませんか
- “link”要素~“crossorigin”属性
- Cross Origin なんちゃらの備忘
- なんとなく CORS がわかる...はもう終わりにする。
- やんわり分かる Origin や CORS -とりあえず手を動かしてみよう-
- Nginxでキャッシュをする際の、Cookieに関する注意点
- Nginxをリバースプロキシで使う場合のCORS設定
- 重要! まずは「オリジン」を理解しよう
- Nginxエラー対応ログ
- 安全なウェブサイトの作り方