動かない時

WSL2 でポート転送できない

WSL2 上のコンテナに Windows ブラウザや同じ LAN の他端末から繋がらない問題。Windows ファイアウォール、`localhostForwarding`、`netsh portproxy` の順で切り分けます。

WSL2 でポート転送できない diagram

症状

  • WSL2 内で docker run -p 3000:3000 したのに、Windows 側の Chrome から http://localhost:3000 が開かない
  • 開けるときもあれば開けないときもある(PC スリープ復帰後などで再現しやすい)
  • Windows からは localhost で繋がるが、同じ Wi-Fi 上のスマホや別 PC からは繋がらない
  • http://<Windows の IP>:3000 は開かない

原因

WSL2 ネットワークの多段構造が原因です。

パケットは以下を経由します。

  1. コンテナ(Docker のブリッジネットワーク)
  2. WSL2 VM(固有の仮想 NIC、動的な IP が割り振られる)
  3. Windows ホスト
  4. LAN

典型的な詰まりどころ:

  • localhostForwarding が無効: Windows 側の localhost から WSL2 へ自動転送する仕組み。.wslconfig で無効化されているとそもそも繋がらない
  • Windows Defender Firewall: WSL2 からの応答を落とす / 他端末からの接続を受信ルールで拒否
  • 動的 IP の変動: WSL2 VM の IP は再起動ごとに変わる。固定 netsh portproxy を書いていると PC 再起動後に別 IP を指してしまい無効化される
  • LAN からの到達: localhost:3000 転送はあくまで Windows→WSL2 の片方向。LAN→WSL2 には netsh interface portproxy による明示的な橋渡しが必要

確認コマンド

まずどの層で詰まっているかを特定します。

PowerShell と WSL 内のシェルを並べて開いて実行するのが効率的です。

bash
# WSL2 内: コンテナが正しくリッスンしているか
ss -tlnp | grep :3000
curl -v http://localhost:3000

# WSL2 内: WSL VM 自身の IP
ip addr show eth0 | grep "inet "
hostname -I

# PowerShell: Windows から WSL の IP に直接届くか
wsl hostname -I
Test-NetConnection -ComputerName <WSL の IP> -Port 3000

# PowerShell: localhost 転送の状況
Get-Content $env:USERPROFILE\.wslconfig  # localhostForwarding が false になっていないか

# PowerShell: 現在の portproxy ルール
netsh interface portproxy show v4tov4

# PowerShell: Windows ファイアウォールのルール確認
Get-NetFirewallRule -DisplayName "*WSL*" | Format-Table DisplayName, Enabled, Direction, Action

解決策 1: Windows から localhost で繋げたい

多くのケースはこれで十分です。

%USERPROFILE%\.wslconfig(存在しなければ新規作成)に次を書き、PowerShell で wsl --shutdown → WSL 起動し直します。

bash
# %USERPROFILE%\.wslconfig
[wsl2]
localhostForwarding=true

# WSL を再起動して反映
wsl --shutdown

解決策 2: LAN の他端末(スマホなど)から繋げたい

netsh portproxy で「Windows の全 IP の 3000 番 → WSL VM の 3000 番」へ橋渡しし、Windows ファイアウォールで受信を許可します。

管理者 PowerShell で実行してください。

WSL2 の IP は再起動ごとに変わるので、手動で最新 IP に合わせ直すか、起動時スクリプトで自動化する運用が無難です。

bash
# 管理者 PowerShell
$wslIp = (wsl hostname -I).Trim().Split(" ")[0]

netsh interface portproxy add v4tov4 `
  listenaddress=0.0.0.0 listenport=3000 `
  connectaddress=$wslIp connectport=3000

New-NetFirewallRule -DisplayName "WSL2 3000" `
  -Direction Inbound -LocalPort 3000 `
  -Protocol TCP -Action Allow

# 確認
netsh interface portproxy show v4tov4

# ルール削除
netsh interface portproxy delete v4tov4 listenaddress=0.0.0.0 listenport=3000

解決策 3: 根本的にネットワーク構成を単純化する(WSL 2.0.0 以降)

Windows 11 + WSL バージョン 2.0.0 以降では「mirrored networking mode」が使えます。

WSL VM に独立 NIC を持たせず Windows の NIC を共有するモードで、localhost / LAN 両方の到達性が一気に改善し、netsh portproxy もほぼ不要になります。

社内 VPN や厳密なセグメンテーションとは相性の問題があるので、導入前に検証を。

bash
# %USERPROFILE%\.wslconfig
[wsl2]
networkingMode=mirrored
localhostForwarding=true

# 反映
wsl --shutdown