ランダム遅延プロキシ
本番環境では当たり前のようにネットワーク遅延が発生しますが、ローカルや検証環境で「それっぽい遅延」を再現するのは意外と面倒です。この記事では nginx の JavaScript エンジン njs を使い、既存のサーバー構成を一切変えずにランダム遅延を差し込むミニマルなプロキシを作ります。
njsについては以下を見てみてください。
こんな場面に使えます
- ローディング UI のテスト
- ローカルが速すぎて「ロード中」の表示が一瞬で消えてしまう問題を解消したい
- タイムアウト処理の検証
- クライアント側のタイムアウト実装が正しく動くか確認したい
- オブザーバビリティの動作確認
- Datadog などのダッシュボードでレイテンシの変化を可視化・アラート検知させたい
- カオスエンジニアリングの入口
- 本格的なカオスツールを導入する前に、手軽に遅延の影響を体験したい
完成形
wait.js(njs モジュール)
function hello(r) {
var waitMs = Math.floor(Math.random() * 1000);
new Promise(resolve => setTimeout(resolve, waitMs))
.then(() => ngx.fetch(r.variables['upstream'] + r.uri))
.then(res => res.arrayBuffer()
.then(buf => r.return(res.status, buf)))
.catch(e => r.return(502, String(e) + '\n'));
}
export default { hello };
nginx.conf(抜粋)
load_module modules/ngx_http_js_module.so;
js_import wait from wait.js;
# ウェイト付きの口(テスト用)
server {
listen 8080;
set $upstream "http://127.0.0.1:80";
location / {
js_content wait.hello;
}
}
# 本物のコンテンツを返す口
server {
listen 80;
root /usr/share/nginx/html;
location / {
try_files $uri $uri/ =404;
}
}
仕組みの解説
ポートで役割を分離する
ポイントは ウェイトを担うポート(8080)とコンテンツを返すポート(80)を分けることです。両方を同じ location / で処理すると、8080 → 8080 の無限ループになってしまいます。
クライアント → :8080 (wait.js でウェイト) → :80 (静的ファイル配信)
テスト時はエンドポイントを :8080 に向けるだけで済み、既存の :80 の設定には一切手を入れません。
njs の ngx.fetch() でプロキシ
ngx.fetch() は njs が提供する Fetch API 互換の関数です。r.uri にはクライアントが要求したパス(例: /anyurl.html)が入っているので、$upstream と結合するだけで転送先URLが完成します。
GET /anyurl.html
→ waitMs 待機
→ ngx.fetch("http://127.0.0.1:80/anyurl.html")
→ レスポンスをそのままクライアントへ
setTimeout で非同期ウェイト
njs では setTimeout がサポートされており、Promise と組み合わせることで nginx のイベントループをブロックせずに待機できます。他のリクエストの処理を妨げない点が重要です。
使い方
1. ファイルを配置する
js_import wait from wait.js; は nginx の設定ディレクトリ(通常 /etc/nginx/)からの相対パスで解決されるため、以下のパスに置きます。
cp wait.js /etc/nginx/wait.js
2. nginx.conf に設定を追加して再読み込み
nginx -t && nginx -s reload
3. テスト時はポートを 8080 に向けるだけ
# 従来
curl http://localhost/api/data
# ウェイト付き
curl http://127.0.0.1:8080/api/data
これだけで 0〜1000ms のランダム遅延が自動的に挟まります。
実際のアクセスログはこのようになります。
127.0.0.1 - - [25/Feb/2026:19:24:26 +0900] "GET / HTTP/1.1" 200 615 0.860 "-" "curl/8.5.0"
127.0.0.1 - - [25/Feb/2026:19:25:28 +0900] "GET / HTTP/1.1" 200 615 0.157 "-" "curl/8.5.0"
127.0.0.1 - - [25/Feb/2026:19:25:29 +0900] "GET / HTTP/1.1" 200 615 0.318 "-" "curl/8.5.0"
127.0.0.1 - - [25/Feb/2026:19:25:31 +0900] "GET / HTTP/1.1" 200 615 0.961 "-" "curl/8.5.0"
127.0.0.1 - - [25/Feb/2026:19:25:33 +0900] "GET / HTTP/1.1" 200 615 0.901 "-" "curl/8.5.0"
127.0.0.1 - - [25/Feb/2026:19:25:34 +0900] "GET / HTTP/1.1" 200 615 0.183 "-" "curl/8.5.0"
8列目がレスポンスタイム(秒)です。0.157〜0.961 とリクエストごとにばらつきが出ており、本番環境に近い挙動を再現できています。Datadog などに取り込めば、レイテンシの分布がヒストグラムとして可視化されアラートの動作確認にも使えます。
なお、レスポンスタイムはデフォルトの nginx ログフォーマットには含まれていません。Datadog の nginx インテグレーションドキュメントの「注:」にもその旨が記載されており、あわせて以下のフォーマットが紹介されています。
http {
log_format nginx '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent $request_time '
'"$http_referer" "$http_user_agent"';
access_log /var/log/nginx/access.log nginx;
}
このフォーマットを設定することで、レスポンスタイム($request_time)がログに記録され、オブザーバビリティツールでレイテンシのメトリクス化やアラート設定ができるようになります。
カスタマイズのヒント
ウェイト範囲を変えたい場合は wait.js の該当行を編集します。
// 200ms〜2000ms に変更する例
var waitMs = Math.floor(Math.random() * 1800) + 200;
アップストリームを別サーバーに向けることもできます。
set $upstream "http://api.internal:3000";
動作要件
- nginx 1.25.3 以降(njs 同梱)
- または別途
nginx-module-njsパッケージをインストール
バージョン確認は以下で行えます。
nginx -V 2>&1 | grep njs
トラブルシューティング
Error: no resolver defined
ngx.fetch() はホスト名を DNS で解決しようとします。localhost のようなホスト名を使うとリゾルバ未設定の場合にこのエラーが出ます。アップストリームには IP アドレスを直接指定することで回避できます。
# NG: ホスト名はDNS解決が必要
set $upstream "http://localhost:80";
# OK: IPアドレスなら解決不要
set $upstream "http://127.0.0.1:80";
外部ホスト名を使いたい場合は server ブロック内に resolver を追加します。
resolver 8.8.8.8;
set $upstream "http://api.example.com";
まとめ
10行の njs スクリプトと数行の nginx 設定で、既存環境を壊さずにランダム遅延プロキシが完成します。ローカル開発でのUI確認から、オブザーバビリティツールのダッシュボード検証まで、幅広い用途に使い回せます。