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_headerproxy_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をすると・・・ヘッダーを見てみよう👀
しょうもないものが確認できます・・・

PGP --- Contact --- Machines --- cat -v