Beginning To See The Nginx

14 min

language: ja bn en es hi pt ru zh-cn zh-tw

こんにちは、タイトルはアレのもじりです。ふと思い出した曲なだけです。

最近異常に記事更新が多いのは一度気になると止まらなくなってくるのでその時期なだけです。

Nginx

今までとりあえずリバースプロキシ兼キャッシュサーバとして建ててましたが IPv6 対応と共に気になってしまったのでリファクタ含めて設定を見直しました。

ファイル構成

一部は隠してますがこんな感じになってます

├── http.d
│   ├── bot_rate_limit.conf
│   ├── gzip.conf
│   ├── proxy_cache_zones.conf
│   └── proxy_common.conf
├── mime.types
├── mime.types-dist
├── nginx.conf
├── scgi_params
├── selfsigned.crt
├── selfsigned.key
├── sites-enabled
│   ├── 1btc.love.conf
│   ├── btclol.xyz.conf
│   ├── damepo.jp.conf
│   ├── git.soulminingrig.com.conf
│   ├── soulminingrig.com.conf
│   ├── starlink.soulminingrig.com.conf
│   ├── stg.api.1btc.love.conf
├── snippets
│   ├── common_error_pages.conf
│   ├── proxy_headers.conf
│   └── ssl_common.conf
├── uwsgi_params
└── win-utf

ちょっと location ディレクティブ単位で読み込んでいる conf をどの名前のフォルダ管理するか悩みましたがChatGPTと相談してこうなりました。 http.dnginx.confhttp ディレクティブから include しているファイルたちなので結構納得がいってこうなりました。

ただ snipets になったのはちょっと絶妙やなあと思いながらでしたがまあいいでしょう。

それぞれの設定項目をみていきます。

http.d/bot_rate_limit.conf

メタのクローラがやばすぎたので fail2ban だけじゃなく UA 単位でも制限をかけることにしました。

また、 feed/RSS に関してはご丁寧にご連絡いただいたのですがそもそも適応しても意味がないしほぼキャッシュ応答するのでOriginへの到達負荷にはあまりなり得ないとして穴あけしました。

# Bot やリンク展開クローラだけを対象にレート制限をかける
map $http_user_agent $is_bot {
    default 0;
    ~*bot 1;
    ~*crawler 1;
    ~*spider 1;
    ~*facebookexternalhit 1;
    ~*slackbot 1;
    ~*discordbot 1;
    ~*twitterbot 1;
    ~*linkedinbot 1;
    ~*embedly 1;
    ~*quora 1;
    ~*skypeuripreview 1;
    ~*whatsapp 1;
    ~*telegrambot 1;
    ~*applebot 1;
    ~*pingdom 1;
    ~*uptimerobot 1;
}

# stg.api.1btc.love は検証用途のため、Bot であってもレート制限の対象外にする
# key を空文字にすると limit_req_zone ではカウントされない
map $server_name $bot_limit_host_key {
    stg.api.1btc.love "";
    default $binary_remote_addr;
}

# feed.xml / feed.json は Bot であってもレート制限の対象外にする
map $uri $is_feed_path {
    default 0;
    ~*feed\.(xml|json)$ 1;
}

# Bot かつ feed.xml / feed.json 以外のときだけ IP 単位の key を使う
map "$is_bot:$is_feed_path" $bot_limit_key {
    default "";
    "1:0" $bot_limit_host_key;
}

limit_req_zone $bot_limit_key zone=bot:10m rate=1r/s;
limit_req_status 429;
limit_req zone=bot burst=5 nodelay;

もちろん http ディレクティブに対して適応しているので基本的に全適応となりますが最低限穴あけ出来るようにしました。本来はデフォルトで有効になっていたほうがいいものとして判断してます。

UA偽造もといブラウザ偽装して明らかにDoSしてくるならば fail2ban でブロックされて drop 扱いになりしばらくリクエスト送れないので二段構えです。

http.d/gzip.conf

brotli 対応とかしてましたが別途ビルドしたりするとバージョン上げるときにも面倒なのでやめました。 pkg/apt で更新が出来るメリットは大きい。

gzip on;
gzip_vary off;
gzip_proxied any;
gzip_min_length 1024;
gzip_comp_level 7;
gzip_http_version 1.1;
gzip_types text/plain
text/xml
text/css
text/javascript
image/gif
image/png
image/svg+xml
application/javascript
application/json
application/xml
application/x-javascript
application/font-woff
application/font-woff2
application/font-ttf
application/octet-stream;

特に言うことはないかなと、昔いれてなかったけど数年前から gzip_min_length はいれるようにしました。非効率的な圧縮していても無駄かなとおもってちゃんと入れようとしたときですね。

http.d/proxy_cache_zones.conf

絶妙にインデント崩れているのでは、となりますがフォーマッタの制約です。

