本文へスキップ
hdknr blog
戻る

WSL2 の sshd を Windows 起動時に自動起動する — セッション 0 と 15 秒タイムアウトの罠

前回、Orca から WSL2 へ SSH 接続する話を書いた。画面は Windows のまま、開発環境だけ WSL2 側にまとめる構成だ。

その構成で次に来る不満は決まっている。再起動のたびに Ubuntu を開いて sudo service ssh start を叩くのが面倒、というやつだ。当然「Windows の起動と同時に WSL と sshd を上げてしまえばいい」という発想になる。

ところが、この用途でネット上に出回っている定番手順を裏取りしていくと、一番目立つ設定項目が、まさに動かない組み合わせだった。しかもそれは公式ドキュメントには書かれておらず、microsoft/WSL の Issue を追わないと出てこない。

この記事では、定番手順のどこが詰まるのかを主な罠 2 つを軸に、周辺の前提(sudoers・systemd・ネットワーク)まで含めて整理する。

前置き: 筆者の手元は macOS で、Windows + WSL2 の実機検証はしていない。ここで「確認した」と書いているのは、Microsoft Learn の WSL ドキュメントと microsoft/WSL リポジトリの Issue という、実機なしで裏が取れる範囲に限る。挙動は WSL のバージョンと Windows のビルドでかなり変わるため、最後に挙げる確認コマンドで自分の環境の実際を見てほしい。

先に結論

WSL2 の sshd 自動起動の 2 経路を比較した図。ログオン時トリガーはアイドル停止を .wslconfig で回避して接続に到達するが、ログオン不問トリガーはセッション 0 で行き止まりになる

出回っている定番手順

まず、検証の対象にした手順を整理しておく。おおむねこの 4 ステップで説明されることが多い。

1. WSL 側に SSH サーバーを入れる

sudo apt update
sudo apt install openssh-server -y

2. パスワードなしで SSH サービスを起動できるようにする

echo 'your-username ALL=(root) NOPASSWD: /usr/sbin/service ssh start' | sudo tee /etc/sudoers.d/service-ssh-start

3. Windows 側にバッチファイルを置く

@echo off
wsl -u root -- service ssh start

4. タスクスケジューラに登録する

手順 1 と 3 は問題ない。wsl -u root -- <コマンド> は正しい書き方で、-- 以降が Linux 側に渡る。手順 2 は後述するとおり、この経路では使われない。詰まるのは 4 と、その先だ。

罠 1: 「ログオンしているかどうかにかかわらず実行する」はセッション 0 で走る

Windows は、ログオンしたユーザーの対話セッションと、サービスが動くセッション 0 を分離している。Windows Vista 以降、サービスは常にセッション 0 に置かれ、ユーザーの画面とは切り離される。

タスクスケジューラの「ユーザーがログオンしているかどうかにかかわらず実行する」は、ログオンしていなくても走らせるという指定なので、必然的にセッション 0 側で実行される。「スタートアップ時」トリガーと組み合わせれば、なおさら誰もログオンしていない状態で走ることになる。

そして、ここがこの記事の本題だ。ストア版の WSL はセッション 0 からアクセスできない。

microsoft/WSL の Issue #9231「Store WSL isn’t accessible from Session 0」がその報告だ。2022 年 11 月に立ってから 2026 年 9 月現在も open で、コメントは 160 件を数える。同じ症状を「スケジュールされたタスクに影響する」と報告した Issue #9271 は、この #9231 の重複として閉じられた。

症状がたちが悪いのは、エラーが表に出ないことだ。#9271 の報告によれば、WSL 側にはコマンドが実行された形跡が残らず、セッション 0 に conhost.exe のプロセスだけが作られる。タスクスケジューラの履歴上はタスクが「実行された」ように見えるのに、sshd は上がっていない。「設定したのに繋がらない」という一番デバッグしづらい形で失敗する。

見分け方: パスワードを求められたら、その選択肢を選んでいる

エラーが出ないと書いたが、設定中に気づける手がかりが一つだけある。パスワードの入力を求められるかどうかだ。

「ユーザーがログオンしているかどうかにかかわらず実行する」は、タスクを保存するときに資格情報の入力ダイアログを出す。ログオンしていない状態で走らせる以上、Windows がユーザーの代わりにログオンするための資格情報を保存しておく必要があるからだ。逆に「ユーザーがログオンしているときのみ実行する」ではダイアログは出ない。すでにログオンしているセッションで走らせるだけなので、保存するものがない。

つまり、パスワードを聞かれた時点で、セッション 0 に向かう側の選択肢を選んでいることになる。ここで「設定はしたのに繋がらない」の未来が確定する。

そして、会社から配られたマシン — Microsoft Entra ID(旧 Azure AD)参加機 — では、そのパスワードがそもそも受け付けられない。組織アカウントのサインインに使っているパスワードを正しく入れても弾かれる。これは設定ミスではなく、Entra ID のアカウントでは「ログオンしているかどうかにかかわらず実行する」を使えないという制約だ。

