最新技術を取り入れることで発生するアプリのネットワークPort枯渇問題
最新技術を取り入れることで発生するアプリのネットワークPort枯渇問題
ある日、アプリが動かなくなった
FA(Factory Automation)系のシステム開発をやっている筆者が、ある日突然ハマった話をする。
いつも通りの開発作業。自作のFA系アプリを起動する。TCPの50020番ポートでサーバを立ち上げる、いつものやつだ。
起動しない。
「え?昨日まで動いてたのに?」
コードは一切変えていない。設定も変えていない。なのにポートのbindが失敗する。誰かがポートを掴んでいるのか?
おなじみのTcpViewを開いて50020番を探す。
誰もいない。
netstatでも確認する。50020番をLISTENしているプロセスはゼロ。なのにbindできない。
これ、犯人は幽霊だった。
犯人を見つけるまで
しばらく頭を抱えた後、ふと思い出した。最近WSL2を入れたな、と。
試しにこのコマンドを叩いてみた。
netsh int ipv4 show excludedportrange protocol=tcpプロトコル tcp ポート除外範囲
開始ポート 終了ポート
---------- --------
45537 45537
50000 50059 *
59531 59630
59631 59730
59731 59830
59831 59930
59931 60030
60109 60208
60209 60308
60309 60408
60409 60508
60509 60608
60609 60708
60709 60808
60809 60908
60909 61008
* - 管理されている除外ポート。おいおいおい。
59531〜61008の範囲に、100ポート単位の予約がズラーっと並んでいる。約1,500ポートが「除外」されている。 TcpViewに出てこないのは当然だ。これはプロセスが掴んでいるんじゃなくて、カーネルが「ここ俺の」って確保しているのだから。
犯人はHyper-V。WSL2を動かすために裏で起動しているハイパーバイザだった。
代替ポートを探す旅
50020がダメなら別のポートを使えばいい。そう思うじゃん?
1回目:ポート60030に変更 → ダメ。Hyper-Vの予約範囲だった
2回目:ポート50060に変更 → ダメ。TcpViewで見たらSlackが使ってた
3回目:ポート50070に変更 → ダメ。Teamsが使ってた
4回目:ポート50080に変更 → やっとOK
4回目でやっと空きポートを見つけた。 この作業だけで30分溶けた。本来やるべき開発は1行も進んでいない。
しかもこれ、明日また同じポートが使える保証がない。Hyper-Vの予約範囲は再起動のたびに変わるし、SlackやTeamsがどのポートを使うかもランダムだ。
そもそも何が起きているのか
エフェメラルポートという共有地
アプリが自由に使えるTCPポートは49152〜65535番の範囲で、「エフェメラルポート」と呼ばれている。約16,000個。多いように見えるが、ここに思った以上のプレイヤーが群がっている。
プレイヤー1:Hyper-V / WSL2
WSL2を有効化した瞬間、ハイパーバイザがカーネルレベルでポートをブロック予約する。先ほどの通り、約1,500ポートが一撃で消える。
プレイヤー2:Electron系アプリ
Slack、Teams、VS Code、Discord...最近のデスクトップアプリの多くはElectronで作られていて、中身はChromiumブラウザそのもの。1アプリ起動するたびにChromiumが1個立ち上がるようなもので、それぞれがWebSocketやHTTP/2の接続を複数張りっぱなしにする。1アプリで数十ポートを消費する。
プレイヤー3:Windows自身
OneDriveの同期、Windows Updateのバックグラウンド通信。こいつらもポートを使う。しかも勝手に。
全員が同じプールに手を突っ込んで、早い者勝ちでポートを取っていく。
DHCPがない世界
IPアドレスの管理にはDHCPがある。「このアドレス帯はサーバ用に予約」「この範囲は端末に動的割り当て」とプールを分けて管理できる。衝突は起きない。
ポート番号の世界には、この仕組みが存在しない。
DHCPサーバなしで全端末がAPIPAで勝手にIPアドレスを振っているネットワークを想像してほしい。それが今のWindowsのポート管理の実態だ。
「じゃあWSL2を止めればいいじゃん」→ ダメでした
当然そう思うよね。自分もそう思った。
# WSL2停止前のポート予約を確認
netsh int ipv4 show excludedportrange protocol=tcp
→ 59531〜61008に約1,500ポートの予約
# WSL2を停止
wsl --shutdown
# 停止後に再確認
netsh int ipv4 show excludedportrange protocol=tcp
→ 全く同じ。1ポートも解放されていない。変わらない。
wsl --shutdownはVMを落とすだけで、ハイパーバイザ自体は生きている。WSL2を有効化した時点でWindowsは「Hyper-Vの上で動くOS」に変わっているので、OS再起動しない限りハイパーバイザは止まらないし、ポート予約も消えない。
「使う時だけ起動して、終わったら止める」という運用は、ポート予約問題に対しては完全に無意味だった。
HomeでもProでも同じ地獄
「Hyper-VってWindows Pro専用じゃないの?」
筆者も最初はそう思っていた。でも違った。
Pro専用なのは「Hyper-Vマネージャー」というVM管理ツールであって、ハイパーバイザエンジン自体はHomeでも有効化される。Microsoftは「Virtual Machine Platform」という別の名前でHomeにもこっそり入れている。中身は同じだ。
実際、筆者のサブマシン(Windows 11 Home)でもWSL2は問題なく動いているし、ポート予約も同様に発生している。
HomeだろうがProだろうが、WSL2を入れた瞬間に約1,500ポートが消える。平等に。
Electron系アプリの「見えないポート消費」
Electron系アプリがやっかいなのは、サーバとしてポートを使っているわけではないこと。
Slackはサーバに向かってWebSocket接続を張っている。この時、TCPコネクションの送信元としてローカルポートが必要になる。これ自体は普通のブラウザでWebサイトを開くのと同じだ。
ただ、Electronアプリは接続数が異常に多い。1アプリでチャンネル通知、プレゼンス更新、ファイルプレビュー、Analytics...常時数十本の接続が張りっぱなし。しかもローカルの送信元ポートはOSに「空いてるとこちょうだい(bind(0))」で委ねているので、どのポートに着地するかは誰にもわからない。制御する設定項目もない。
つまりElectron系アプリはランダムな位置のポートを黙って占有する。特定ポートでbindしたいアプリと「たまたま被る」可能性があり、しかも再起動のたびにパターンが変わる。
Electron系アプリとポート指定アプリは同居させるな
ここまでの話を踏まえると、一つの実践的な結論が見えてくる。
Electron系アプリと、特定ポートを使うアプリを同じマシンで動かすのは避けたほうがいい。
Electron系はbind(0)でOSに丸投げするので、どのポートに着地するか制御できない。サーバ系アプリが確保したいポートに「たまたま」着地するリスクは、Electronアプリの数が増えるほど高くなる。Slack、Teams、VS Code、Discord...4つ起動すれば、Chromiumが4個分のコネクションを張る。数百ポートがランダムに消費される。
「サーバ側を先に起動すればElectronは避けてくれるんじゃない?」
確かに、先にbindしてLISTEN状態にしておけば、Electronのbind(0)はそのポートを避ける。ただしこれも万全ではない。サーバアプリを再起動した場合、TIME_WAIT状態(TCP切断後2〜4分のロック期間)の隙にElectronが同じポートを送信元として使う可能性がある。
根本的には、特定ポートでサーバを動かすマシンではElectron系アプリを極力減らすのが安全だ。Slackはブラウザ版で代替する、TeamsもWeb版を使う。Electronの「便利なデスクトップアプリ」は、ポートを気にしなくていいマシンで使えばいい。
1台に全部載せたくなる気持ちはわかる。でも、ポート指定アプリの安定動作が重要なら、同居を避ける設計判断が一番確実だ。
Electronで作る側になる場合の注意
もう一つ、見落としがちな観点がある。
Windowsデスクトップアプリの後継として、マルチプラットフォーム対応を考えた時、Electronは有力な選択肢になる。Windows/Mac/Linuxで動くGUIアプリをWeb技術で構築できる。C++やC#で書かれたレガシーなWindowsアプリをモダナイズする手段として検討する場面は少なくないだろう。
ただし、そのアプリが特定ポートで通信するタイプだった場合、話がややこしくなる。
Electronで作ったアプリは、起動した瞬間にChromium 1個分のネットワークスタックを抱える。UIのレンダリングやアプリ内通信のために、バックグラウンドで複数のTCPコネクションが発生する。これらはアプリ本来の通信(PLCとのSLMP通信など)とは無関係に、エフェメラルポートを消費する。
つまり、自分のアプリがポートの消費者にもなるということだ。
FA系ツールは他のFA系ツールと同じマシンで同居することが多い。データ転送システムのGUI、PLCモニタリングツール、ライン管理画面...これらが1台のPCで同時に動く。もし自分のアプリをElectronで作ったら、隣で動いている別のFA系サーバアプリのポートを圧迫する可能性がある。自分のアプリの通信は問題なくても、同居しているアプリに迷惑をかけるという構図だ。
Electronを選ぶこと自体が悪いわけではない。ただ、特定ポートを使う通信アプリの開発基盤として採用する場合は、Chromiumが裏で消費するポートの影響を理解した上で判断する必要がある。代替として、Tauri(Rust + WebView)のようなChromiumを内蔵しないフレームワークも選択肢に入れておくといいかもしれない。
FA系エンジニアにとっての「怖さ」
一般的なオフィスワークやWeb開発では、この問題はほとんど顕在化しない。ブラウザやAPIクライアントとしてHTTP通信する分には、ローカルの送信元ポートが何番でも関係ないからだ。
でもFA系は違う。
PLCや上位システムとの通信では、特定のポートでサーバを立ち上げて待ち受ける。bindが失敗したらその時点で通信が確立できない。生産ラインの制御系で「ポートの空きがなかったので通信できませんでした」は許されない。
さらに怖いのが、サービスとして動かしているFA系アプリがポート衝突で起動に失敗した場合だ。初期化フェーズでの即死だと、ログ出力機構自体がまだ立ち上がっていないので痕跡が残らない。Windowsイベントログは汎用的すぎて原因特定には役立たない。リモート監視がないと現地対応が必須になる。
「昨日まで動いてたシステムが、今朝から動かない。ログにも何も残っていない。」
これ、現場で起きたら冷や汗どころの話じゃない。
打てる対策、でも全部「たぶん」
そもそもエフェメラルポート帯域を使わない
ここまでエフェメラルポート帯域(49152〜65535)の中でどう守るかを議論してきたが、そもそも戦場を変えるという選択肢がある。
Registered Ports(1024〜49151)を使う方法だ。
Hyper-Vのカーネル予約はエフェメラルポート帯域が対象なので、Registered Ports帯域には幽霊が出ない。Electron群のbind(0)もエフェメラルポートから割り当てられるので、こちらにも来ない。今回の問題がまるごと回避できる。
「勝手に使っていいの?」と思うかもしれないが、Registered PortsはIANA(Internet Assigned Numbers Authority)が「このポートはこのサービス用」と登録を管理している範囲であって、使用が禁止されているわけではない。実際、MySQLの3306番やPostgreSQLの5432番もこの範囲だし、三菱PLCのMELSEC通信も5000番台を使っている。
注意点としては、IANAに登録済みのサービスと番号が被らないようにすること。IANAのポート番号一覧(Service Name and Transport Protocol Port Number Registry)は公開されているので、自分が使いたい番号が既に登録済みかどうかは確認できる。FA系の現場マシンでPostgreSQLやRedisが動いているケースは少ないだろうから、実質的な衝突リスクは低い。
エフェメラルポート帯域の中で幽霊やElectronと椅子取りゲームをするより、最初から別の部屋に座るほうがよほど確実だ。
動的ポート範囲をずらす
netsh int ipv4 set dynamic tcp start=60000 num=5535エフェメラルポートの開始位置を60000番に押し上げて、50000番台をFA系アプリ用に空ける。ただし、Hyper-Vの予約がこの設定に従う保証はない。実際、先の検証では59531〜61008が予約されていて、60000番台に既に食い込んでいた。
FA系アプリをWindowsサービスにして先に起動する
Windowsサービスはユーザーログイン前に起動するので、Electron系アプリ(ログイン後に起動)より先にポートを確保できる。Hyper-Vのカーネル予約はサービスより先なので、そちらとの衝突は防げない。Electron群との競合には有効。
ポートを設定ファイルで外部化+bind失敗時のフォールバック
アプリ側でポート番号を固定せず設定ファイルから読む。bind失敗時は代替ポートを試す。成功したポートをファイルに書き出して、クライアントがそれを参照する。自前でDHCPもどきを作るイメージだ。ただし自社開発アプリにしか適用できない。既製のFA系ツールには手を出せない。
環境の思想分離
現場マシン:WSL2無効、Electron最小限、ポートの純粋性を最優先
開発マシン:WSL2+Electron群の同居を受け入れ、ベストエフォート
ただし、AIコーディングツール(Claude Code等)をWSL2上で常用している場合、開発環境でのWSL2常駐は避けられない。そしてさっき検証した通り、WSL2を「必要な時だけ起動」しても意味がない。
どの対策も「たぶん大丈夫」であって、「確実に大丈夫」ではない。
便利にするほど不安定になるジレンマ
エフェメラルポートはもう「安全地帯」ではない
少し前まで、エフェメラルポート帯域(49152〜65535)は「アプリが自由に使える安全地帯」だった。OSのサービスはWell-Known Ports、登録済みアプリはRegistered Ports、自前のアプリはエフェメラル。住み分けができていた。
この前提が、ここ数年で崩れてきている。
Hyper-V/WSL2がカーネルレベルでエフェメラル帯域をブロック予約するようになった。Electron系アプリがbind(0)でエフェメラル帯域にランダム着地するようになった。どちらもここ数年で急速に普及した技術だ。
「エフェメラルポートなら安全」という常識は、もうアップデートが必要だ。特定ポートでサーバを立てるアプリを開発しているなら、エフェメラル帯域よりもむしろRegistered Ports帯域のほうが安定する、という逆転現象が起きている。
便利さと安定性のトレードオフ
最新の開発環境を整えれば整えるほど、ポート指定アプリの動作基盤が揺らぐ。
WSL2を入れる → 1,500ポートが消える
Claude Codeを使う → WSL2が常時必要
VS Codeで開発する → Chromium 1個分のポート消費
Slackで連絡する → もう1個分
便利さと安定性がトレードオフになっている。これは特定のアプリのバグではなく、Windowsのポート管理にDHCPのような仕組みがないという、OSアーキテクチャレベルの構造的問題だ。
ちなみにLinuxではcgroupsやネットワーク名前空間でプロセスごとのポート範囲を制御できるし、sysctlでポート範囲を明示的に管理できる。「誰が何を使っているか」が見える世界だ。Windowsの「誰やねん」とは対照的に。
ポート帯域の勢力図が変わった
最後に、ポート帯域の変化をざっくりまとめておく。
Hyper-V/Electron台頭以前
Well-Known Ports(0〜1023): 絶対不可侵、神の領域。HTTP、HTTPS、SSHなどが鎮座する聖域
Registered Ports(1024〜49151): 特権を与えられた貴族の領域。MySQLやPostgreSQLなど、IANAにお伺いを立てた者だけが住む
エフェメラルポート(49152〜65535): 下々の自由な領域。誰でも使える、気軽で平和な共有地
Hyper-V/Electron台頭以後
Well-Known Ports(0〜1023): 変わらず絶対不可侵、神の領域
Registered Ports(1024〜49151): 貴族の領域に、エフェメラルから追い出されたポート難民が押し寄せている。でも実は今、一番安全な避難先
エフェメラルポート(49152〜65535): Hyper-VとElectronという蛮族が荒らす修羅の領域。カーネル予約で1,500ポートが消え、Electron群がランダムに占拠し、もはや「自由な共有地」ではない
かつて「自由に使える安全地帯」だったエフェメラルポートが修羅の国と化し、「勝手に使うな」と言われていたRegistered Portsがむしろ安全な避難先になるという逆転現象。これが2026年現在のWindowsポート事情だ。
エフェメラルポートから適当に選べばOK、という時代は終わった。これからはIANAのRegistered Portsレジストリとにらめっこして、「この番号は空いてるか?同居するアプリと被らないか?」を確認する手間が、アプリ開発者にも求められる。インフラ屋やネットワーク屋だけの仕事だったポート管理が、アプリ開発者の日常になりつつある。
おわりに
もしあなたが「特定ポートでサーバを立てる」タイプの開発をしているなら、一度このコマンドを叩いてみてほしい。
netsh int ipv4 show excludedportrange protocol=tcp見えない幽霊が、あなたのポートを占拠しているかもしれない。
検証環境: Windows 11 Home / WSL2(Ubuntu) / VS Code / Slack / Teams / 自社開発FA系通信アプリ
本記事は2026年2月時点の検証に基づいています。



コメント