Nginxでリバースプロキシしている場合にバックエンド側の正常性キャッシュを返すのとNginxのリファクタリング
6 min read
こんにちは、無能です。
Nginxで正常性キャッシュが維持できるかやってたらできたので書いておきます。
目的
このサーバは現在WireGuard上の内部IP経由で自宅のサーバのHTTPにリバースプロキシをしています。
このリバースプロキシで、バックエンド側が死んでもNginx側が持っているキャッシュを返せればいいので、やってみました。
特にLumeの場合はビルドするときにdest先のフォルダの中身消してから再構築しているのでビルドコケたら何もない状態になって不安定で、これをある主スクリプトで頑張って運用してましたがコケるときはコケる。でもこのスクリプトも全部回収して完全に動作するようにはしまいした。
ただし正常性キャッシュを返し続けることができれば他にも応用できるため、実現したく行ってみました。
現状の設定
現在この設定とそれぞれのコメントを追加しました。
upstream backend_sm {
バックエンドとのTCPコネクション系
バックエンドへのリクエスト回数
一つしかないので意味ないがupstreamディレクティブで管理するとset
とかの変数格納より管理しやすい(と感じる)
特にNginxの場合は組み込み変数がそれなりに多いので記述時には$
つけないことでバックエンドが明示的になる気がする
後述するが、keepaliveを入れているのはTCPコネクションこの間貼って正常性確認としてNginx側が正常性キャッシュを返すかの応答もしやすくするため
ここで指定してるIPがWireGuard上のInternal IP
server 10.1.0.20:88 max_fails=3 fail_timeout=15s;
keepalive 16;
keepalive_timeout 30s;
}
map $uri $static_cache {
静的ファイルのキャッシュ設定
基本画像とかJS, フォント系は大きく変わることないのでデフォで1年
ブラウザ側だと別に一週間くらいであればクライアント側の負荷もそんなにない
サーバー側の差分チェックが5分間間隔で走ってビルドされるので7分だとちょうどいいかなと思ってhtmlはそうした
ただ、Lumeの場合htmlだと https://test.com/posts/ みたいな感じが記事ページになるので最終的にはdefaultにマッチしているはず、自動でindex.htmlが解釈されるヤツ
参考: Cache-Control - HTTP | MDN
~\.(jpg|jpeg|png|webp|gif|mp4|css|js|ico|woff2)(\?.*)?$ "public, max-age=604800";
~\.html$ "public, max-age=420";
default "public, max-age=420";
}
server {
# HTTPをリダイレクト
listen 80;
server_name soulminingrig.com www.soulminingrig.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
QUIC
QUIC用のListenとして443/udpをListenする
こんな感じ↓なのでサーバ側は443/udpでポート開放する必要がある
# lsof | grep nginx | grep UDP
nginx 38088 root 10u IPv4 0xfffff80012541540 0 UDP *:https->*:*
nginx 38088 root 11u IPv6 0xfffff80012541c40 0 UDP *:https
nginx 38089 www 10u IPv4 0xfffff80012541540 0 UDP *:https->*:*
nginx 38089 www 11u IPv6 0xfffff80012541c40 0 UDP *:https
nginx 38090 www 10u IPv4 0xfffff80012541540 0 UDP *:https->*:*
nginx 38090 www 11u IPv6 0xfffff80012541c40 0 UDP *:https
listen 443 quic reuseport;
listen [::]:443 quic reuseport;
HTTP/2 と HTTP/3
HTTP/2とHTTP/3をつかう
しかし、HTTP/2を使っている状態で今回のようなバックエンド側HTTP/1だとcurl
したときにRFC違反で怒らる
これは無印良品も近いエラーが返ってくる
$ curl -I "https://www.muji.com/jp/ja/store"curl: (92) HTTP/2 stream 1 was not closed cleanly: INTERNAL_ERROR (err 2)
前に注文しようとしたときのログインで完全なステージングドメインstg-muji.com
だったかが戻りとして返ってきてバグ報告したが、キャッシュ消してと言われただけで終わった・・・
間違いなくキャッシュの問題ではないはずだけれど・・・
HTTP/2で対応していないHTTPヘッダーをNginx側で proxy_hide_header
で除去すれば行けてしまう
私の場合Upgrade
ヘッダーを除去してる
http2 on;
http3 on;
server_name soulminingrig.com www.soulminingrig.com;
client_max_body_size 50M;
location / {
proxy_pass http://backend_sm/;
共通ルールを作ってproxy_set_header
とproxy_hide_header
してinclude
include proxy_headers.conf;
proxy_set_header X-Forwarded-Host $host;
一応明示的にバックエンド間の疎通はHTTP/1.1
にするように
proxy_http_version 1.1;
proxy_redirect off;
proxy_cache_revalidate
Nginx側でETag / Last-Modified
をバックエンドに問い合わせして変更をチェックする
proxy_cache_revalidate on;
proxy_ignore_headers Cache-Control Expires;
正常性キャッシュ系
バックエンドが死んでたら正常性キャッシュを返す
本当は400系は入れないほうがいいけど、Lume
自体がフォルダの中身消すとバックエンドは404返すので400系も入れている
proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504 http_403 http_404;
proxy_cache_background_update on;
バックエンド死んでるか確認
これで駄目だったらproxy_cache_use_stale
の正常性キャッシュを返す
proxy_connect_timeout 3s;
proxy_read_timeout 15s;
proxy_cache posts;
expires $static_cache;
add_header Cache-Control $static_cache always;
add_header X-Content-Type-Options nosniff always;
add_header Content-Security-Policy "upgrade-insecure-requests" always;
add_header Alt-Svc 'h3=":443"; ma=86400' always;
専用エラーページ
error_page
ディレクティブで専用エラーページ表示する用
proxy_intercept_errors on;
}
設定のモジュール化
エラーページとSSL系の設定を外に出して再利用可能に
include common_error_pages.conf;
include ssl_common.conf;
ssl_certificate /usr/local/etc/letsencrypt/live/soulminingrig.com/fullchain.pem;
ssl_certificate_key /usr/local/etc/letsencrypt/live/soulminingrig.com/privkey.pem;
}
設定ファイルを外部モジュール化
この設定を入れている
proxy_headers.conf
proxy_hide_header
は前述の通り あまりここには興味が無いので、というかお決まり的な部分があり割愛
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_hide_header Upgrade;
ssl_common.conf
基本的には/usr/local/etc/letsencrypt/options-ssl-nginx.conf
を参考にして作成
ssl_prefer_server_ciphers
: サーバの指定した暗号化方式を使用
ssl_session_cache
: TLS通信キャッシュ, この期間のセッション維持
ssl_session_timeout
: TLS通信自体のタイムアウト
ssl_ciphers
: 使用する暗号化方式
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:le_nginx_SSL:10m;
ssl_session_timeout 10m;
ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GC
common_error_pages.conf
エラーページ用
error_page
: 対象ステータスコードが返したときのページパス
実際には対象*.html
ファイルはハッシュした値の長いファイル名が入ってます。
error_page 400 /400.html;
error_page 403 /403.html;
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
# お遊びで入れている画像系用のエイリアス
location /pic/ {
alias /usr/local/www/error/pic/;
}
location = /400.html {
root /usr/local/www/error;
internal;
add_header Cache-Control "public, max-age=30";
}
location = /403.html {
root /usr/local/www/error;
internal;
add_header Cache-Control "public, max-age=30";
}
location = /404.html {
root /usr/local/www/error;
internal;
add_header Cache-Control "public, max-age=30";
}
location = /500.html {
root /usr/local/www/error;
internal;
add_header Cache-Control "public, max-age=30";
}
色々やっていたらまるまる半日くらいかかった・・・。
疲れたのでココまで。
おまけ
みんな大好きおまけコーナー
汚れがちなNginxの設定ファイルをきれいにしてくれる
GitHub - vasilevich/nginxbeautifier: Format and beautify nginx config files
ちなみにエラーページ表示されるとお遊びとしてランダムな画像が表示されます。
また、よろしくお願いします。
そ、し、て・・・
家のサイトにcurl -I
をすると・・・ヘッダーを見てみよう👀
しょうもないものが確認できます・・・