「パスワードを保存しない」(S4U ログオン)に切り替えれば入力を回避できるが、これは解決にならない。S4U で作られるトークンはネットワークリソースにアクセスできず、しかもタスクの実行結果は成功と表示される。罠 1 の「エラーが表に出ない」失敗を、もう一種類増やすだけだ。

ここで粘る意味はない。仮に登録できたとしても、その先はセッション 0 で、#9231 のとおり WSL は起動しない。認証で弾かれているうちに引き返すほうが、症状が見えているぶんまだましだ。

どうするか: トリガーは「ログオン時」にする

デスクトップ機なら、素直にログオンを前提にするのが一番はっきりしている。

自分のマシンで自分がログオンして使う分には、これで実用上まったく困らない。開発機の WSL に SSH したいという動機なら、そもそもログオンしているはずだ。

なお、バッチファイルを別途作らなくても、タスクの「操作」に直接こう書けば済む。

この設定ではタスク実行時にコンソールウィンドウが一瞬開くので、気になるなら [全般] タブの「表示しない」にチェックを入れておく。

ログオンせずに動かす必要がある場合(サーバー機に RDP で入るだけ、といった用途)は、#9231 のスレッドに回避策の報告がある。NSSM でサービス化する方法と、MSI 版 WSL を追加インストールして C:\Program Files\WSL\wsl.exe を明示的に叩く方法だ。ただしこれらは有志のワークアラウンドであって公式手順ではないし、スレッドを読むと Windows のエディションやバージョンによって効いたり効かなかったりしている。恒久的な解決を当てにする対象ではない。

罠 2: sshd を上げてもインスタンスが 15 秒で止まる — instanceIdleTimeout

罠 1 を避けてログオン時トリガーにしたとして、次に来るのがこちらだ。

WSL のアイドル停止には、VM のタイムアウトとインスタンスのタイムアウトという別々の 2 つがある。ここを混同すると、いくら設定しても止まる。

設定対象既定値Learn 記載
vmIdleTimeout([wsl2])WSL2 を動かしている VM60000 ミリ秒あり
instanceIdleTimeout([general])ディストリビューションのインスタンス15000 ミリ秒なし

vmIdleTimeout のほうは Microsoft Learn の .wslconfig の表に載っている。しかし止まっているのは VM ではなくインスタンスのほうで、そちらを制御する instanceIdleTimeout は、同じページの表に存在しない。

この挙動そのものは古くから報告がある。Issue #8661 の再現手順はこうだ。/etc/wsl.conf の [boot] command で cron を起動し、シェルを抜ける。すると cron が動いているにもかかわらず、15 秒後にインスタンスが STOPPED になる。この Issue は「systemd の挙動から見て仕様」として 2022 年 10 月にクローズされた。

そして 2025 年、Issue #13291 で「vmIdleTimeout=-1 を設定しているのに ollama や sshd が落ちる」という報告が上がる。

どうするか: .wslconfig に instanceIdleTimeout=-1 を書く

ここで WSL のメンテナ(OneBlue 氏)が回答しているのが、次の設定だ。

# %UserProfile%\.wslconfig
[general]
instanceIdleTimeout=-1

[wsl2]
vmIdleTimeout=-1

[general] セクションと [wsl2] セクションの両方が要る。片方だけでは、VM は生きたままインスタンスが落ちる、あるいはその逆になる。同じスレッドでメンテナが「これはドキュメント化すべきだ」と書いており、実際 2026 年 6 月更新版の Learn のページを見てもまだ載っていない。

instanceIdleTimeout はミリ秒でも指定できるので、常時起動まではしたくないなら instanceIdleTimeout=300000(5 分)のような妥協点も取れる。

常時起動のコスト: メモリと autoMemoryReclaim

-1 はつまり「勝手に落とすな」という指定なので、WSL2 の VM が常駐してメモリを保持し続ける。ノート PC で気になるなら、メモリ回収を併用する手がある。

[wsl2]
memory=4GB

[experimental]
autoMemoryReclaim=gradual

autoMemoryReclaim は Learn の [experimental] セクションに載っている設定で、gradual はキャッシュメモリをゆっくり回収する。ただし実験的機能という位置づけなので、そのつもりで使う。

sudoers の行は、この手順では使われない

定番手順の 2 番目、/etc/sudoers.d/service-ssh-start を置くステップについて。

echo 'your-username ALL=(root) NOPASSWD: /usr/sbin/service ssh start' | sudo tee /etc/sudoers.d/service-ssh-start

これは「一般ユーザーがパスワードなしで sudo service ssh start を実行できるようにする」設定だ。だが手順 3 のバッチは wsl -u root で、最初から root として実行している。sudo は一度も介在しない。

つまりこの行は、この手順の中では使われないまま /etc/sudoers.d/ に残る。害が大きいわけではないが、sudoers に不要なエントリを残すのは避けたい。WSL の中から一般ユーザーとして手で sudo service ssh start を叩く運用も併用するなら意味があるので、そこは自分の使い方次第で判断すればいい。

