
Wenn Cloud-Hosting keine Option ist, ermöglicht ein Self-hosted Azure DevOps Agent vollautomatische CI/CD-Pipelines im eigenen Rechenzentrum. Der Agent verbindet sich aktiv von innen nach außen – ohne eingehenden Traffic, offene Ports oder aufwendige Firewall-Regeln. Der Beitrag zeigt die komplette Einrichtung auf einem lokalen Windows Server: von der TLS-Konfiguration über PAT-Scopes, Agent Pools und die Dienst-Account-Anpassung für IIS bis zur fertigen azure-pipelines.yml für eine ASP.NET Core Blazor Anwendung. Inklusive der typischen Stolpersteine aus der Praxis – TLS 1.2, fehlende Manage-Berechtigungen und Pool-Konfiguration – und ihrer Lösungen. Ergebnis: ein reproduzierbares On-Premise-Deployment vom Pull Request bis zum laufenden IIS.
In vielen Unternehmen läuft die Entwicklung über Azure DevOps – CI/CD-Pipelines, Repositories, Work Items. Doch nicht immer landet die App am Ende in der Cloud. Lokale Server, On-Premise-Infrastruktur und Sicherheitsanforderungen machen ein klassisches Cloud-Deployment oft unmöglich.
Dieser Artikel zeigt, wie man einen Self-hosted Azure DevOps Agent auf einem lokalen Windows Server einrichtet und eine vollautomatische CI/CD-Pipeline für eine ASP.NET Core Blazor Anwendung aufbaut – vom Pull Request bis zum laufenden IIS-Deployment.
Dabei wird nicht nur der Idealfall behandelt, sondern auch die typischen Stolpersteine aus der Praxis: TLS-Probleme, fehlende Berechtigungen, falsche PAT-Scopes und die richtige Konfiguration des Dienst-Accounts.
Der entscheidende Unterschied zu einem Microsoft-hosted Agent: Der Self-hosted Agent verbindet sich aktiv von innen nach außen zu Azure DevOps – nicht umgekehrt. Eingehender Traffic, geöffnete Ports oder eine komplizierte Firewall-Konfiguration sind nicht notwendig.

Bevor der Agent installiert wird, muss sichergestellt werden, dass der Server Azure DevOps überhaupt erreichen kann.
Verbindungstest in PowerShell:
Test-NetConnection -ComputerName dev.azure.com -Port 443

