
ホームラボを管理しよう。OpenSSHでLinux母艦からWindows 11をAnsible管理する
$PROFILE パス乖離など、構築時にハマったポイントと対処法を実践的なPlaybook(Fastfetch導入例)とともにまとめています。はじめに
ホームラボ環境で複数台のLinuxサーバーをAnsibleでコード管理(IaC)していると、どうしても1台だけ浮いてしまいがちなのがWindowsマシンです。
かつてWindowsをAnsibleで管理する際の主流は WinRM1 でした。ただ、WinRMは認証周りの設定やHTTPS化(証明書管理)がなかなか面倒で、Linuxノードと同じ感覚でサクッと管理ノードに追加できないのが地味なストレスでした。
現在では、Windows 10(1809以降)やWindows 11に標準の OpenSSH Server が搭載されているため、WinRMを一切使わずに、Linuxとまったく同じ「SSH + 鍵認証」の仕組みでAnsible管理ができるようになっています。
この記事では、Windows 11をOpenSSH経由でAnsible管理下におさめるセットアップ手順と、実際に試す中でハマった「管理者権限の鍵ルール」「画面フリーズ」「$PROFILEのパス乖離」といった罠の回避策をまとめてみます。
全体構成と検証環境

全体構成と検証環境
- 制御ノード(Ansible母艦): RHEL系Linux(または macOS)
- ターゲットノード: Windows 11 Pro(IP:
192.168.X.Xなど) - ターゲット側ユーザー:
WINDOWS_USER(管理者権限保持) - 接続方式: OpenSSH(Port 22)
1. Windows 10 / 11 側の OpenSSH Server セットアップ
昔はWindowsでSSHサーバーを動かすためにサードパーティ製ツールを入れる必要がありましたが、今はOS標準のオプション機能(Feature on Demand)として OpenSSH Server が提供されています。
まずはWindows 11側で 管理者権限のPowerShell を開き、OpenSSH Server の有効化と、SSH接続時のデフォルトシェルをコマンドプロンプトから PowerShell へ切り替える設定を行います。
1-1. OpenSSH Server のインストールと起動
# OpenSSH Server 機能のインストール
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
# サービスの起動
Start-Service sshd
# OS起動時に自動実行されるよう設定
Set-Service -Name sshd -StartupType 'Automatic'
1-2. デフォルトシェルを PowerShell に変更
初期状態のままSSH接続すると コマンドプロンプト が起動してしまいます。Ansibleからの制御をスムーズに行うため、レジストリを変更してデフォルトシェルを PowerShell に設定します。
New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -PropertyType String -Force
2. ハマりポイント①:Administratorsグループの鍵参照ルール
ここが最初の大きなハマりどころでした。WindowsのOpenSSHでは、「管理者権限(Administratorsグループ)を持つユーザーの場合、通常の ~/.ssh/authorized_keys を無視する」 というデフォルト仕様が存在します。
そのため、普通に ~/.ssh/authorized_keys に鍵を置いても無視され、C:\ProgramData\ssh\administrators_authorized_keys を見に行こうとしてログインに失敗します。Linuxと同じ挙動にするため設定を変更します。
2-1. sshd_config の修正
管理者権限で C:\ProgramData\ssh\sshd_config を開きます。
.txt が付与され、設定ファイルが読み込まれなくなることがあります。既存の sshd_config を直接開いて上書きするか、VS Codeが入っているならターミナルから code C:\ProgramData\ssh\sshd_config で開くのが確実です。保存後は Get-ChildItem C:\ProgramData\ssh\sshd_config* でファイル名を確認します。ファイルの最下部にある以下の2行を探し、先頭に # を付けてコメントアウトします。
# Match Group administrators
# AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys
2-2. SSH サービスの再起動
設定反映のため、SSHサービスを再起動しておきます。
Restart-Service sshd
3. ハマりポイント②:ファイアウォールとネットワークプロファイル
SSH接続時に Operation timed out になる場合は、sshd の待受状態、Windowsファイアウォール、IPアドレスや経路を順に確認します。タイムアウトだけで原因をファイアウォールに限定することはできません。
3-1. ファイアウォールルールの有効化
OpenSSH Serverをインストールするとルール自体は自動生成されますが、無効のままになっていることがあります。
# OpenSSH インバウンドルールを有効化
Enable-NetFirewallRule -Name 'OpenSSH-Server-In-TCP'
# ルールの確認(Enabled が True になっていればOK)
Get-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' | Select-Object Name, Enabled, Direction, Action
3-2. ファイアウォールルールの適用プロファイルを確認
Public だから必ずSSHが遮断されるわけではなく、ルールの Profile 設定次第です。現在のネットワークカテゴリと、OpenSSHルールが適用されるプロファイルを確認します。
# 現在のネットワークカテゴリを確認
Get-NetConnectionProfile
# OpenSSHルールが適用されるプロファイルを確認
Get-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' |
Select-Object Name, Enabled, Profile, Direction, Action
信頼できる自宅LANをWindows上でもプライベートネットワークとして扱いたい場合に限り、対象を明示して変更します(公共のネットワークでは変更しません)。
Set-NetConnectionProfile -InterfaceIndex <確認したInterfaceIndex> -NetworkCategory Private
4. SSH公開鍵の配置と厳格なアクセス権限(icacls)の設定
WindowsのOpenSSHはパーミッションチェックが厳格で、authorized_keys ファイルのアクセス権限(ACL)に他のユーザーの読み取り権限が残っていると、安全のために鍵を無視してパスワード認証へフォールバックしてしまいます。
4-1. 公開鍵の配置
Linux/Mac母艦側で作成した公開鍵(id_ecdsa.pub 等)の内容を、Windowsターゲットのユーザーディレクトリ配下に配置します。
PowerShellで実行:
# .ssh ディレクトリの作成
New-Item -ItemType Directory -Path "C:\Users\WINDOWS_USER\.ssh" -Force
# 公開鍵ファイルの作成
# Windows PowerShell 5.1 の -Encoding utf8 はBOMを付けるため、ASCII文字だけで
# 構成されるOpenSSH公開鍵は ascii を指定する
Set-Content -Path "C:\Users\WINDOWS_USER\.ssh\authorized_keys" -Value "ecdsa-sha2-nistp256 AAAAE3NzaC1..." -Encoding ascii
4-2. アクセス権限(ACL)の剥奪と再付与
icacls コマンドを使って、アクセス権を「対象ユーザー本人」と「SYSTEM」のみに絞り込みます。
$path = "C:\Users\WINDOWS_USER\.ssh\authorized_keys"
# 親フォルダからの権限継承を解除し、既存アクセス権をクリア
icacls $path /inheritance:r
# 本人と SYSTEM にのみフルコントロール権限を付与
icacls $path /grant:r "WINDOWS_USER:F"
icacls $path /grant:r "SYSTEM:F"
権限を設定したら、念のため Restart-Service sshd でSSHサービスを再起動しておきます。
5. ハマりポイント③:SSH接続直後に画面が固まるフリーズ対策
一部の環境では、OpenSSH経由でPowerShellを対話起動した際、入力補完モジュールの PSReadLine と端末処理の組み合わせにより、キー入力を受け付けないように見える現象が起きることがあります。これはすべての環境で必要な設定ではありません。まずWindows Update、OpenSSH、PSReadLineを更新し、それでも再現する場合の回避策として、SSHの対話セッションだけでPSReadLineを解除します。
対策:$PROFILE に判定処理を追加
Windows側でプロファイルを開き、以下のコードを追加します。
# プロファイルが存在しない場合は作成
if (!(Test-Path $PROFILE)) { New-Item -Type File -Path $PROFILE -Force }
code $PROFILE
追記する内容:
# TTYが割り当てられたSSH対話セッションだけでPSReadLineを無効化
# SSH_CONNECTIONだけで判定すると、Ansibleの非対話実行にも影響する
if ($env:SSH_TTY) {
Remove-Module PSReadLine -ErrorAction SilentlyContinue
}
これでローカル操作とAnsibleの非対話実行には影響を与えず、SSHの対話操作時だけモジュールを解除できます。
6. 制御ノード(Linux/Mac母艦)からの Ansible 接続設定
ここまで準備できたら、Ansible母艦から接続確認を行っていきます。
6-1. ansible.windows コレクションの導入
Windowsモジュール群を利用するため、ansible.windows コレクションをインストールします。あわせて ansible --version で ansible-core 2.18以降であることを確認してください。
collections/requirements.yml を作成:
collections:
- name: ansible.windows
インストール実行:
ansible-galaxy collection install -r collections/requirements.yml
6-2. インベントリファイル(hosts.yml)の作成
SSH経由でPowerShellを実行させるため、ansible_connection: ssh と ansible_shell_type: powershell を指定します。
all:
children:
windows:
hosts:
windows11dev:
ansible_host: 192.168.X.X # ターゲットのWindows IPアドレス
# 実機側で確認したプロファイルの絶対パスを定義
powershell_profile_path: 'C:\Users\WINDOWS_USER\OneDrive\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1'
vars:
ansible_user: WINDOWS_USER
ansible_connection: ssh
ansible_ssh_private_key_file: /home/ansible/.ssh/id_ecdsa
ansible_shell_type: powershell
ansible_become: false
become: true(特権昇格)を有効にしている環境の場合、Windowsグループでは ansible_become: false を明示しておかないと認証エラーの原因になります。6-3. 疎通テスト(win_ping)
win_ping モジュールで疎通を確認してみます。
ansible windows11dev -m ansible.windows.win_ping
成功すると以下のように返ってきます。
windows11dev | SUCCESS => {
"changed": false,
"ping": "pong"
}
7. 実践編:WingetでのFastfetch導入と $PROFILE の冪等管理
単なる疎通確認だけで終わるのも味気ないので、実際のPlaybook例として「Winget経由のFastfetch導入」と「SSHログイン時にFastfetchを自動実行するプロファイル設定」を作成してみます。
7-1. ハマりポイント④:対話/非対話での $PROFILE パス乖離問題
PowerShellの $PROFILE は単一の固定値ではなく、ホスト(ConsoleHostなど)・実行ユーザー・対象範囲によって複数あります。また、Ansibleモジュールの非対話実行ではプロファイルを読み込むこと自体を前提にすべきではありません。
特にWindows 11で OneDriveのドキュメントリダイレクト が有効になっていると、実際のプロファイルパスは以下のようにOneDrive配下を指すようになります。
- 実際の参照先例:
C:\Users\WINDOWS_USER\OneDrive\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1
そのため、Playbook内で動的に $PROFILE を参照させるのではなく、インベントリ変数(powershell_profile_path)として確認済みの絶対パスを渡すのが一番確実でした。
7-2. ハマりポイント⑤:末尾改行がないファイルへの追記事故
既存の $PROFILE の最終行に改行コードが入っていない状態で単純に文字列を追加すると、前の行の末尾と新しいコマンドが連結してスクリプトが壊れてしまう事故が起きます。
# 事故例:最終行の直後に「fastfetch」が繋がってしまう
Set-Alias csc C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exefastfetch
7-3. 完成した Playbook(install_fastfetch_windows.yml)
改行の自動補正と、マーカーコメントによる冪等性(何度実行しても安全に同じ状態へ収束すること)を考慮したPlaybookです。winget とプロファイル更新の両方で、実際に変更した場合だけAnsibleへ変更ありと通知します。
- name: Install Fastfetch and Configure PowerShell Profile
hosts: windows
gather_facts: false
tasks:
- name: Winget経由で Fastfetch をインストール
ansible.windows.win_powershell:
script: |
if (Get-Command fastfetch -ErrorAction SilentlyContinue) {
$Ansible.Changed = $false
return
}
winget install --id Fastfetch-cli.Fastfetch --exact --source winget `
--accept-source-agreements --accept-package-agreements `
--disable-interactivity
if ($LASTEXITCODE -ne 0) {
throw "winget install failed (exit code: $LASTEXITCODE)"
}
$Ansible.Changed = $true
- name: PowerShellプロファイルへ Fastfetch を冪等に追記
ansible.windows.win_powershell:
parameters:
ProfilePath: "{{ powershell_profile_path }}"
script: |
param([string]$ProfilePath)
$parent = Split-Path -Parent $ProfilePath
if (-not (Test-Path -LiteralPath $parent)) {
New-Item -ItemType Directory -Path $parent -Force | Out-Null
}
$content = if (Test-Path -LiteralPath $ProfilePath) {
Get-Content -Raw -LiteralPath $ProfilePath
} else {
""
}
if ($content -match '(?m)^# BEGIN ANSIBLE MANAGED FASTFETCH\s*$') {
$Ansible.Changed = $false
return
}
if ($content.Length -gt 0 -and -not $content.EndsWith("`n")) {
$content += "`r`n"
}
# AnsibleもSSHを使うため、TTYのある対話ログイン時だけ実行する
$content += @'
# BEGIN ANSIBLE MANAGED FASTFETCH
if ($env:SSH_TTY) {
fastfetch
}
# END ANSIBLE MANAGED FASTFETCH
'@
$content += "`r`n"
Set-Content -LiteralPath $ProfilePath -Value $content -Encoding utf8
$Ansible.Changed = $true
7-4. 実行と動作確認
作成したPlaybookを実行します。
ansible-playbook install_fastfetch_windows.yml
実行完了後、Ansible母艦から手動でSSHログインしてみます。
ssh -i /home/ansible/.ssh/id_ecdsa WINDOWS_USER@192.168.X.X
ログイン直後にFastfetchが自動起動し、Windows 11のシステム情報とアスキーアートが表示されれば完了です。