systemd を使うなら service コマンド自体が不要になる

もう一つ、そもそも論として。WSL は systemd をサポートしている。前回の記事では systemd 有効を前提に systemctl enable --now ssh で導入したが、ここでは systemd を切ったままの環境も含めて整理しておく。

# /etc/wsl.conf
[boot]
systemd=true

これはストア版 WSL の 0.67.6 以降で使える。wsl --version が認識されないなら古いインボックス版なので、先に更新が要る。設定後は PowerShell から wsl --shutdown して入れ直す。

systemd が動いていれば、SSH の自動起動は Linux 側の作法で済む。

sudo systemctl enable ssh
sudo systemctl start ssh

これで WSL インスタンスが起動するたびに sshd が上がるので、Windows 側のバッチから service ssh start を叩く必要がなくなる。

ただし勘違いしないでほしいのは、これが解決するのは「インスタンス起動後に sshd を上げる」部分だけだということだ。WSL インスタンスそのものを起動するトリガーは、依然として Windows 側に要る。つまりタスクスケジューラの出番は消えない。変わるのはバッチの中身だけだ。wsl -u root -- service ssh start が wsl -d Ubuntu -e true のような「起動するだけ」の呼び出しになる。罠 1 と罠 2 はそのまま残る。

なお wsl.conf の [boot] command にコマンドを書く手もあるが、Learn には「Boot 設定は Windows 11 と Server 2022 でのみ利用可能」と明記されている。加えて #8661 の通り、[boot] command で起動したデーモンはインスタンスの延命に寄与しない。

ネットワークの前提: NAT とミラーモード

sshd が上がっても、そこに届かなければ意味がない。WSL2 は既定で NAT の下にいるため、接続経路の話は別途ついて回る。

このあたりは前回の記事のミラーモードの副作用で詳しく書いた。要点だけ再掲すると、ミラーモードは NAT の上位互換ではない。Windows とポートが競合するようになり、localhostForwarding は設定ごと無視され、LAN から WSL に直接届くようになる。

最後の点は、自動起動と組み合わせると意味が変わる。手で service ssh start していた頃は「使うときだけ開くポート」だったものが、自動起動にした瞬間起動のたびに開きっぱなしのポートになる。ミラーモードなら、それが LAN から見える。

セキュリティは自動起動で前提が変わる

というわけで、自動起動を入れるなら最低限これは押さえておきたい。

パスワード認証を切る。 openssh-server を入れた直後の Ubuntu はパスワード認証が有効だ。常時上がっているポートをパスワードで守るのは無理がある。公開鍵を置いたうえで /etc/ssh/sshd_config を絞る。

PasswordAuthentication no
PubkeyAuthentication yes

ポート競合を確認する。 Windows 側に OpenSSH サーバーが入っていると、ミラーモードでは 22 番が競合する。WSL 側を 2222 番などにずらすか、.wslconfig の [experimental] ignoredPorts で逃がす。

待ち受け範囲を確認する。 sudo ss -tlnp | grep sshd で、0.0.0.0:22 なのか 127.0.0.1:22 なのかを見ておく。

直した手順: タスクスケジューラと .wslconfig の最終形

以上を反映した最終形はこうなる。

1. WSL 側

sudo apt update
sudo apt install openssh-server -y

公開鍵を ~/.ssh/authorized_keys に置き、/etc/ssh/sshd_config で PasswordAuthentication no にする。systemd を有効にしているなら、あわせて sudo systemctl enable ssh。

2. Windows 側 — %UserProfile%\.wslconfig

[general]
instanceIdleTimeout=-1

[wsl2]
vmIdleTimeout=-1

3. Windows 側 — タスクスケジューラ

4. 確認

再起動後、PowerShell から次を確認する。

wsl -l -v

STATE が Running のままかどうか。ここで 15 秒待って Stopped になるなら、罠 2 の設定が効いていない。.wslconfig を書き換えたら wsl --shutdown が必要な点にも注意(Learn の言う「8 秒ルール」で、完全に停止するまで設定は反映されない)。

sshd 側は WSL に入って確認する。

sudo ss -tlnp | grep sshd

まとめ

「公式ドキュメントに書いてあることだけでは足りず、Issue のスレッドを読まないと動かない」というのは、前回の ~/.bashrc の話と同じ形をしている。動かない理由が仕様として決まっているのに、その仕様がドキュメントの表に無い、というタイプの詰まり方だ。この手の構成を組むときは、wsl -l -v の STATE のような中間状態を直接見られるコマンドを先に手に入れておくと、切り分けが一気に楽になる。

参考



前の記事
サブエージェントの役割は tools で縛る — ECC の68体を数えて見えた設計と抜け穴
次の記事
Video Use の「沈黙する失敗」— ffmpeg が成功しても動画が壊れる 5 つの罠と 12 の Hard Rules