Häufiges Problem – TLS-Version:
Ältere Windows Server verwenden standardmäßig TLS 1.0, Azure DevOps erfordert jedoch mindestens TLS 1.2. Symptom: Test-NetConnection funktioniert, aber Invoke-WebRequest schlägt mit „Die zugrunde liegende Verbindung wurde geschlossen“ fehl.
Test:
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12Invoke-WebRequest -Uri "https://dev.azure.com" -UseBasicParsing
Funktioniert es damit, muss TLS 1.2 dauerhaft als Standard gesetzt werden (Admin-PowerShell erforderlich):
Set-ItemProperty -Path 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\.NETFramework\v4.0.30319' -Name 'SystemDefaultTlsVersions' -Value 1 -Type DWordSet-ItemProperty -Path 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\.NETFramework\v4.0.30319' -Name 'SchUseStrongCrypto' -Value 1 -Type DWordSet-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\.NETFramework\v4.0.30319' -Name 'SystemDefaultTlsVersions' -Value 1 -Type DWordSet-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\.NETFramework\v4.0.30319' -Name 'SchUseStrongCrypto' -Value 1 -Type DWordHinweis: Keine Ausgabe = Erfolg.
Zur Verifikation in PowerShell:
Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\.NETFramework\v4.0.30319' | Select-Object SystemDefaultTlsVersions, SchUseStrongCryptoBeide Werte sollten „1“ zeigen.
Der Agent authentifiziert sich über einen PAT gegen Azure DevOps. Wichtig: Der PAT braucht explizit den Agent-Pools-Scope – andere Scopes reichen nicht aus.
Warum reicht „Full Access auf Projektebene“ nicht?
Agent Pools sind eine organisationsweite Ressource, keine projektspezifische. Ein PAT mit Full Access auf Projektebene hat keine Rechte auf Organisation-Level-Ressourcen.
PAT erstellen:
Scope-Erklärung:
ScopeBerechtigungReadPool und Agents sehen, Status abfragenManageAgents registrieren, löschen, konfigurieren
Für die Agent-Registrierung wird Manage benötigt, da config.cmd intern einen POST-Request gegen die Azure DevOps API sendet – ohne Manage kommt ein „403 Forbidden“.
Agent Pools werden auf Organisation-Ebene verwaltet – nicht auf Projektebene.
Navigation:
https://dev.azure.com/EURE-ORGANISATION/_settings/agentpools
Wichtig: Der ausführende Account benötigt Administrator-Rechte am Pool, um Agents registrieren zu können. Dies muss vom Organisation Admin einmal eingerichtet werden: Organisation Settings → Agent Pools → [Pool] → Security → Account als Administrator eintragen.
Den Agent direkt aus Azure DevOps herunterladen, das garantiert die aktuelle Version:_settings/agentpools → Pool auswählen → New Agent → Windows → DownloadVersionsnummer notieren, wird später gebraucht.
Die Zip-Datei nach C:\azagent entpacken.
Konfiguration in Admin-PowerShell:
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12cd C:\azagent\vsts-agent-win-x64-X.XXX.X.\config.cmd --url https://dev.azure.com/ORGANISATION `--auth pat `--token DEIN_PAT_TOKEN `--pool "LocalServer" `--agent local-server-01 `--runAsService `--work C:\azagent\_workDas Backtick (`) am Ende der Zeile ist für PowerShell wichtig, da es signalisiert, dass die Anweisung in der nächsten Zeile weitergeht.
Interaktive Fragen während der Konfiguration:
Hinweis zu weiteren möglichen Fragen: In Umgebungen mit Proxy wird nach Proxy-URL, Benutzername und Passwort gefragt. Bei self-signed Zertifikaten nach der CA-Zertifikatdatei.
Agent-Status prüfen:
Nach erfolgreicher Konfiguration in Azure DevOps prüfen:
Organisation Settings → Agent Pool → [Pool] → Agent
Der Agent sollte mit einem grünen Punkt als Online erscheinen.
Standardmäßig läuft der Agent-Dienst als NETWORK SERVICE – dieser Account hat keine Rechte, IIS zu steuern. Für automatisches Stoppen und Starten von IIS-Websites muss der Dienst-Account auf LocalSystem geändert werden.
In PowerShell:
Dienstname ermitteln:
Get-Service | Where-Object { $_.Name -like "*vstsagent*" }Account ändern:
sc.exe config "DIENSTNAME" obj= "LocalSystem"Dienst neustarten:
Restart-Service "DIENSTNAME"
Niemals direkt auf der Produktionsumgebung testen. Eine separate IIS-Website auf einem anderen Port ist schnell eingerichtet:
PowerShell:
Ordner anlegen:
mkdir C:\inetpub\wwwroot\app_testNeue Website erstellen:
New-Website -Name "MeineApp Test" -PhysicalPath "C:\inetpub\wwwroot\apptest" -Port 8080 -Force(Hinweis: Ordnerpfad und -PhysicalPath sollten identisch sein – siehe Code-Hinweise oben.)
Die Pipeline-Konfiguration gehört als azure-pipelines.yml in den Root des Repositories.
trigger:
branches:
include:
- main
# Self-hosted Agent Pool angeben
pool:
name: LocalServer
variables:
buildConfiguration: Release
websiteName: 'MeineApp' # Exakter IIS Website-Name
deployPath: 'C:\inetpub\wwwroot\meineapp'
stages:
# CI – Build & Publish
- stage: CI
jobs:
- job: Build
steps:
- task: DotNetCoreCLI@2
displayName: Build
inputs:
command: build
projects: '**/*.csproj'
arguments: '--configuration $(buildConfiguration)'
- task: DotNetCoreCLI@2
displayName: Publish
inputs:
command: publish
publishWebProjects: true
arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)'
zipAfterPublish: false
- task: PublishPipelineArtifact@1
displayName: Artifact hochladen
inputs:
targetPath: '$(Build.ArtifactStagingDirectory)'
artifact: 'webapp'
# CD – Deploy auf lokalen IIS
- stage: CD
dependsOn: CI
condition: succeeded()
jobs:
- deployment: DeployIIS
environment: 'local-server'
strategy:
runOnce:
deploy:
steps:
- task: DownloadPipelineArtifact@2
inputs:
artifact: 'webapp'
path: '$(Pipeline.Workspace)/webapp'
- task: PowerShell@2
displayName: IIS Website stoppen
inputs:
targetType: inline
script: |
Import-Module WebAdministration
Stop-Website -Name '$(websiteName)'
- task: CopyFiles@2
displayName: Dateien deployen
inputs:
SourceFolder: '$(Pipeline.Workspace)/webapp'
Contents: '**'
TargetFolder: $(deployPath)
OverWrite: true
- task: PowerShell@2
displayName: IIS Website starten
inputs:
targetType: inline
script: |
Import-Module WebAdministration
Start-Website -Name '$(websiteName)'Warum PowerShell statt IIS-Tasks?
Der offizielle IISWebAppManagementOnMachineGroup Task erfordert über 16 Pflichtfelder. Der PowerShell-Ansatz mit WebAdministration macht dasselbe mit deutlich weniger Konfigurationsaufwand.
In Azure DevOps:
Beim ersten Run fragt Azure DevOps, ob die Pipeline den Pool verwenden darf → Permit klicken.
Nach Abschluss dieser Einrichtung läuft der komplette Deployment-Prozess vollautomatisch:
Kein manuelles Deployment mehr. Keine vergessenen Schritte. Keine Downtime durch menschliche Fehler.
Ein Self-hosted Azure DevOps Agent ist eine geeignete Lösung, wenn Cloud-Hosting keine Option ist. Die Einrichtung erfordert etwas Geduld – besonders beim Thema Berechtigungen und TLS – aber das Ergebnis ist eine vollautomatische, robuste Deployment-Pipeline, die on-premise genauso zuverlässig funktioniert wie in der Cloud.
Der entscheidende Vorteil: Der Agent verbindet sich aktiv nach außen. Keine eingehenden Verbindungen, keine offenen Ports, keine Kompromisse bei der Netzwerksicherheit.

Unser Geschäftsführer Tibor Csizmadia und unser Kundenbetreuer Jens Walter stehen Ihnen persönlich zur Verfügung. Profitieren Sie von unserer langjährigen Erfahrung und erhalten Sie eine kompetente Beratung in einem unverbindlichen Austausch.