Deploying to an on-prem Windows server
On this page
Say a team builds its app in AWS, and one of the places it runs is a Windows server in a company data center, app01.example.com. Every release produces a new installer, reporting-service-2.4.0.msi, and the release pipeline has to get it installed on app01 without anyone logging in to click through it. Three tools can do that job, and the biggest difference between them is which machine opens the network connection. That direction decides what the data center’s firewall has to allow, and the firewall usually decides the answer.
A few terms
- On-prem (on-premises): running in a data center or office you control, not in a cloud provider’s.
- Pipeline: the automated steps that take a change from code to a running release: build, test, deploy.
- Agent: a small program installed on a server that does work on a service’s behalf, like the CodeDeploy agent or SSM Agent.
- Inbound and outbound: from the server’s point of view, an inbound connection is one something else opens to it, and an outbound connection is one the server opens itself. A firewall rule usually allows one direction for a given port.
- MSI: a Windows Installer package, the
.msifile thatmsiexecinstalls [1]. - WinRM (Windows Remote Management): the Windows service that lets another machine run commands on this one over the network [2].
- Ansible: a tool that configures machines by running playbooks, files that list the steps to apply, from a control node, the machine where Ansible itself runs [3].
- CodeDeploy and Systems Manager: two AWS services that can run work on servers, including servers that aren’t in AWS [4], [5].
Who opens the connection
The arrows point from the machine that opens the connection. With both agents, app01 reaches out and asks for work, so the firewall only has to let it out. With Ansible, the control node reaches in, so the firewall has to let something in.
Both agents work the same way: the agent on app01 opens a connection out to the AWS service and asks whether there’s anything to do, and the work comes back over that connection. Nothing outside ever connects in to the server [6], [7]. Ansible is the other way around. The control node connects to WinRM on the server and runs the steps over that connection [8], so a path from the control node to app01 has to exist and the firewall has to allow it.
| CodeDeploy | Ansible over WinRM | Systems Manager | |
|---|---|---|---|
| Who opens the connection | The agent on the server | The control node | The agent on the server |
| Inbound rule on the server | None | TCP 5985 (HTTP) or 5986 (HTTPS) | None |
| Outbound from the server | HTTPS 443 to AWS | None | HTTPS 443 to AWS |
| Runs an installer | Yes, from a hook script | Yes | Yes, as a command |
| Runs a playbook against the server | Only through a Linux control node | Yes | Only through a Linux control node |
The rest of this primer takes them one at a time, then how to choose.
CodeDeploy on a non-AWS server
CodeDeploy treats any machine that can run its agent and reach AWS’s public endpoints as an on-premises instance, and deploys to it the same way it deploys to EC2 [4], [9]. The agent is tested on Windows Server 2016, 2019, 2022, and 2025 [6], and its only network requirement is outbound HTTPS on port 443 [9].
A deployment is a bundle of files with an appspec.yml at its root. The AppSpec file says where the files go and which scripts to run at each lifecycle event, the named steps of a deployment such as BeforeInstall, AfterInstall, and ValidateService [10]. For app01, the bundle holds the MSI and one script:
version: 0.0
os: windows
files:
- source: installer
destination: C:\deploy\reporting-service
hooks:
AfterInstall:
- location: scripts\install-msi.ps1
timeout: 1800
That’s the shape of AWS’s AppSpec example, with the Windows changes its docs call for: os: windows, a fully qualified destination path, and no runas setting, which Windows Server doesn’t support [11]. timeout is how long the script may run before it counts as failed. It can be at most 3600 seconds (an hour) for everything in one lifecycle event, and a script that runs past it fails the deployment on that server [10].
The hook script
CodeDeploy treats an exit code of 0 as success and anything else as failure [11]. Windows Installer doesn’t quite agree. It reports success with 0, and also with 3010 (installed, restart needed) and 1641 (installed, restart started) [12]. A script that passes msiexec’s exit code straight through fails a deployment that worked, so install-msi.ps1 translates it:
$msi = 'C:\deploy\reporting-service\reporting-service-2.4.0.msi'
$log = 'C:\deploy\reporting-service\install.log'
# /i installs, /qn shows no UI, /l*v writes a detailed log
$p = Start-Process msiexec.exe -ArgumentList "/i `"$msi`" /qn /l*v `"$log`"" -Wait -PassThru
# 0, 3010, and 1641 all mean the install succeeded
if ($p.ExitCode -in 0, 3010, 1641) { exit 0 }
exit $p.ExitCode
msiexec’s /i installs the package, /qn turns off its user interface, and /l*v writes a log of everything it did [1]. -Wait makes Start-Process wait for the installer to finish, and -PassThru returns the process so the script can read its exit code [13]. Without -Wait, the script would return before the install did. A restart-required result still needs a restart scheduled somewhere, and this script deliberately leaves that decision to whoever owns the server.
Registering the server {only: AWS}
Before app01 can receive deployments, it has to be registered with CodeDeploy, using an administrator account on the server [9]. AWS recommends registering with an IAM session ARN, which gives the agent temporary credentials, for production deployments of any size. The other method, an IAM user with permanent access keys, is only meant for non-production use [14].
The temporary credentials expire after an hour at most, so something on your side has to keep replacing them, either a scheduled job that copies fresh credentials from your own identity system to the server or one on the server that requests them from AWS. If the credentials aren’t refreshed, deployments to the server fail [15]. That refresh job is the part of this option most likely to be forgotten, and the first thing to check when deployments that used to work start failing.
Ansible over WinRM
Ansible doesn’t install an agent on the machines it manages. The control node connects to each Windows host and runs the playbook’s steps over that connection. It can use WinRM, PowerShell remoting over WinRM, or SSH, which Ansible has officially supported for Windows since version 2.18 [8]. Targets need Windows Server 2016 or Windows 10 or newer [8].
WinRM listens on TCP 5985 for HTTP and 5986 for HTTPS [2], and both are inbound to the server. The control node needs a network path to app01, through a VPN or a private connection if it runs outside the data center, or none at all if it runs inside.
The control node can’t be Windows
Ansible’s control node has to be a UNIX-like machine with Python, such as Linux or macOS. Windows only works under WSL (the Windows Subsystem for Linux), and native Windows isn’t supported [3], [8]. So a playbook can’t simply run on app01 itself. It has to run from a Linux machine that reaches app01 over WinRM.
To check that the control node can reach the server and log in, Ansible has a test module that runs a trivial command over the connection [16]:
ansible app01.example.com -m ansible.windows.win_ping
The control node is always Linux or macOS, so this one has no PowerShell version.
Authentication
WinRM supports several ways to log in, and they differ in which accounts work and whether the traffic is protected without HTTPS [2]:
| Method | Local accounts | Domain accounts | Encrypted over plain HTTP |
|---|---|---|---|
| Basic | Yes | No | No |
| Certificate | Yes | No | No |
| NTLM | Yes | Yes | Yes |
| Kerberos | No | Yes | Yes |
| CredSSP | Yes | Yes | Yes |
Ansible’s guidance is Kerberos when the server is in a domain, and Basic or NTLM over HTTPS (port 5986) for local accounts. It warns against Basic or NTLM over an HTTP listener, since they offer no encryption or very weak encryption there, and it treats CredSSP as a last resort, because it hands the server credentials it can reuse elsewhere [2].
Systems Manager Run Command
Run Command is the part of AWS Systems Manager that runs commands on managed nodes: EC2 instances, or other machines set up for Systems Manager [5]. Setting up app01 takes a hybrid activation, an activation code and ID you create in AWS with an IAM role attached, which you give to SSM Agent when you install it on the server [17].
The agent then connects out over HTTPS on port 443 to the Systems Manager endpoints (ssm, ssmmessages, and ec2messages in your Region). It opens every connection itself, so the firewall needs no inbound rule [7]. Running the installer is one Run Command invocation that executes the same PowerShell as the CodeDeploy hook above, without a bundle, an AppSpec file, or lifecycle events. Systems Manager can also run Ansible playbooks, but its AWS-ApplyAnsiblePlaybooks document runs Ansible on the managed node itself, and the dependencies it installs are Linux packages, so it fits a Linux control node, not app01 [18].
The pricing changed in 2026. Registering machines outside AWS is now free, and since September 30, 2026, Run Command on those machines costs $0.002 per invocation, while it stays free on EC2 [19], [5]. Check the pricing page before relying on it, since it changed twice in 2026.
Which one to pick
- The server may only connect out, and the job is an installer. CodeDeploy if you want deployments tracked, with an AppSpec file, lifecycle events, and a history per server. Run Command if one command is enough.
- Playbooks already exist, and a path from a Linux control node to the server is acceptable. Ansible over WinRM, over HTTPS, with Kerberos or NTLM.
- Both. The control node is a Linux machine, so it can run a CodeDeploy or SSM agent itself. The pipeline deploys to the control node, and the control node runs the playbook against
app01over WinRM. Only the control node needs outbound 443, and only it needs a path in toapp01, which is easier to allow when both sit in the same data center.
In most cases the firewall picks for you. If nothing may connect in to app01, the choice is between the two agents.
References
Show all 19 sourcesHide sources
- [1] ↩ a b Microsoft, “msiexec,” Windows Server Documentation. Accessed: Oct. 12, 2026. [Online]. Available: https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/msiexec
- [2] ↩ a b c d Ansible project contributors, “Windows Remote Management,” Ansible Community Documentation. Accessed: Oct. 12, 2026. [Online]. Available: https://docs.ansible.com/projects/ansible/latest/os_guide/windows_winrm.html
- [3] ↩ a b Ansible project contributors, “Installing Ansible,” Ansible Community Documentation. Accessed: Oct. 12, 2026. [Online]. Available: https://docs.ansible.com/projects/ansible/latest/installation_guide/intro_installation.html
- [4] ↩ a b Amazon Web Services, “Working with on-premises instances for CodeDeploy,” AWS CodeDeploy User Guide. Accessed: Oct. 12, 2026. [Online]. Available: https://docs.aws.amazon.com/codedeploy/latest/userguide/instances-on-premises.html
- [5] ↩ a b c Amazon Web Services, “AWS Systems Manager Run Command,” AWS Systems Manager User Guide. Accessed: Oct. 12, 2026. [Online]. Available: https://docs.aws.amazon.com/systems-manager/latest/userguide/run-command.html
- [6] ↩ a b Amazon Web Services, “Working with the CodeDeploy agent,” AWS CodeDeploy User Guide. Accessed: Oct. 12, 2026. [Online]. Available: https://docs.aws.amazon.com/codedeploy/latest/userguide/codedeploy-agent.html
- [7] ↩ a b Amazon Web Services, “Improve the security of EC2 instances by using VPC endpoints for Systems Manager,” AWS Systems Manager User Guide. Accessed: Oct. 12, 2026. [Online]. Available: https://docs.aws.amazon.com/systems-manager/latest/userguide/setup-create-vpc.html
- [8] ↩ a b c d Ansible project contributors, “Managing Windows hosts with Ansible,” Ansible Community Documentation. Accessed: Oct. 12, 2026. [Online]. Available: https://docs.ansible.com/projects/ansible/latest/os_guide/intro_windows.html
- [9] ↩ a b c Amazon Web Services, “Prerequisites for configuring an on-premises instance,” AWS CodeDeploy User Guide. Accessed: Oct. 12, 2026. [Online]. Available: https://docs.aws.amazon.com/codedeploy/latest/userguide/instances-on-premises-prerequisites.html
- [10] ↩ a b Amazon Web Services, “AppSpec "hooks" section,” AWS CodeDeploy User Guide. Accessed: Oct. 12, 2026. [Online]. Available: https://docs.aws.amazon.com/codedeploy/latest/userguide/reference-appspec-file-structure-hooks.html
- [11] ↩ a b Amazon Web Services, “AppSpec file example,” AWS CodeDeploy User Guide. Accessed: Oct. 12, 2026. [Online]. Available: https://docs.aws.amazon.com/codedeploy/latest/userguide/reference-appspec-file-example.html
- [12] ↩ Microsoft, “MsiExec.exe and InstMsi.exe error messages,” Windows Installer Documentation. Accessed: Oct. 12, 2026. [Online]. Available: https://learn.microsoft.com/en-us/windows/win32/msi/error-codes
- [13] ↩ Microsoft, “Start-Process,” PowerShell 7.6 Documentation. Accessed: Oct. 12, 2026. [Online]. Available: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/start-process
- [14] ↩ Amazon Web Services, “Register an on-premises instance,” AWS CodeDeploy User Guide. Accessed: Oct. 12, 2026. [Online]. Available: https://docs.aws.amazon.com/codedeploy/latest/userguide/on-premises-instances-register.html
- [15] ↩ Amazon Web Services, “Use the register-on-premises-instance command (IAM session ARN),” AWS CodeDeploy User Guide. Accessed: Oct. 12, 2026. [Online]. Available: https://docs.aws.amazon.com/codedeploy/latest/userguide/register-on-premises-instance-iam-session-arn.html
- [16] ↩ Ansible project contributors, “ansible.windows.win_ping module,” Ansible Community Documentation. Accessed: Oct. 12, 2026. [Online]. Available: https://docs.ansible.com/projects/ansible/latest/collections/ansible/windows/win_ping_module.html
- [17] ↩ Amazon Web Services, “Create a hybrid activation to register nodes with Systems Manager,” AWS Systems Manager User Guide. Accessed: Oct. 12, 2026. [Online]. Available: https://docs.aws.amazon.com/systems-manager/latest/userguide/hybrid-activation-managed-nodes.html
- [18] ↩ Amazon Web Services, “Creating associations that run Ansible playbooks,” AWS Systems Manager User Guide. Accessed: Oct. 12, 2026. [Online]. Available: https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-state-manager-ansible.html
- [19] ↩ Amazon Web Services, “AWS Systems Manager pricing.” Accessed: Oct. 12, 2026. [Online]. Available: https://aws.amazon.com/systems-manager/pricing/
Every source cited across the primers is collected on one page.