
今回は、この LIFEWORK Blog Next でサイドバーを設置したときに、 503エラーが頻発して困ったときの話です。
【Next.jsブログ構築】Next.jsブログ全ページにサイドバーと人気記事ランキングを実装する方法
ブログにサイドバーを追加して、デプロイしました。 きれいに表示されているのを確認して、ひと安心。
そのつもりでした。
ところがトップページ以外を開いてみると、503エラーが表示されていました。 「サービスが利用できません」
その後は、個別記事のページでも、カテゴリーページでも、トップページでも、 ずっと503エラーが頻発しました。
作業は順調に進んでいて、特にエラーも発生していなかったのに なぜエラーになるのかわからず、とても混乱しました。
何が起きていたのか
最初に確認したのは、エラーの再現条件でした。
トップページは表示できます。 でも個別記事ページやカテゴリーページを開くと503になります。 しかも「必ず」ではなく、最初の数ページは開けて、そのあとから崩れ始める。 一度崩れると、トップページに戻っても503になります。
これはランダムな不具合ではなく、何か条件があるはずだと思いました。
次に試したのは、 Next.js に直接アクセスする方法です。
Nginx を経由せず、ポート3000に直接ループでリクエストを送ります。
bashfor i in {1..25}; do curl -s -o /dev/null -w "%{http_code} " http://localhost:3000/; done
結果は全部 200 でした。 Next.js 自体は完全に正常です。
今度は Nginx 経由( HTTPS )で同じことをします。
bashfor i in {1..25}; do curl -s -o /dev/null -w "%{http_code} " https://next.lifework-blog.com/; done
結果:200 200 200 200 200 200 503 503 503...
6件目から503が始まります。
原因は Next.js ではなく Nginx にある。そこまでは特定できました。
見つかった原因は2つ、どちらも同じ回のコードに潜んでいた
調査を進めると、問題の原因が2つ見つかりました。
どちらも、数回前の作業で書いた Nginx の設定が原因でした。
原因①:Connection ヘッダーの誤設定
Nginx の設定ファイルに、こんな記述があります。
nginxproxy_set_header Connection 'upgrade';
Connection: upgrade は、本来 WebSocket の接続を確立するときだけ使うヘッダーです。
WebSocket は、サーバーとブラウザが常時接続してリアルタイムでやり取りするための仕組みで、チャットや通知機能などで使われます。
ところが上の書き方だと、通常の HTTP リクエストにも常にこのヘッダーを送り続けます。
Nginx には「キープアライブ接続」という仕組みがあります。
1つの接続を使い回すことで、リクエストのたびに接続を張り直すコストを省く仕組みです。
Connection: upgrade を常に送ると、この仕組みが正しく動かなくなります。
そして接続プールが壊れると503エラーになる、という流れでした。
なぜ今まで気づかなかったのか
この設定は、以前の作業から存在していました。 それなのに、なぜ今回まで問題が出なかったのか。
今回のサイドバーを追加したことで、1ページを表示するときのリクエスト数が増えたからです。 以前は1ページ = 1リクエストに近い状態でしたが、サイドバーを加えたことで「ページ本体」に加えて「ランキング API 」「閲覧数 API 」のリクエストも一緒に飛ぶようになりました。
ずっと潜在していたバグが、機能追加をきっかけに表面化したのです。
サイドバーを追加するまでは、普通にサイトを閲覧できていたので、バグがあるということには気づいていませんでした。 いろいろとやってみないと分からないことが多いということにも気づかされましたが、こうして自分自身でサイトを作っていくことの難しさと同時に、面白さもあらためて感じました。
原因②:レート制限の設計ミス
nginx.conf にもう1つ問題がありました。
nginxmap $request_method $nextjs_post_limit_key { POST $binary_remote_addr; default ""; } limit_req_zone $nextjs_post_limit_key zone=nextjs_post:10m rate=10r/m;
POST リクエストだけをレート制限しようとした設計です。
default "" と書いた部分が問題でした。
GET リクエストのキーが空文字列になります。
Nginx は空文字列のキーを「全ユーザー共通の1つのバケット」として扱います。
つまり、「世界中のユーザーの GET リクエストを合算して、上限に達したら503を返す」という動きになっていました。
意図と正反対のことが起きていたわけです。
修正した内容
Connection ヘッダーの修正
map ディレクティブを使って、通常 HTTP と WebSocket を動的に切り替えます。
設定ファイルの冒頭( upstream ブロックの前)に追加します。
nginxmap $http_upgrade $connection_upgrade { default upgrade; '' ""; }
location / ブロック内の記述を変更します。
変更前:
nginxproxy_set_header Connection 'upgrade';
変更後:
nginxproxy_set_header Connection $connection_upgrade;
$http_upgrade には、クライアントから来た Upgrade ヘッダーの値が入ります。
WebSocket の接続時は upgrade が入るので Connection: upgrade を送ります。
通常の HTTP リクエストのときは空(ヘッダーを除去)になります。
これにより Nginx のキープアライブ接続が正しく機能するようになります。
レート制限ゾーンの修正
問題のあった nextjs_post ゾーン定義を nginx.conf から削除しました。
Server Action 攻撃への対策は、サイト設定ファイルで if ディレクティブを使う方式に切り替えています。
nginxif ($http_next_action) { return 444; }
設定の反映
bashsudo nginx -t && sudo systemctl reload nginx
nginx: configuration file ... test is successful と表示されれば成功です。
修正後の結果
503エラーは完全になくなりました。
同じコマンドで25回ループしても、全て 200 が返ります。
今回の出来事で、コードは「動いていればよい」というものではないと感じました。 問題が表面化していないだけで、別のコードが潜在的に壊れている可能性がある。
機能を追加したことでバグが見つかったというのは、ある意味では機能追加が正しく機能したとも言えます。
これからも少しずつ、この LIFEWORK Blog Next の開発を進めていきたいと思います。
この記事で使った Nginx の設定の詳細については、メインブログで解説しています。
ぜひご覧ください。