いらないもの消して zone が4からだったりばらばらになってますがまあ・・・

inactive に関しては7日もhitしてなければ消えて、 use_temp_path に関しては直接キャッシュパスに対してキャッシュするようにしています。 proxy_temp_path /tmp/nginx;
みたいな設定をいれててもそれを介さずにキャッシュするようになり早くなるみたい。

proxy_cache_path /tmp/nginx/zone4 levels=1:2 keys_zone=zone4:10m
inactive=7d
max_size=3g
use_temp_path=off;

proxy_cache_path /tmp/nginx/posts levels=1:2 keys_zone=posts:10m
inactive=7d
max_size=2g
use_temp_path=off;

proxy_cache_path /tmp/nginx/git levels=1:2 keys_zone=git:10m
inactive=7d
max_size=2g
use_temp_path=off;

proxy_cache_path /tmp/nginx/static levels=1:2 keys_zone=static_cache:10m
inactive=7d
max_size=1g
use_temp_path=off;

proxy_cache_path /tmp/nginx/1btc_cache levels=1:2 keys_zone=1btc_cache:10m
inactive=7d
max_size=512m
use_temp_path=off;

http.d/proxy_common.conf

proxy_cache_valid は共通ルールとして設定しておいて、残りは location ディレクティブ側で上書きする運用にしてます。

こうするとキャッシュ設定いれてなくてもキャッシュ運用出来るし

proxy_cache_bypass $http_cookie にしていることでクッキー付きのリクエスト、例えばログインとかしたあとにもログイン前の画面になり得てしまうことを防ぎます。

また、リダイレクトやエラー、そして any にキャッシュ応答させているのは攻撃対策です。これをすれば最低限キャッシュ応答するのでOriginへの異常な到達を防げます。

proxy_temp_path は本来永続化出来るパス先の方が好ましいと思いますが基本的に自分の用途ではそんな場面は無いので /tmp 先にしてます。それなりなトラフィックがあるサイトでこれやるとさっきのこの使わないオプションあったが多分サーバー再起動でキャッシュ全て消えてOrigin負荷高まって障害リスクがある。

proxy_buffering on;

proxy_cache_bypass $http_cookie;
proxy_cache_background_update on;
proxy_cache_key "$scheme$request_method$host$request_uri";
proxy_cache_revalidate on;
proxy_cache_use_stale updating;
proxy_connect_timeout 60;
proxy_no_cache $http_cookie;
proxy_read_timeout 90;
proxy_send_timeout 60;
proxy_temp_path /tmp/nginx;

proxy_cache_valid 200 201 60s;
proxy_cache_valid 301 1d;
proxy_cache_valid 302 3h;
proxy_cache_valid 304 1d;
proxy_cache_valid 404 1m;
proxy_cache_valid any 5s;
proxy_cache_lock on;

nginx.conf

AレコードのIP直接叩かれた場合の専用エラーを返すようにしていたりしているだけでそれほどいうことがありません。

multi_accept をonにしているのは自分のリバプロ兼キャッシュサーバという運用上であまりハイスペにしていない弱小インスタンスなので有効にしています。

もともと http ディレクティブに直書きだった設定たちが目的別に include されるのはかなり見通しがよくなった。

worker_processes auto;
worker_cpu_affinity auto;
worker_rlimit_nofile 65535;

events {
    multi_accept on;
    worker_connections 65535;
}

http {
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    server_tokens off;
    types_hash_max_size 4096;
    client_max_body_size 16M;

    # MINE
    include mime.types;
    default_type application/octet-stream;

    include ./http.d/bot_rate_limit.conf;
    include ./http.d/proxy_common.conf;
    include ./http.d/proxy_cache_zones.conf;
    include ./http.d/gzip.conf;

    server {
        listen 80;
        listen [::]:80;

        server_name 163.44.113.145 91.98.169.80 2400:8500:2002:3317:163:44:113:145;

        include snippets/common_error_pages.conf;
        return 444;
    }

    server {
        listen 443 ssl;
        listen [::]:443 ssl;

        server_name 163.44.113.145 91.98.169.80 2400:8500:2002:3317:163:44:113:145;

        ssl_certificate ./selfsigned.crt;
        ssl_certificate_key ./selfsigned.key;

        include snippets/common_error_pages.conf;

        # IPアドレスへのアクセスを拒否
        return 444;
    }

    ### Damepo.jp
    include ./sites-enabled/damepo.jp.conf;

    ### Soulminingrig My Blog
    include ./sites-enabled/soulminingrig.com.conf;
# ~~~略~~~~
}

sites-enabled/soulminingrig.com.conf

site-enabled 配下は例として一個だけ紹介します。このサイトの設定です。

また、自分がシンボリックリンクとかを張っていないのはいらないものをすぐ消すようにしたいだけです。

