Deploying an ASP.NET Site on IIS: Bindings, App Pools, and Go-Live Checks

Getting a web application live on a Windows server is mostly about three things lining up: the files, the application pool that runs them, and the binding that points a hostname at the site. When any one of the three is misaligned, visitors see a blank page, a 500 error, or the default IIS page.

This sequence takes a .NET application from a build output folder to a working site behind a real hostname. If you do not yet have the server itself, our Windows VPS comparison table shows the IIS-ready images and the memory tiers that comfortably run ASP.NET alongside SQL Server.

Install the role and the runtime

Start on a server that has the Web Server (IIS) role. On Server Core the fastest route is PowerShell:

Install-WindowsFeature Web-Server, Web-Mgmt-Tools, Web-Asp-Net45, Web-Windows-Auth
# For ASP.NET Core, install the Hosting Bundle separately:
# dotnet-hosting-x.y.z-win.exe /install /quiet /norestart

ASP.NET Core applications need the Hosting Bundle, not just the .NET runtime, because it registers the AspNetCoreModuleV2 with IIS. Reboot or run net stop was /y && net start w3svc afterwards so the module loads. A site that returns 500.19 immediately after deployment is almost always missing this step.

Create the application pool first

Give the site its own pool so a crash in one application cannot take down the others. Isolation is the whole point of the feature; running three unrelated sites in DefaultAppPool throws that away and makes recycling decisions affect all of them at once.

Import-Module WebAdministration
New-WebAppPool -Name 'myapp'
Set-ItemProperty IIS:\AppPools\myapp -Name managedRuntimeVersion -Value 'v4.0'
# Managed code 64-bit by default; set $false only for legacy 32-bit assemblies
Set-ItemProperty IIS:\AppPools\myapp -Name enable32BitAppOnWin64 -Value $false

For ASP.NET Core, leave the CLR version at No Managed Code — the app runs out of process or via in-process hosting, and IIS is only the reverse proxy in front of Kestrel. For .NET Framework apps, match the runtime version the project targeted, because loading a v2.0 assembly into a v4.0 pool fails at request time rather than at startup.

Publish the files and create the site

Deploy to a directory outside the IIS installation tree, for example C:\sites\myapp. Use Web Deploy from Visual Studio or a straightforward robocopy of the publish output. Then point a site at it:

New-Website -Name 'myapp' -PhysicalPath 'C:\sites\myapp' -ApplicationPool 'myapp' -HostHeader 'www.example.com' -Port 80
New-WebBinding -Name 'myapp' -Protocol https -Port 443 -HostHeader 'www.example.com'
Start-Website -Name 'myapp'

Grant the pool identity read access to the folder. The identity is IIS AppPool\myapp; without a read ACE the site returns 401.3 or 500.19 even though the pool is healthy. Grant write access only to the specific subdirectory the application needs, such as an uploads or logs folder.

Bindings, HTTPS, and the go-live checks

A binding is the pair of hostname and port that IIS matches against the incoming request. Two sites can share port 80 as long as their host headers differ. Attach the certificate to the HTTPS binding and, once traffic is confirmed working on both, add a rewrite rule that sends HTTP to HTTPS permanently rather than relying on users to type the right scheme.

  • Browse locally with a Hosts-file entry before touching public DNS.
  • Confirm the correct app pool is serving the site under IIS Manager → Sites → Basic Settings.
  • Check %SystemDrive%\inetpub\logs\LogFiles for the real status code rather than guessing.
  • Test a static file and a dynamic route — a 200 on the favicon does not prove the app pool loaded your assembly.
  • Verify the certificate chain with certutil -verify before announcing the URL.

Where sites break after go-live

The most common post-launch problems come from permissions changed by a deployment, a recycled pool whose warm-up step failed, and DNS still pointing at an old host during propagation. Keep deployment and configuration in source control, and record the pool identity and folder ACLs in the runbook so the next deploy does not rediscover them.

If you are planning several sites on one machine, see the full specs and pricing so you can size for concurrent application pools rather than a single application, and leave headroom for the storage and memory that each additional pool consumes.

Automating the repeat deployments

The first deployment is a one-off; the tenth is where a script pays for itself. Wrap the pool, site, and binding creation in an idempotent PowerShell script and store it beside the application’s source. Run it against a staging server before production so a mistyped host header or a missing ACL is caught somewhere harmless.

Keep a short smoke-test list that the script can execute after deployment: request the health endpoint, confirm the response status, and check that the pool identity can read the site root. That converts a deployment from a hopeful manual sequence into a repeatable step with a pass or fail result.

Leave a Comment