FTP was how .NET apps got deployed for years, and it is still how sites get broken: a half-uploaded file, a lost web.config, a permissions fight. Microsoft Web Deploy (MSDeploy) replaces that with a sync engine that understands IIS – it uploads only what changed, applies web.config transforms, and can take the app offline during the swap. This is the setup for publishing from Visual Studio to a Windows VPS running IIS.
Install Web Deploy on the server
Download the Web Deploy installer (the current 4.x MSI, available from the official Microsoft download page) and choose Complete install. The Typical option installs only the client tool; Complete adds the handler, which is what lets remote machines publish:
# Confirm the handler is registered after install
& "$env:ProgramFiles\IIS\Microsoft Web Deploy V4\msdeploy.exe" -version
Enable remote management and create a deploy user
In IIS Manager, open the server node and go to Management Service. Enable remote management on the default port 8172 with HTTPS, then create an IIS Manager user (under IIS Manager Users) – this avoids handing out a Windows account. Finally, on the target site, open IIS Manager Permissions, add that user, and grant Content Management delegation so it can read and write the site folder but nothing else.
netsh advfirewall firewall add rule name="Web Deploy 8172" dir=in action=allow `
protocol=TCP localport=8172
The management service endpoint is https://yourserver:8172/msdeploy.axd. Test it from your workstation before involving Visual Studio:
msdeploy.exe -verb:dump `
-dest:computerName="https://yourserver:8172/msdeploy.axd",userName="webdeploy",password="***",authType="Basic",`
-allowUntrusted
Publish profile in Visual Studio
- Right-click the project, Publish → New profile → Web Deploy.
- Server:
https://yourserver:8172/msdeploy.axd; Site name: the IIS site name (e.g.contoso); user/password: the IIS Manager user. - Enable Remove additional files at destination for clean builds (it deletes orphaned files – disable it if you upload user content to the site folder).
- Add a
Web.Release.configwithxdt:Transformentries to swap connection strings and app settings per environment.
Before the first real deploy, use Visual Studio’s Preview step in the publish dialog: it lists every file that will be added, updated or deleted on the server without touching anything. A five-second glance at the preview catches the classic mistakes – a missing appsettings.json, a stray bin folder, or a transform that dropped a connection string.
<!-- Web.Release.config -->
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<connectionStrings>
<add name="Default" connectionString="Server=.;Database=app;User ID=app;Password=***"
xdt:Transform="SetAttributes" xdt:Locator="Match(name)" />
</connectionStrings>
</configuration>
The 5 errors everyone hits (and the fixes)
| Error | Cause | Fix |
|---|---|---|
| ERROR_USER_UNAUTHORIZED | IIS Manager user lacks delegation on the site | Grant Content Management in IIS Manager Permissions on the site node |
| ERROR_DESTINATION_NOT_REACHABLE | Port 8172 blocked or Management Service stopped | Open the firewall rule; verify the service is running and set to Automatic |
| ERROR_INSUFFICIENT_ACCESS_TO_SITE_FOLDER | ACLs on the web root block the deploy identity | Give the IIS Manager user or app pool identity Modify on the site folder (icacls) |
| ERROR_FILE_IN_USE / locked files | w3wp.exe holds the old DLLs | Drop an app_offline.htm into the site root before sync (Web Deploy removes it after) |
| 500 on msdeploy.axd | Handler not installed | Re-run the Web Deploy installer and choose Complete; restart Management Service |
The locked-file problem deserves extra attention: Web Deploy can take the app offline automatically, but the reliable pattern for a busy site is to place app_offline.htm in the root, publish, then delete it. IIS sees the file, unloads the app pool, and no process holds the files.
Using the same pipeline for CI/CD
The Visual Studio profile is just a wrapper around msdeploy.exe. Your build agent can do the identical sync from the command line:
msdeploy.exe -verb:sync -source:package="artifacts\site.zip" `
-dest:auto,computerName="https://yourserver:8172/msdeploy.axd",userName="webdeploy",password="***",authType="Basic" `
-setParam:name="IIS Web Application Name",value="contoso" `
-allowUntrusted
Store the password in the build agent secret store, never in the repo. A dedicated IIS Manager user per site keeps one pipeline from deploying to another site, and the per-site delegation model means a compromised build agent can touch only its own folder.
Locking down the deploy endpoint
The msdeploy.axd endpoint accepts authentication from anywhere that can reach port 8172, so treat it like an admin interface. Restrict the firewall rule to your build agents or office IP range instead of all addresses, prefer IIS Manager users over local Administrator credentials, and rotate the password whenever a developer leaves the project:
netsh advfirewall firewall set rule name="Web Deploy 8172" new remoteip=203.0.113.10,198.51.100.0/24
For unattended syncs, add -enableRule:AppOffline to the msdeploy command: it drops app_offline.htm automatically before syncing and removes it afterwards, which eliminates the locked-file race without manual steps.
Before you wire up the pipeline
Web Deploy works best when the server is cleanly provisioned: dedicated app pool per site, known ACLs on the web root, and a firewall that only exposes 8172 to your build agents or office IPs. If you are still choosing where to host, the Windows VPS comparison table on our site compares providers by admin access and networking, and the feature checklist on our site lists the IIS-related capabilities worth confirming before you sign up.



