Install the GraphWarden proxy as a Windows Service

Install the GraphWarden proxy as a Windows Service on Windows Server 2019 or 2022. The proxy runs a self-hosted Kestrel listener and is managed via sc.exe or the Services MMC snap-in. The proxy does NOT run under IIS. This page covers service-account setup, Key Vault permissions, and firewall prerequisites.

Windows Server 2019 and 2022 (Server 2016 out of support)

Prerequisites

- Windows Server 2019 or 2022 (Server 2016 is out of support) - Administrative rights on the target server - A service account with Key Vault `get` permission on the proxy's secrets - .NET 8.0 runtime installed (the proxy is ASP.NET Core self-hosted) - Outbound network access to Microsoft identity, Graph, Key Vault, and the GraphWarden control plane (see `./networking-firewall` for the full list)
The GraphWarden proxy does NOT run under IIS. It is an ASP.NET Core self-hosted application using Kestrel. Do not create an IIS application pool for it. Do not place it behind `w3wp.exe`. The Windows Service wrapper described below is the supported hosting model.

What the proxy runs as

The GraphWarden proxy is an ASP.NET Core application that hosts its own Kestrel HTTP listener. A Windows Service wrapper (builder.Host.UseWindowsService() in Program.cs) registers the process with the Service Control Manager so it starts at boot, restarts on crash per your recovery configuration, and logs to the Windows Event Log.

The proxy binds to a configurable port on startup; the health endpoint (GET /health) defaults to http://localhost:5000/health when no Kestrel binding is set. Production deployments override the listener via appsettings.json or environment variables to bind to a specific hostname and port.

Step 1: Prepare the service account

Create a dedicated account for the proxy rather than running it as LocalSystem. The account needs read access to the proxy's Key Vault secrets and the right to log on as a service.

  1. Create a domain or local account (for example DOMAIN\graphwarden-svc). Set a strong password and store it in your secret management system — the password is supplied to sc.exe in Step 4.
  2. Grant Log on as a service to the account. Open Local Security Policy > Local Policies > User Rights Assignment > Log on as a service, then add the account.
  3. Grant the account the Key Vault get secrets permission. In the Azure portal, open the target Key Vault, navigate to Access policies (or Access control (IAM) for RBAC-mode vaults), and grant the account Secret — Get. The proxy reads the Graph client secret, the ProxyJwt:SigningKey, and the RuleSync:HmacSecret (if sync is enabled) from Key Vault at startup.

If you use Managed Identity instead of a traditional service account, assign the VM's system-assigned identity the same Secret — Get permission. Managed Identity is the production-recommended pattern per the proxy architecture recon.

Step 2: Install .NET 8.0 runtime

The proxy targets .NET 8.0 and requires the ASP.NET Core runtime (hosting bundle) on the server. Install it from an elevated PowerShell session:

# Install .NET 8.0 runtime (hosting bundle includes runtime + ASP.NET Core runtime).
winget install Microsoft.DotNet.HostingBundle.8

Verify with dotnet --list-runtimes; you should see Microsoft.AspNetCore.App 8.0.x in the output. If winget is not available on the server, download the hosting bundle from the Microsoft .NET site and install it interactively.

Step 3: Deploy the proxy binaries

Unzip the published GraphWarden Proxy artifacts to C:\Program Files\GraphWarden\Proxy\ (or your preferred path). Copy your appsettings.json into the same directory. Ensure the service account from Step 1 has Read & Execute on the directory.

The directory layout after deployment looks like:

C:\Program Files\GraphWarden\Proxy\
  GraphProxy.Proxy.exe
  appsettings.json
  appsettings.Production.json   (optional overrides)
  (other runtime DLLs)

Keep the appsettings.json free of real secrets — the proxy pulls secrets from Key Vault at startup based on KeyVault:VaultUri and KeyVault:UseManagedIdentity.

Step 4: Create the Windows Service

Register the service with sc.exe. Run the commands below from an elevated command prompt (not PowerShell — sc.exe argument parsing differs in PowerShell).

# Replace the following paths and account name with your values.
sc.exe create GraphWardenProxy binPath= "C:\Program Files\GraphWarden\Proxy\GraphProxy.Proxy.exe" start= auto obj= "DOMAIN\graphwarden-svc" password= "<stored in secure secret store>"
sc.exe description GraphWardenProxy "GraphWarden proxy for Microsoft Graph API governance."
sc.exe failure GraphWardenProxy reset= 60 actions= restart/5000/restart/5000/restart/5000

