TCPのポート枯渇とTIME_WAIT ── 外部APIを大量に呼ぶ業務アプリがある日つながらなくなる原因と直す順番
· 更新日: · 小村 豪 · TCP, TIME_WAIT, ポート枯渇, HttpClient, C#, .NET, Windows, ネットワーク, トラブルシューティング
毎日問題なく動いていた受注の取り込みや在庫の同期が、ある日の繁忙時間帯にだけ、外部の API につながらなくなることがあります。CPU にもメモリにも余裕があり、再起動するとしばらくは直ります。こうした症状の原因の一つが TCP のポート枯渇です。この記事では、その症状が出たときに何を調べ、どの順番で直すかをまとめます。
答えの要点は3つです。まず、ポートがどこで尽きているかを分けます。自分のマシンの動的ポートか、途中の NAT かで、確かめ方も直し方も変わります。次に、TIME_WAIT の数ではなく外向き接続の一斉失敗、イベントID 4227 と 4231、特定のプロセスへの偏りで枯渇を確かめます。直すときは、接続の使い回し、並列数の上限、リトライの間隔の順に見直し、OS の設定を変えるのは最後の一時しのぎにします。
対象は Windows の上で外部の API を呼ぶ業務アプリ(.NET の HttpClient を中心に、独自の TCP 通信も含む)です。HttpClient を毎回作り直してはいけない理由と、エフェメラルポートの基本は「HttpClientをusingで囲んではいけない」で説明しました。この記事はその続編で、症状が出たあとの切り分けと直し方を扱います。コード例は .NET 6 以降の SocketsHttpHandler を前提にしていて、.NET Framework の HttpClient には当てはまらない設定があります1。内容は2026年11月8日に Microsoft の資料と RFC で確かめたものです。PowerShell のコマンドは GitHub Actions の Windows Server 2025 のランナーで、Windows PowerShell 5.1 と PowerShell 7 の両方で動くことを確かめました。C# のコードは .NET 10 でビルドし、設定した HttpClient を例外なく作れることを確かめました。API への送信は試していません。
つながらなくなる業務アプリの典型
外部の API を大量に呼ぶ業務アプリでは、次のような経過をたどることが多くあります。普段は問題がなく、月末の締めや大口の取り込みのように通信が集中した時間帯だけ、接続の失敗やタイムアウトが続きます。アプリやサーバーを再起動すると直りますが、しばらくすると同じ症状が戻ります。Microsoft のポート枯渇の資料も、再起動で一時的に直っても時間がたつと症状が戻ることを、枯渇の特徴として挙げています2。
原因を調べる前に、ポートがどこで尽きているかを考えます。候補は自分のマシンの動的ポートと、途中で通信を変換している NAT です。相手のサーバーに残る TIME_WAIT は、あとで説明するとおり、こちらのポートを減らしません。ただし、相手側の設定によっては、同じ送信元ポートからすぐに張り直した接続を受け付けないことがあります。Azure の資料も、宛先のファイアウォールなどが送信元ポートに待ち時間を設けている場合に備えて、NAT Gateway が同じ宛先へのポートの再利用に待ち時間を置くと説明しています3。
flowchart TB
accTitle: つながらなくなったときに疑う場所
accDescr: 外部APIにつながらなくなったとき、自分のマシンの動的ポートと途中のNATのポートを疑い、相手のサーバーに残るTIME_WAITはこちらのポートを減らさないが、同じ送信元ポートからの張り直しを相手が受け付けないことはあると示す。
SYM["外部APIにつながらない"] --> LOCAL["自分のマシンの動的ポート"]
SYM --> NAT["途中のNATのポート"]
SYM -.-> SRV["相手のサーバーのTIME_WAIT"]
SRV -.-> NOT["こちらのポートは減らない"]
SRV -.-> REJ["すぐの張り直しは拒まれうる"]
図1: 疑う場所は自分のマシンと途中のNATで、相手のサーバーのTIME_WAITはこちらのポートを減らさない。
図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全7件、根拠・確度つき)と主要概念の定義は知識マップ詳細ページにまとめています。データ: JSON-LD / Turtle
TIME_WAIT が残る側
TCP の接続を閉じるとき、原則として先に閉じた側のソケットが TIME-WAIT 状態でしばらく残ります。RFC 9293 は、能動的に閉じた側が 2×MSL(Maximum Segment Lifetime の2倍)の間この状態にとどまらなければならないと定め、MSL を2分としています4。遅れて届いた古いパケットが、同じ組み合わせで張り直した新しい接続に紛れ込まないようにするためです。
flowchart TB
accTitle: TIME_WAITが残る側
accDescr: TCPの接続では、先に閉じた側のソケットがTIME-WAIT状態で2×MSLの間残り、後から閉じた側には残らないことを示す。
A["先に閉じた側"] --> TW["TIME-WAITで残る"]
TW --> WAIT["2×MSLのあいだ再利用できない"]
B["後から閉じた側"] --> FREE["TIME-WAITは残らない"]
図2: TIME-WAITは原則として先に閉じた側に残る。
つまり、クライアントが先に閉じれば TIME_WAIT はクライアントに残り、サーバーが先に閉じればサーバーに残ります。サーバーに残る TIME_WAIT がサーバーのエフェメラルポートを減らさないことは、切り分けのうえで大事な点です。Microsoft の資料も受け付けた側の接続にはエフェメラルポートが要らないと書いています2。ポート枯渇で困るのは、外へ接続を張る側です。
Windows で TIME_WAIT がどれだけ続くかは、資料によって書き方が違います。Microsoft のポート枯渇の資料は既定で4分としています2。一方、TIME_WAIT の長さを決めるレジストリ値 TcpTimedWaitDelay の資料は、既定値を120秒としています5。どちらかの値を前提に計算するより、実際の環境で TIME_WAIT の接続がどれだけの時間で減るかを見るほうが確かです。
同じ宛先への新しい接続の上限
TIME_WAIT のあいだは、同じソケットの組(自分側のアドレスとポート、相手側のアドレスとポート)を使えません5。TCP の接続はこの組で区別されるので4、同じ宛先へ新しい接続を張り続けると、使えるポートの数を TIME_WAIT の長さで割った数が、1秒あたりに張れる新しい接続の目安になります。
既定の動的ポートの範囲は 49152〜65535 の 16384 個です2。TIME_WAIT が120秒なら1秒あたり約136接続、240秒なら約68接続が目安になります。毎回新しい接続を張るアプリが同じ API を1秒に100回呼ぶと、TIME_WAIT が4分の環境では、この目安を超えます。
flowchart TB
accTitle: 同じ宛先への新しい接続の上限の目安
accDescr: 同じ宛先への新しい接続は、TIME_WAITのあいだ同じソケットの組を使えないので、使えるポートの数をTIME_WAITの長さで割った数が1秒あたりの上限の目安になり、既定の16384個なら120秒で約136接続、240秒で約68接続になることを示す。
PORT["使えるポート 16384個"] --> DIV["TIME_WAITの長さで割る"]
DIV --> R120["120秒なら 約136接続/秒"]
DIV --> R240["240秒なら 約68接続/秒"]
図3: 同じ宛先へ張れる新しい接続の数は、ポートの数とTIME_WAITの長さで決まる。
この計算は接続を毎回張って閉じる場合の目安です。接続を使い回していれば、新しい接続はほとんど張られないので、この上限には近づきません。
ポートが尽きる場所ごとの症状
自分のマシンの動的ポート
自分のマシンの動的ポートが尽きると、そのマシンからの外向きの接続が軒並み失敗します。Microsoft の資料は症状として、ドメインのアカウントでサインインできない、グループポリシーの更新が失敗する、ファイル共有に入れない、そのサーバーから別のサーバーへ RDP できない、といったものを挙げています2。業務アプリだけでなく同じマシンのほかの通信も一緒に失敗するのが特徴です。
途中の NAT のポート
クラウドの仮想マシンから外部の API を呼ぶ場合は、途中の NAT のポートが先に尽きることがあります。Azure の資料は、外向き接続の問題の多くが SNAT ポートの枯渇と接続のタイムアウトによるものだとしています3。Azure Load Balancer はバックエンドの仮想マシンごとに決まった数の SNAT ポートを割り当てます。既定の割り当てはバックエンドプールの台数に応じた控えめな数で、資料の例では1台あたり 1,024 個です3。
この場合、仮想マシンの動的ポートにはまだ余裕があっても、SNAT ポートが先に尽きることがあります。仮想マシンの中の接続の数を見るだけでは、枯渇に気づけないことがあります。Azure の資料は Load Balancer のメトリックで SNAT ポートの使用量を監視するよう勧めています3。クラウドでなくても、社内のネットワークとインターネットの境目で NAT をしている機器があるなら、その機器の状態も確かめます。
flowchart TB
accTitle: ポートが尽きる場所ごとの症状
accDescr: 自分のマシンの動的ポートが尽きるとドメインのサインインやファイル共有を含む外向き接続が軒並み失敗し、途中のNATのポートはマシンのポートより先に尽きることがあり、マシンの中の接続の数を見るだけでは気づけないことを示す。
LOCAL["自分のマシンの動的ポート"] --> L1["外向き接続が軒並み失敗"]
L1 --> L2["サインインや共有も失敗"]
NAT["途中のNATのポート"] --> N1["マシンより先に尽きうる"]
N1 --> N2["マシンの中では気づけない"]
図4: 自分のマシンで尽きればほかの通信も止まり、NATで尽きるとマシンの中を見るだけでは気づけない。
切り分けの順番
失敗している宛先の範囲で、疑う場所を絞れます。ほかのサーバーへの共有や RDP も失敗するなら、Microsoft の資料の手順に沿って、自分のマシンの動的ポートが尽きていないかを確かめます2。1つの宛先だけが失敗する場合でも、自分のマシンの枯渇は否定できません。前の節の目安は動的ポートの範囲全体を使った計算なので、同じ宛先へその目安を超えて接続を張っているなら、マシンのポートも使い切りかけています。そのため、この場合もイベントID 4227 と 4231 を確かめたうえで、同じ宛先への張りすぎ、途中の NAT、相手のサーバー側の制限を疑います。
flowchart TB
accTitle: ポート枯渇を疑うときの切り分け
accDescr: ほかのサーバーへの共有やRDPも失敗するなら自分のマシンのポート枯渇を確かめ、1つの宛先だけが失敗する場合もイベントID 4227と4231を確かめたうえで、同じ宛先への接続の張りすぎ、途中のNAT、相手のサーバー側の制限を疑うことを示す。
Q{"ほかの宛先もつながらないか"} -->|"すべて失敗"| L["自分のマシンの枯渇を確かめる"]
Q -->|"1つの宛先だけ"| O["イベント4227と4231も確かめる"]
O --> SAME["同じ宛先への張りすぎ"]
O --> NAT["NATのポートの使用量"]
O --> SRV["相手のサーバーの制限"]
図5: 失敗している宛先の範囲で、疑う場所が変わる。
自分のマシンで枯渇を確かめる
TIME_WAIT の接続が多いことだけでは、いまポートが尽きている証拠になりません。Microsoft の資料は、TIME_WAIT が多いのは接続をたくさん作っていることを示すだけで、外向き接続の一斉失敗とイベントログを先に確かめるよう求めています2。確かめる順番は次のとおりです。
- ほかのサーバーのファイル共有を開く、別のサーバーへ RDP する、といった外向きの接続を試します。すべて失敗するなら次に進みます。
-
システムログでイベントID 4227 と 4231 を探します。PowerShell では次のように調べられます。
Get-WinEvent -FilterHashtable @{ LogName = 'System'; Id = 4227, 4231 } -MaxEvents 20 -ErrorAction SilentlyContinue | Select-Object TimeCreated, Id, Message - 管理者のコマンドプロンプトで
netstat -anobqを実行し、TIME_WAIT や BOUND の接続が1つのプロセスに偏っていないかを見ます。BOUND は TIME_WAIT を抜けたポートの状態です。Windows 10 のnetstatとGet-NetTCPConnectionで見られ、Windows 8.1 と Windows Server 2012 R2 にも同じ機能を加える更新プログラムがあります2。
Microsoft の資料は、netsh trace で採ったトレースに STATUS_TOO_MANY_ADDRESSES が記録されていれば、ポート枯渇と断定できるとしています2。手順は資料にあるので、ここでは省きます。
flowchart TB
accTitle: 自分のマシンでポート枯渇を確かめる順番
accDescr: 外向き接続がすべて失敗するかを試し、システムログのイベントID 4227と4231を探し、netstatでTIME_WAITやBOUNDが1つのプロセスに偏っていないかを見て、必要ならnetsh traceのSTATUS_TOO_MANY_ADDRESSESで断定することを示す。
S1["外向き接続を試す"] --> S2["イベントID 4227と4231"]
S2 --> S3["プロセスごとの偏り"]
S3 -.-> S4["netsh traceで断定"]
図6: TIME_WAITの数ではなく接続の失敗、イベント、プロセスの偏りの順に確かめる。
増え方を記録する
症状が出たときだけ調べても、増え方は分かりません。次のスクリプトは1分ごとに状態とプロセスごとの TCP 接続の数を CSV に追記します。Ctrl+C で止めます。
# 1分ごとに、状態とプロセスごとの TCP 接続の数を CSV に追記する
$log = "$env:USERPROFILE\tcp-states.csv"
while ($true) {
$now = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
Get-NetTCPConnection |
Group-Object State, OwningProcess |
ForEach-Object {
$state, $procId = $_.Name -split ', '
[pscustomobject]@{
Time = $now
State = $state
ProcId = $procId
Process = $(if ($procId -ne '0') { (Get-Process -Id $procId -ErrorAction SilentlyContinue).ProcessName })
Count = $_.Count
}
} |
Export-Csv -Path $log -Append -NoTypeInformation -Encoding UTF8
Start-Sleep -Seconds 60
}
TIME_WAIT の接続は決まった時間で消えます4。負荷が止まったあとも TimeWait の数が減らないなら、まだ新しい接続を作り続けているプロセスがあります。一方、負荷が止まっても Bound の数が減らないプロセスは、ポートを閉じずに抱えている疑いがあります。Microsoft の資料も、BOUND の行が最も多いプロセスを探すよう勧めています2。
flowchart TB
accTitle: 記録した接続の数の読み方
accDescr: 負荷が止まったあともTimeWaitの数が減らないなら新しい接続を作り続けているプロセスがあり、Bound の数が減らないならポートを閉じずに抱えている疑いがあることを示す。
STOP["負荷が止まったあと"] --> TW{"TimeWaitが減るか"}
STOP --> BD{"Boundが減るか"}
TW -->|"減らない"| NEW["新しい接続を作り続けている"]
BD -->|"減らない"| HOLD["ポートを閉じずに抱えている疑い"]
図7: 負荷が止まったあとの減り方で、作りすぎと抱え込みを見分ける。
なお、TIME_WAIT の接続はプロセスから切り離されているため、プロセス番号から呼び出し元を特定できないことがあります。GitHub Actions の Windows Server 2025 のランナーでこのスクリプトを動かすと、TIME_WAIT の行はプロセス番号がすべて 0 でした。スクリプトはプロセス番号が 0 の行ではプロセス名を調べませんが、ほかの行でも Process の列が呼び出し元とは限りません。その場合は「HttpClientをusingで囲んではいけない」の記事のとおり、相手のアドレスとポートで束ねて見ます。
プロセスが分からないときは、ハンドルの数も手がかりになります。Microsoft の資料は、ソケットはハンドルなので、タスクマネージャーや Process Explorer でハンドルの多いプロセスを探す方法を挙げています。Process Explorer では、\Device\AFD と表示されるハンドルがソケットです2。使い方は「Process Explorer / Handle / VMMap実践」で説明しています。
枯渇を招く呼び方
「HttpClientをusingで囲んではいけない」の記事で説明した、リクエストごとに HttpClient を作り直す書き方のほかにも、接続を作りすぎる呼び方があります。
Connection: close の指定
HttpRequestHeaders.ConnectionClose を true にすると、要求の Connection ヘッダーに close が付きます6。HTTP/1.1 では、close は送った側が今回の応答を終えたあとで接続を閉じることを示します7。この指定が残っていると、要求のたびに新しい接続を張ることになります。
上限のない並列送信
.NET の SocketsHttpHandler では、同じサーバーへの HTTP/1.1 の同時接続数には既定で上限がありません8。数千件の注文を一度に並列で送ると、その数だけ接続が開きます。
.NET 6 以降では、プールの中で1分使われなかった接続は再利用の対象から外れます9。一度に大量の接続を開いた処理が終わると、使われなくなった接続がまとめて閉じられ、閉じた側に TIME_WAIT がまとめて残ります。並列処理の山が来るたびに、この流れが繰り返されます。
flowchart TB
accTitle: 並列数に上限がないときにTIME_WAITが増える流れ
accDescr: 同じサーバーへの同時接続数に上限がないと、大量の並列送信で接続が一度に開き、処理が終わって使われなくなった接続がまとめて閉じられ、TIME_WAITがまとめて残ることを示す。
P["上限なしで大量に並列送信"] --> OPEN["接続が一度に開く"]
OPEN --> IDLE["処理が終わり使われなくなる"]
IDLE --> CLOSE["まとめて閉じられる"]
CLOSE --> TW["TIME_WAITがまとめて残る"]
図8: 上限のない並列送信は接続の山とTIME_WAITの山を作る。
間を置かないリトライ
接続に失敗したとき、間を置かずにリトライを繰り返すと、新しい接続がさらに増えます。Azure の資料は、減衰や待ち時間のない強引なリトライが、SNAT ポートの枯渇を引き起こしたり長引かせたりするとしています3。ポートが尽きかけているときのリトライは、状況を悪くします。
メッセージごとの TCP 接続
HttpClient を使わない独自の TCP 通信でも、メッセージを送るたびに TcpClient を作って閉じる設計は、同じようにポートを消費します。装置やほかのシステムと常時やりとりするなら、接続を張ったまま使い続ける設計にします。TCP でメッセージの区切りを扱う方法は「TCPでSendした単位ごとにReceiveできるという誤解」で説明しています。
直す順番
直すときは、アプリの側から順に見直します。OS やネットワークの設定を変えるのは、アプリを直すまでの一時しのぎです。
flowchart TB
accTitle: ポート枯渇を直す順番
accDescr: 接続を使い回し、同時接続数と並列数に上限を設け、リトライに間隔を置き、相手が対応していればHTTP/2で接続をまとめ、OSの動的ポートの範囲を広げるのは最後の一時しのぎにする順番を示す。
F1["接続を使い回す"] --> F2["並列数に上限を設ける"]
F2 --> F3["リトライに間隔を置く"]
F3 --> F4["HTTP/2で接続をまとめる"]
F4 -.-> F5["OSの設定は一時しのぎ"]
図9: アプリの側から直し、OSの設定は最後に回す。
接続を使い回す
HttpClient は、PooledConnectionLifetime を設定した共有のインスタンスを使うか、IHttpClientFactory から受け取ったものを使います1。理由と書き方は「HttpClientをusingで囲んではいけない」の記事にあります。ConnectionClose = true を付けている箇所があれば、本当に必要かを確かめて外します。
並列数に上限を設ける
同じサーバーへの同時接続数は MaxConnectionsPerServer で上限を決められます10。並列で送る処理には並列度そのものにも上限を設けます。
using System.Net.Http.Json;
// アプリ全体で1つだけ作って使い回す
private static readonly HttpClient Client = new(new SocketsHttpHandler
{
PooledConnectionLifetime = TimeSpan.FromMinutes(5),
MaxConnectionsPerServer = 20,
})
{
BaseAddress = new Uri("https://api.example.com/"),
};
public static async Task SendOrdersAsync(IEnumerable<Order> orders, CancellationToken token)
{
var options = new ParallelOptions
{
MaxDegreeOfParallelism = 20,
CancellationToken = token,
};
await Parallel.ForEachAsync(orders, options, async (order, ct) =>
{
using var response = await Client.PostAsJsonAsync("orders", order, ct);
response.EnsureSuccessStatusCode();
});
}
上限の値は相手の API の利用条件と、1件あたりの処理時間から決めます。この例の20は書き方を示すための値です。
リトライに間隔を置く
リトライは間隔を指数的に延ばしながら回数に上限を設けます。NuGet パッケージ Microsoft.Extensions.Http.Resilience の AddStandardResilienceHandler で加える標準のハンドラーは、既定でこの形のリトライ(最大3回、指数的な待ち時間とゆらぎ)を持っています11。既定では POST などすべてのメソッドをリトライするので、再実行すると二重登録になる要求は除きます。その扱いは「HttpClientをusingで囲んではいけない」の記事で説明しています。
HTTP/2 で接続をまとめる
HTTP/2 では1本の接続で複数の要求を同時に送れます。RFC 9113 は、クライアントが同じホストとポートに複数の HTTP/2 接続を開かないよう求めています12。HttpClient の既定の HTTP バージョンは 1.1 なので13、相手の API が HTTP/2 に対応しているなら、次のように指定します。
using System.Net;
private static readonly HttpClient Client = new(new SocketsHttpHandler
{
PooledConnectionLifetime = TimeSpan.FromMinutes(5),
})
{
DefaultRequestVersion = HttpVersion.Version20,
DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower,
};
RequestVersionOrLower を指定すると、相手が HTTP/2 に対応していないときは HTTP/1.1 で通信します。HTTP/2 になるのは暗号化した接続(https)で相手と取り決められたときだけで、http の URL では HTTP/1.1 のままです14。DefaultRequestVersion は最初の要求を送る前に設定します13。
SocketsHttpHandler は、既定では同じサーバーへ追加の HTTP/2 接続を張りません。EnableMultipleHttp2Connections を true にすると追加の接続を張れますが、資料はこれが RFC 9113 に反すると注記しています15。
flowchart TB
accTitle: RequestVersionOrLowerで使われるHTTPのバージョン
accDescr: RequestVersionOrLowerでHTTP/2を指定すると、httpsで相手がHTTP/2に対応していればHTTP/2を使い、httpのURLや相手が対応していない場合はHTTP/1.1で通信し、HTTP/2の接続は既定では同じサーバーへ1本にまとまることを示す。
REQ["HTTP/2を指定"] --> Q{"httpsで相手が対応しているか"}
Q -->|"対応している"| H2["HTTP/2で通信"]
Q -->|"httpか未対応"| H1["HTTP/1.1で通信"]
H2 --> ONE["既定では同じサーバーへ1本"]
図10: HTTP/2になるのはhttpsで相手が対応しているときだけで、接続は既定で1本にまとまる。
OS の設定の変更
再起動できないまま症状を抑える必要があるときは、動的ポートの範囲を広げられます。Microsoft の資料は範囲を広げても恒久的な解決にはならず、ポートを使い切っているプロセスを突き止めて、そのプロセスの側で直す必要があるとしています2。
広げる前に、追加する範囲のポートで待ち受けているサービスがないか、ファイアウォールの規則に影響しないかを確かめます。Microsoft の資料は動的ポートの範囲を前提にファイアウォールを設定している環境があることにも触れています2。次のコマンドは管理者として実行します。
# いまの範囲を確かめる
netsh int ipv4 show dynamicport tcp
# 開始を10000、個数を55536にする(10000〜65535)
netsh int ipv4 set dynamicport tcp start=10000 num=55536
設定できる最小の個数は255、開始ポートの最小は1025で、終わりは65535を超えられません。IPv6 の範囲は netsh int ipv6 で別に設定します2。
TIME_WAIT の長さを TcpTimedWaitDelay で短くする方法もあります。この値は既定では存在せず、HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters に追加します。設定できる範囲は Windows Server 2012 R2 と Windows 8.1 以降で2〜300秒です5。この資料は BizTalk Server の性能調整の手引きで、推奨値として30秒を挙げています。ただし同じ資料は、レジストリの設定を変えるのは、システムへの影響を十分に分析してからにするよう注意しています。TIME_WAIT は遅れて届くパケットから新しい接続を守るための状態なので、アプリを直さずに短くすることは勧めません。
flowchart TB
accTitle: OSの設定を変えるときの位置づけ
accDescr: 動的ポートの範囲を広げることとTcpTimedWaitDelayでTIME_WAITを短くすることは、どちらもアプリを直すまでの一時しのぎで、ポートを使い切っているプロセスを突き止めて直すことが恒久的な対策であることを示す。
WIDE["動的ポートの範囲を広げる"] --> TEMP["一時しのぎ"]
SHORT["TIME_WAITを短くする"] --> TEMP
TEMP --> FIX["プロセスを突き止めて直す"]
図11: OSの設定は時間を稼ぐだけで、直す場所はアプリにある。
Azure の SNAT ポート
Azure の仮想マシンで SNAT ポートが尽きている場合、Microsoft は多くの場合に NAT Gateway を使うよう勧めています3。NAT Gateway はサブネットの中の仮想マシンに SNAT ポートを必要に応じて割り当てます。資料は同じ宛先へ外向きの接続を繰り返すサービスには NAT Gateway を特に勧めています。Load Balancer を使い続けるなら、送信規則でポートを手動で割り当て、1台あたりのポートを増やせます。Azure Storage や Azure SQL のような Azure のサービスへの通信は、Private Link を使えば SNAT を通りません3。
flowchart TB
accTitle: AzureでSNATポートが尽きたときの選択肢
accDescr: AzureでSNATポートが尽きたときは、NAT Gatewayでポートを必要に応じて割り当てるか、Load Balancerの送信規則でポートを手動で割り当てて増やすか、AzureのサービスへはPrivate LinkでSNATを通らないようにすることを示す。
SNAT["SNATポートが尽きる"] --> GW["NAT Gatewayを使う"]
SNAT --> RULE["送信規則で手動割り当て"]
SNAT --> PL["Azureのサービスへは Private Link"]
図12: SNATポートの枯渇は外向き接続の構成を変えて対処する。
ここでも、アプリの接続の使い回しとリトライの見直しが先です。Azure の資料も接続の再利用と接続プール、控えめなリトライを、アプリの設計として求めています3。
症状ごとの対応表
| 症状 | 疑う場所 | 確かめること | 対処 |
|---|---|---|---|
| そのマシンからの接続が軒並み失敗する | 自分のマシンの動的ポート | イベントID 4227・4231、プロセスごとの偏り | 接続の使い回し、並列数の上限 |
| 1つの外部 API だけ失敗する | 同じ宛先への張りすぎ、途中の NAT、相手側 | イベントID 4227・4231、その宛先への TIME_WAIT の数、NAT のポートの使用量、相手の利用条件 | 接続の使い回し、NAT の構成、相手への問い合わせ |
| クラウドの仮想マシンから外部への接続が失敗やタイムアウトになる | SNAT ポート | Load Balancer の SNAT の使用量 | NAT Gateway、送信規則、Private Link |
| 負荷の山のあとに TIME_WAIT が急に増える | 並列数の上限がない処理 | 増え方の記録 | MaxConnectionsPerServer と並列度の上限 |
関連記事
- HttpClientをusingで囲んではいけない ── C#業務アプリのHTTP通信実務
- Process Explorer / Handle / VMMap実践
- Get-WinEventでイベントログを実務的に調べる
- Windowsのパケットキャプチャ実務 ── pktmon・netsh・Wireshark
- 社内プロキシとWindowsアプリ ── WinINET・WinHTTP・.NET
- TCPでSendした単位ごとにReceiveできるという誤解
参考リンク
-
Microsoft Learn, HttpClient guidelines for .NET. .NET Core 2.1 以降は
SocketsHttpHandlerが実装を担うこと、長く使うクライアントにPooledConnectionLifetimeを設定するかIHttpClientFactoryを使うこと、.NET Framework ではIHttpClientFactoryを使うこと。 ↩ ↩2 -
Microsoft Learn, TCP/IP port exhaustion troubleshooting. 既定の動的ポートの範囲、範囲の変更と制限、枯渇の症状、イベントID 4227・4231、TIME_WAITが多いこと自体は枯渇の証拠にならないこと、既定の4分、BOUND状態、ハンドルの数での特定、
STATUS_TOO_MANY_ADDRESSES、範囲を広げることが一時しのぎであること、受け付けた側の接続にエフェメラルポートが要らないこと。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 -
Microsoft Learn, Troubleshoot Azure Load Balancer outbound connectivity issues. SNATポートの枯渇、仮想マシンごとのSNATポートの割り当て、NAT Gateway とそのポートの再利用の待ち時間、同じ宛先への接続を繰り返すサービスへの推奨、送信規則、Private Link、接続の再利用と接続プール、強引なリトライの影響。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8
-
IETF, RFC 9293 - Transmission Control Protocol (TCP). 能動的に閉じた側が2×MSLの間TIME-WAIT状態にとどまること(3.6.1)、MSLを2分とすること(3.4.2)、接続がソケットの組で区別されること(3.4.1)。 ↩ ↩2 ↩3
-
Microsoft Learn, Settings that can be Modified to Improve Network Performance. BizTalk Server の性能調整の手引き。
TcpTimedWaitDelayのキー、既定値、設定できる範囲、推奨値、TIME_WAITのあいだソケットの組を再利用できないこと、レジストリの変更は影響を分析してから行うこと。 ↩ ↩2 ↩3 -
Microsoft Learn, HttpRequestHeaders.ConnectionClose Property. ↩
-
IETF, RFC 9112 - HTTP/1.1, Section 9.6.
closeの接続オプションが、送った側が応答を終えたあとで接続を閉じることを示すこと。 ↩ -
Microsoft Learn, HttpClient Class. HTTP/1.1 の同時接続数が既定で無制限であることと、
MaxConnectionsPerServerで上限を設けられること。 ↩ -
Microsoft Learn, SocketsHttpHandler.PooledConnectionIdleTimeout Property. 既定値が .NET 6 以降は1分であること。 ↩
-
Microsoft Learn, SocketsHttpHandler.MaxConnectionsPerServer Property. ↩
-
Microsoft Learn, Build resilient HTTP apps: Key development patterns.
Microsoft.Extensions.Http.ResilienceのAddStandardResilienceHandler、標準のハンドラーのリトライの既定値、既定ですべての HTTP メソッドをリトライすること。 ↩ -
IETF, RFC 9113 - HTTP/2, Section 9.1. クライアントが同じホストとポートに複数のHTTP/2接続を開かないこと。 ↩
-
Microsoft Learn, HttpClient.DefaultRequestVersion Property. 既定値が HTTP/1.1 であること、最初の要求を送る前に変更すること。 ↩ ↩2
-
Microsoft Learn, HttpVersionPolicy Enum.
RequestVersionOrLowerは暗号化した接続で ALPN により取り決めたときだけ要求したバージョンを使い、平文の HTTP/2(h2c)を使わないこと。 ↩ -
Microsoft Learn, SocketsHttpHandler.EnableMultipleHttp2Connections Property. 同じサーバーへの追加の HTTP/2 接続を許すかどうかと、それが RFC 9113 に反すること。 ↩
関連する記事
同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。
社内プロキシとWindowsアプリ ── WinINET・WinHTTP・.NETのプロキシ解決を整理する
ブラウザーではつながるのに業務アプリだけ社内プロキシを越えられない。原因の多くはWinINET・WinHTTP・環境変数・.NETのどのプロキシ設定を誰が読むかの食い違いです。PACとWPAD、認証プロキシ、TLSインスペクション、切り分け手順まで整理します。
OSI参照モデルをはっきりイメージする ── HTTPリクエスト1個を7層に解剖する
OSI参照モデルを実物で理解します。HTTP GETリクエストを運ぶEthernetフレームをC#で組み立てて解剖し、7層が入れ子になっている様子をWiresharkで確認します。各層と.NET APIの対応、障害切り分けまで整理します。
OPC UA 入門 ── PLCや装置のデータをWindowsアプリで集める
OPC UAでPLCや装置のデータをWindowsアプリに集めるときに知っておくことを、C#で動かした結果とともに説明します。アドレス空間、読み書き、購読、証明書、再接続とタイムスタンプの扱いを扱います。
GigE Vision・USB3 Visionカメラの取りこぼし ── 原因の切り分けとWindows側の設定
GigE Vision・USB3 Visionカメラで画像を取りこぼす原因を、カメラからアプリまでの経路ごとに切り分けます。統計の読み方、NIC・USB・電源の設定、受信バッファとアプリの処理の作り方をまとめます。
引数はなぜ壊れるか ── Windowsのコマンドライン引数の規則
Windowsでは引数の配列は存在せず、CreateProcessに渡るのは1本の文字列で、分割は受け取り側が行います。CommandLineToArgvW・CRT・.NETの分割規則と、.NETのArgumentList・C++での正しい組み立て方を解説します。
関連トピック
このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。
Windows技術トピック
Windows 開発、不具合調査、既存資産活用の技術トピックをまとめた入口です。
不具合調査 / 長期稼働テーマ
再現しにくい不具合、通信停止、長期稼働障害、失敗パス検証を整理するトピックです。
このテーマがつながるサービス
この記事は次のサービスページにつながります。近い入口からご覧ください。
不具合調査・原因解析
繁忙時間帯だけ外部APIにつながらなくなる業務アプリについて、ポート枯渇かどうかの切り分け、どのプロセスが接続を作っているかの調査、直す順番の整理を一緒に進められます。
技術相談・設計レビュー
接続の使い回し、並列数の上限、リトライの間隔、クラウドの出口の設計は、呼び出し先と通信量によって答えが変わるので、技術相談・設計レビューとして扱いやすい題材です。
よくある質問
この記事のテーマについて、相談時によくある質問をまとめています。
- TIME_WAITの接続がたくさんあれば、ポートが枯渇しているのですか?
- そうとは限りません。Microsoftの切り分け手順は、TIME_WAITが多いことは接続を多く作っていることを示すだけで、いまポートが尽きている証拠ではないとしています。外向きの接続が軒並み失敗するか、システムログにイベントID 4227や4231が出ているかを先に確かめます。
- TIME_WAITはどちら側に残りますか?
- 原則として、先に接続を閉じた側に残ります。RFC 9293は、能動的に閉じた側が2×MSLの間TIME-WAIT状態にとどまると定めています。受け付けた側の接続はエフェメラルポートを使わないので、サーバー側に残るTIME_WAITは、そのサーバーのエフェメラルポートを減らしません。
- 動的ポートの範囲を広げれば解決しますか?
- 一時しのぎにしかなりません。Microsoftは、範囲を広げても恒久的な解決にはならず、どのプロセスがポートを使い切っているかを突き止めて、そのプロセスの側で直す必要があるとしています。接続の使い回しと並列数の上限を先に見直します。
- 自分のPCのポートに余裕があるのに、外部APIにつながらないのはなぜですか?
- 途中のNATでポートが尽きている可能性があります。たとえばAzure Load Balancerは仮想マシンごとに決まった数のSNATポートを割り当てるので、仮想マシン側より先にSNATポートが尽きることがあります。Microsoftは多くの場合にNAT Gatewayを勧めていて、同じ宛先へ外向きの接続を繰り返すサービスには特に勧めています。