Why This Matters in Delivery
When Azure DevOps is internet-facing, security teams often reject direct on-prem deployment access. The challenge is not only to make the deployment work, but to make it work with a narrow and auditable security model.
- The business wanted automated Azure DevOps deployments to on-prem servers.
- The security team did not want broad exposure or uncontrolled agent access.
- The architect team wanted a solution they could trust and explain.
What I Actually Faced
I had to prove that Azure DevOps could deploy to on-prem servers securely for Web, App, DB, and file-system tasks. The concern was that if the deployment path was not tight enough, the servers would be exposed more than the security team could accept.
- The first proposal was blocked because the team did not want an overly permissive path from internet-facing services into the data center.
- The architect team was not confident that the on-prem deployment story could be secured well enough.
- I researched the remoting model and found a path that the team could support.
Once I proved the command execution path and permissions model, the architect team accepted the design.
Security Questions We Had to Answer
| Question | Expectation | Resulting Control |
|---|---|---|
| Can servers stay off the public internet? | Yes, only private connectivity should be used. | Use ExpressRoute/private network path. |
| Can remoting be encrypted end to end? | Yes, no plain-text command channel. | WinRM over HTTPS on port 5986. |
| Can access be limited? | Yes, only required ports and accounts. | Firewall allowlist + least-privilege deployment account. |
| Can we prove it before rollout? | Yes, with a small smoke test. | Pipeline creates and deletes a test file on each target host. |
The Secure Design I Chose
The deployment path is private, encrypted, and tightly scoped. Azure DevOps orchestrates the release, but the actual command execution happens through WinRM over HTTPS inside the private network boundary.
- Only the required WinRM HTTPS port is exposed on the on-prem servers.
- Certificate-backed listeners are used instead of plain HTTP WinRM.
- The deployment account is restricted to only the target actions it needs.
- Ports for application, database, and file-system activity are only opened when the deployment actually needs them.
WinRM HTTPS Setup
On the target Windows server, I used an HTTPS listener with a certificate and kept unencrypted remoting disabled. That gave us a remoting channel that the security team could reason about and audit.
| Item | Value | Why |
|---|---|---|
| WinRM HTTPS | 5986 | Encrypted PowerShell remoting channel |
| WinRM HTTP | 5985 | Not the secure path for this design |
| Certificate | Internal CA cert on each target server | Proves server identity during remoting |
| Firewall | Allow only required source networks | Minimise attack surface |
Sample Release Pipeline
The pipeline first checks connectivity, then runs a safe smoke action, then performs the actual deployment steps. That sequence gave the team confidence that access and permissions were in place before any real change ran.
trigger: none
pool:
name: SelfHostedWindows
steps:
- powershell: |
$servers = @('web01','app01','db01','fs01')
foreach ($server in $servers) {
Test-WSMan -ComputerName $server -UseSSL
}
displayName: 'Validate WinRM over HTTPS'
- powershell: |
$servers = @('web01','app01','db01','fs01')
$cred = New-Object System.Management.Automation.PSCredential($env:DEPLOY_USER,(ConvertTo-SecureString $env:DEPLOY_PASS -AsPlainText -Force))
foreach ($server in $servers) {
Invoke-Command -ComputerName $server -UseSSL -Credential $cred -ScriptBlock {
New-Item -Path 'C:\DeploySmoke' -ItemType Directory -Force | Out-Null
Set-Content -Path 'C:\DeploySmoke\pipeline.txt' -Value (Get-Date).ToString('o')
Remove-Item -Path 'C:\DeploySmoke\pipeline.txt' -Force
}
}
displayName: 'Smoke test permissions'
Validation Commands
Server-side checks
Enable-PSRemoting -Force
Set-Item WSMan:\localhost\Service\AllowUnencrypted $false
New-Item -Path WSMan:\localhost\Listener -Transport HTTPS -Address * -CertificateThumbPrint <THUMBPRINT> -Force
Get-ChildItem WSMan:\localhost\Listener
Connectivity checks
Test-NetConnection server01 -Port 5986
Test-WSMan -ComputerName server01 -UseSSL
Invoke-Command -ComputerName server01 -UseSSL -Credential $cred -ScriptBlock { hostname }
What the Pipeline Proved
- The security team’s concerns could be met without opening the server broadly.
- The architect team got a design they could defend and operate.
- A small file create/delete smoke test proved the account and remoting path were correct.
- Azure DevOps became the deployment orchestrator for on-prem servers.
Real Scenario: Deployment Approval Blocked Until Connectivity Was Proven
The release was blocked because nobody wanted to approve a deployment path that sounded risky. Once the WinRM HTTPS listener, certificate validation, and file smoke test were demonstrated, the proposal became much easier to approve.
| Symptom | What It Meant | What I Checked |
|---|---|---|
| Agent cannot reach server | Private routing or firewall issue | ExpressRoute path, NSG/firewall, port 5986 |
| WinRM works but command fails | Permission issue | Deployment account rights and target folder ACLs |
| HTTPS listener not trusted | Cert or listener mismatch | Listener thumbprint, certificate store, server name |
Delivery Lessons
- Secure automation is mostly about reducing what is exposed, not about removing automation.
- Proof matters: a tiny test pipeline often convinces people faster than a long design debate.
- Certificate-backed WinRM over HTTPS is much easier to defend than an unsecured remote execution model.
- Start with connectivity, then permissions, then the real deployment action.