The failure command configures automatic restart: three consecutive restarts at 5-second intervals, with the failure counter reset after 60 seconds of uptime. The space after each = in sc.exe is intentional — without it the argument parser rejects the command.

If you prefer Managed Identity over a domain account, omit obj= and password=; the service runs as LocalSystem and Managed Identity handles Key Vault authentication through the VM's metadata endpoint.

Step 5: Configure environment

The proxy reads configuration from appsettings.json, appsettings.Production.json, and environment variables (with environment variables taking precedence). The full inventory of settings — including KeyVault:VaultUri, KeyVault:UseManagedIdentity, GraphClient:TenantId, ProxyJwt:SigningKey, and the RuleSync:* / Audit:* families — is documented on the environment variables reference.

For a minimum on-premise install, set KeyVault:VaultUri, KeyVault:UseManagedIdentity, GraphClient:TenantId, and GraphClient:ClientId in appsettings.json, and store GraphClient:ClientSecret and ProxyJwt:SigningKey in Key Vault under the names the proxy expects.

Step 6: Start the service

Start the service and confirm it reports RUNNING:

sc.exe start GraphWardenProxy
sc.exe query GraphWardenProxy

The query output should show STATE : 4 RUNNING. The Services MMC snap-in (services.msc) is an equivalent UI — find GraphWardenProxy in the list and confirm the status.

Verifying the installation

Once the service reports RUNNING, verify the proxy is healthy from the server itself:

# From the proxy host; adjust the port if you overrode the Kestrel binding.
curl http://localhost:5000/health

A healthy proxy returns a JSON status object. The /metrics endpoint at the same host and port returns Prometheus-compatible counters (request rates, latency percentiles, and audit-buffer state). If either endpoint does not respond, check the Windows Event Log under Applications and Services Logs — the proxy emits Serilog JSON events to the Application log with GraphWardenProxy as the source.

Upgrading the proxy

Upgrade is a stop-replace-start cycle. The Windows Service wrapper waits for in-flight requests to complete before the process exits, but long-running Graph operations may be cut off if your stop-timeout is aggressive.

  1. Stop the service: sc.exe stop GraphWardenProxy.
  2. Wait for sc.exe query GraphWardenProxy to report STOPPED.
  3. Replace the binaries in C:\Program Files\GraphWarden\Proxy\ with the new version. Keep appsettings.json in place unless the release notes flag a configuration change.
  4. Start the service: sc.exe start GraphWardenProxy.
  5. Verify /health returns the new version string.

Roll back by reversing Step 3 and restarting the service. Because configuration is external (Key Vault + appsettings.json), downgrades and upgrades are symmetric.

Troubleshooting

Service fails to start (Error 1053)

Error 1053 (The service did not respond to the start or control request in a timely fashion) almost always means the .NET 8.0 runtime is missing or the wrong architecture. Run dotnet --list-runtimes as the service account; if Microsoft.AspNetCore.App 8.0.x is not listed, repeat Step 2. On a 64-bit Windows Server the x64 hosting bundle is required — the x86 bundle will not host a 64-bit proxy binary.

Service starts but proxy returns 503

The service process is running but the proxy cannot reach Key Vault. Check the Windows Event Log for Azure.Identity.CredentialUnavailableException or KeyVault: access denied. The most common cause is the service account lacking Secret — Get on the vault; repeat Step 1.3. For Managed Identity, confirm the VM has a system-assigned identity and that identity has the same permission.

503 on token endpoint only

The proxy starts, Key Vault access works for the ProxyJwt:SigningKey, but /proxy/token returns 503. The Graph client secret stored in Key Vault is stale — the Azure AD app's secret has been rotated but Key Vault still holds the old version. Retrieve the current secret from the Azure AD app registration, write it to Key Vault under the same secret name, and restart the service. The proxy re-reads secrets at startup.

Port already in use

Service start fails with Kestrel: Failed to bind to address. Another process is bound to the listener port. Run netstat -ano | findstr :5000 (or whichever port you configured) to find the PID, then tasklist /fi "pid eq <PID>" to identify the process. Either stop the conflicting service or override the Kestrel binding in appsettings.json to a free port.

IIS returns 404 for proxy URL

You accidentally created an IIS site that binds the same hostname or port as the proxy. The proxy does NOT run under IIS — it is a self-hosted Kestrel service. Open IIS Manager and remove any site or application pool that references GraphWardenProxy or the proxy's binaries. After removing the IIS configuration, restart the GraphWardenProxy Windows Service.

Last reviewed:

Last reviewed: .