Windows 11にSSHした画面
トラブルシューティングまとめ
| 症状 | 主な原因 | 対策 |
|---|---|---|
| Operation timed out | sshd停止、22番ポート未待受、ファイアウォール、IP/経路の問題 | Get-Service sshd、Get-NetTCPConnection -LocalPort 22、ファイアウォールルールを順に確認 |
| パスワードを聞かれる | Administrators グループの個別ルール / ACL(権限)が緩い | sshd_config のコメントアウト & icacls で権限を本人のみに絞る |
| 接続直後に画面が固まる | PSReadLine と OpenSSH(ConPTY)の組み合わせ | $PROFILE で $env:SSH_TTY がある場合に Remove-Module PSReadLine |
| 設定追記後にスクリプトエラー | 対象ファイルの最終行に改行がない | 追記前にファイルが改行で終わっているか判定して補正 |
おわりに
Windows 11をAnsibleで管理する際、OpenSSHを使うことで「WinRM特有の証明書トラブル」や「独自ポートの設定」から解放され、Linuxサーバーと同じ「SSH+鍵認証」の思考モデルで扱えるようになるのが一番のメリットだと感じました。
アクセス権限(ACL)の設定や $PROFILE のパスなど、Windowsならではの仕様でいくつか罠はありますが、一度設定してPlaybook化してしまえばホームラボの運用効率がぐっと上がります。Windowsマシンの管理に悩んでいる方は、ぜひ試してみてください。
WinRM(Windows Remote Management)は、Windowsをリモート管理するためのWS-Managementプロトコル実装です。 ↩︎