実際一部ハードコードされたままだったりする箇所はありますが今も手直し中というだけでご了承ください。

一応、最近 www.soulminingrig.com にしたのですがそれ前は www なしのルートドメインで配信してたので現状まだルートドメインでもリダイレクトせずに受け付けるようにしてます。

検証期間としてサーバキャッシュが応答したか、クライアントキャッシュが応答したかわかりやすいようなヘッダ応答出来るようにしています。

そして、画像とかフォント、cssとかのキャッシュルールもうちょいどうにかできんもんかなとか思いつつうーんとなっています・・・。どうにかならんのか・・・これ・・・。

upstream を設定しているのはバックエンド先増えた場合にすぐ対応出来るようにしてます。まあ現状一個しかないんですが、一番上にあることでどのバックエンド先を向いている設定かわかりやすいです。

upstream backend_sm {
    server 10.1.0.228:8888 max_fails=3 fail_timeout=3s;
    keepalive 16;
    keepalive_timeout 30s;
}

map $uri $static_cache {
    ~\.(jpg|jpeg|png|webp|gif|mp4|css|js|ico|woff2)(\?.*)?$ "public, max-age=604800";
    ~\.html$ "public, max-age=600";
    default "public, max-age=600";
}

map $upstream_cache_status $server_cache_status {
    default $upstream_cache_status;
    "" "NONE";
}

map "$http_if_none_match:$http_if_modified_since" $client_cache_request {
    default "MISS";
    "~.+:.+" "REVALIDATE";
    "~.+:" "REVALIDATE";
    "~:.+" "REVALIDATE";
}

server {
    listen 80;
    listen [::]:80;
    server_name soulminingrig.com www.soulminingrig.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl reuseport backlog=65535 rcvbuf=256k sndbuf=256k fastopen=256 so_keepalive=on;
    listen [::]:443 ssl reuseport backlog=65535 rcvbuf=256k sndbuf=256k fastopen=256 so_keepalive=on ipv6only=on;
    listen 443 quic reuseport;
    listen [::]:443 quic reuseport;
    http2 on;
    http3 on;

    server_name soulminingrig.com www.soulminingrig.com;
    client_max_body_size 50M;

    location ~* \.(jpg|jpeg|png|webp|gif|ico|mp4|js|css|woff2)(\?.*)?$ {
        proxy_pass http://backend_sm;
        include snippets/proxy_headers.conf;

        proxy_http_version 1.1;
        proxy_redirect off;

        proxy_cache static_cache;
        proxy_cache_valid 200 301 302 7d;
        proxy_cache_valid 404 1m;

        proxy_cache_revalidate on;
        proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
        proxy_cache_background_update on;
        proxy_cache_lock on;
        proxy_connect_timeout 3s;
        proxy_read_timeout 15s;

        expires 7d;
        add_header X-Cache-Status $upstream_cache_status always;
        add_header X-Server-Cache-Status $server_cache_status always;
        add_header X-Client-Cache-Request $client_cache_request always;
        add_header X-Client-Cache-Policy "public, max-age=604800" always;
        add_header Cache-Control "public, max-age=604800" always;
    }

    location / {
        proxy_pass http://backend_sm/;
        include snippets/proxy_headers.conf;

        proxy_http_version 1.1;
        proxy_redirect off;

        proxy_cache posts;
        proxy_cache_key $scheme$host$request_uri;

        proxy_cache_valid 200 10m;
        proxy_cache_valid 301 1h;
        proxy_cache_valid 404 1m;

        proxy_cache_revalidate on;
        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_lock on;
        proxy_connect_timeout 3s;
        proxy_read_timeout 15s;

        expires $static_cache;
        add_header X-Cache-Status $upstream_cache_status always;
        add_header X-Server-Cache-Status $server_cache_status always;
        add_header X-Client-Cache-Request $client_cache_request always;
        add_header X-Client-Cache-Policy $static_cache always;
        add_header Cache-Control $static_cache always;
    }

    include snippets/common_error_pages.conf;
    include snippets/ssl_common.conf;
    ssl_certificate /hoge/fullchain.pem; # managed by Certbot
    ssl_certificate_key /hoge/oulminingrig.com/privkey.pem;
    # managed by Certbot

}

おまけ: nginxfmt.py

これすごくいいです。

フォーマッタなのですが一番クウォリティ高い気がする。

AURにあります。

yay -S nginx-config-formatter

使い方は nginxfmt.py example.conf でフォーマットしてくれるので

find . -name "*conf" | xargs -I{} nginxfmt.py {}

で一括フォーマットしてくれます。

ちょっと前まで nginxbeautifier をいれてたんですがリダイレクト周りの構文がぶっ壊れたりしたので鞍替えしました。

Related Posts