Windows Server 2025 includes OpenSSH—no need to install additional software. However, you need to configure it before you can use it. In this post, I'll show you how to use Ansible and key-based authentication to connect to and manage Windows Server 2025.
Prerequisites:
OpenSSH is installed by default on Windows Server 2025. The sshd service is the OpenSSH server process and needs to be enabled and set to automatic.
You can accomplish this in a few different ways. However, I prefer to use PowerShell.
set-service -name sshd -StartupType Automatic
Next, a firewall rule is needed if the Windows Firewall is used.
New-NetFirewallRule -DisplayName 'Allow SSH' -Name 'Allow SSH' -Profile Any -LocalPort 22 -Protocol TCP
Next, we need to set the default SSH shell to use PowerShell instead of cmd.
New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -PropertyType String -Force
Reboot and verify the service is running.
get-service -name sshd
Connect using SSH from a remote server with the OpenSSH client installed. If successful, move on to the next phase.
ssh Administrator@servername
Ansible - Connect Using Username/Password Authentication:
In this example, I'm using Ansible version 2.17.7 running on Ubuntu 22.04 within Windows Subsystem for Linux. There are 4 Windows Server 2025 managed nodes that I'll be using Ansible to configure:
- DC1 - This will eventually be a domain controller.
- SQL1 thru SQL3 - SQL Server 2022 hosts.
But first, we need to ensure Ansible can successfully connect using key-based authentication from the control node. VS Code is installed on the server hosting WSL along with the WSL and Ansible VS Code extensions. Next, we'll need an inventory file that contains our managed hosts along with the connection parameters. The first inventory file we'll use is hosts_initial.ini. You'll see why in a moment.
hosts_initial.ini
[sqlservers]
SQL1
SQL2
SQL3
[domaincontroller]
DC1
[all:vars]
ansible_connection = ssh
ansible_shell_type = powershell
ansible_ssh_common_args='-o StrictHostKeyChecking=no'
Before we move on, let's break down what's defined in this inventory file:
- Groups
- [sqlservers] - This group contains three hosts: SQL1, SQL2, and SQL3. These represent the SQL Server machines that Ansible will manage.
- [domaincontroller] - This group contains a single host: DC1. This server will be configured, using Ansible, as a domain controller.
- Group Variables
- [all:vars] - This section sets variables for all hosts listed in this inventory.
- ansible_connection = ssh - Instructs Ansible to connect to these hosts using SSH rather than WinRM.
- ansible_shell_type = powershell - Tells Ansible that the remote shell will be PowerShell, ensuring modules are executed correctly on Windows targets.
- ansible_ssh_common_args = '-o StrictHostKeyChecking=no' - Disables strict host key checking for SSH connections, ensuring Ansible doesn't halt if the host key isn't recognized. Since this will be the first connection, Ansible will fail because the key is not recognized.
- [all:vars] - This section sets variables for all hosts listed in this inventory.
The first time we connect, we're using username/password authentication. Let's do that now using an Ansible ad-hoc command and the ansible.windows.win_ping module. I'm using the local Administrator account configured on each machine.
On the control node, run the following command:
ansible all -i hosts_initial.ini -m ansible.windows.win_ping -u Administrator --ask-pass
This command should return four SUCCESS messages if we've done everything correctly.
Next, we'll set up key authentication on the four managed nodes.
Ansible - Configure Windows Managed Nodes for Key-Based Authentication:
Let's create the SSH key pair now. From the Ansible control node (Ubuntu), execute the following commands. I'm going to leave the passphrase empty.
1. ssh-keygen -b 4096
2. ssh-agent bash
3. ssh-add ~/.ssh/id_rsa
Here's what each of the commands above do:
- ssh-keygen -b 4096
- This command generates a new SSH key pair with a 4096-bit key size. Running this command interactively will prompt you for a filename (default is usually ~/.ssh/id_rsa) and an optional passphrase to protect the private key. The result is two files:
- A private key file (id_rsa). Keep this secure.
- A corresponding public key file (id_rsa.pub). We'll copy the contents of this file to our managed nodes.
- This command generates a new SSH key pair with a 4096-bit key size. Running this command interactively will prompt you for a filename (default is usually ~/.ssh/id_rsa) and an optional passphrase to protect the private key. The result is two files:
- ssh-agent bash
- This command starts a new bash shell with the ssh-agent process running in the background. The ssh-agent is a program that holds your private keys in memory and provides them to SSH clients as needed. By using ssh-agent, you don't have to repeatedly enter your private key passphrase every time you initiate an SSH connection.
- ssh-add ~/.ssh/id_rsa
- This command adds the private key (id_rsa) that you generated in step 1 to the ssh-agent you started in step 2. If the key is passphrase-protected (ours isn't), you'll be prompted to enter it once. After that, the agent takes care of providing your key to SSH sessions, so you won't need to re-enter the passphrase for each new connection during that agent session.
Next, we need to add the public key to the C:\ProgramData\ssh\administrators_authorized_keys file on each of the managed hosts. Now, we could do that manually, but if we had 10s or 100s of new Windows Server 2025 machines to configure, that wouldn't be ideal. We'll create a playbook to do this.
This playbook will perform the following 6 steps on the Windows-managed nodes defined in the hosts_initial.ini file.
1. Reads a public SSH key from the Ansible controller's file system.
2. Ensures the administrators_authorized_keys file exists on the remote Windows hosts and is ready for modification.
3. Appends the retrieved public key to that file.
4. Updates the SSHD configuration to disable password authentication.
5. Restart the SSHD service to apply the new configuration.
6. Provides debug output showing the results of adding the key.
Save the playbook as playbook_addKey.yml and then run it using ansible-playbook.
ansible-playbook playbook_addKey.yml -i hosts_initial.ini -u Administrator --ask-pass
After a few seconds, you'll see that Ansible has made the required changes.
Next, we'll create a new inventory file named hosts.ini and configure the connection parameters differently.
Test using Key Based Authentication:
Create a new file named hosts.ini and add the following text:
[sqlservers]
SQL1
SQL2
SQL3
[domaincontroller]
DC1
[all:vars]
ansible_connection = ssh
ansible_shell_type = powershell
ansible_user = Administrator
We've replaced the ansible_ssh_common_args='-o StrictHostKeyChecking=no' parameter with ansible_user = Administrator.
Next, we'll run the ad-hoc command using the win_ping module. However, this time, we won't specify a user or password.
ansible all -i hosts.ini -m ansible.windows.win_ping
SUCCESS!
Conclusion:
With SQL Server 2025 on the horizon and Windows Server 2025 already released, I know many of you will be upgrading and migrating environments soon. Dev environments are typically first followed by everything else. 2025 will be a busy year, and I hope to get ahead by using some of the new features available on Server 2025. Using SSH as a common connection protocol will make automation easier when working across Linux and Windows. I plan to test SSH with Kerberos and domain users soon. How are you planning to get ahead?
You can watch a walkthrough of this process using this link.
Thanks for reading!
Get free access to my "SQL Server Automation: Your First Steps with Ansible" Guide
Get started with Ansible using this free guide. You'll discover how simple Ansible is to use, understand core concepts, and create two simple playbook examples.
When you signup, we'll send you periodic emails with additional free content.