Setting Up IIS Isn't as Bad as Microsoft Documentation Makes It Look
I spent about three days troubleshooting a production IIS 10 deployment last year, and the biggest headache wasn't actually the server configuration itself. It was the way application pools inherit settings from the parent server, combined with how the URL Rewrite module processes rules in a non-obvious order. You install IIS through Server Manager, enable the features you need, and then you're basically on your own for anything beyond serving static files. The first thing most people mess up is the application pool CLR version. If you're running ASP.NET Core, setting the CLR version to "No Managed Code" and choosing the right runtime mode for your app is critical. I had a .NET 6 application fail to start with a 503 error because the process model was set to In-Process by default, and a third-party native DLL in the project wasn't compatible with that hosting model. Switching to Out-Process in the web.config fixed it, but finding that error message required digging into the Windows Event Viewer under the Application log, not any IIS-specific area.
The Ultimate Guide To Configuring Iis Web Server Unlocking The Power Of Microsofts Web Server
Here is what you actually do to get a functioning IIS setup for a modern application. Install the necessary roles and features. Open Server Manager and go to Add Roles and Features. Select Web Server (IIS) and then choose the role services your application needs. For most ASP.NET applications, you want Application Development features including ASP.NET 4.8 or .NET Extensibility depending on your framework. Also enable Request Filtering and Static Content if you're serving any files directly. I usually skip Dynamic Content Compression unless bandwidth is a real concern, because the performance gain is negligible for most internal applications. Configure your application pool. This is where most configuration problems start. Open IIS Manager, click Application Pools on the left, and add a new one. Give it a distinct name. Set the .NET CLR Version to "No Managed Code" for .NET Core or .NET 5+ apps. For legacy ASP.NET, pick the appropriate version. The pipeline mode matters too. Use Integrated mode unless you have a specific reason not to, because Classic mode breaks a lot of modern middleware and handler behaviors.
Set the identity. The default ApplicationPoolIdentity is generally fine for file access and database connections through Windows Authentication, but if your application needs to write to the registry or access network shares, you will need to grant permissions to the virtual account NT AUTHORITY\IUSR or a custom service account. I learned this the hard way when a deployment script failed silently because the application pool identity had no write access to a shared network path used for temporary file processing. Assign the application to the pool. Right-click your site in IIS Manager, go to Basic Settings, and change the Application Pool dropdown to match the pool you just created. Then set the physical path to your application directory. If you are deploying from source control, make sure the build artifacts are actually present before you try to browse the site. IIS will not compile your application for you unless you have set up a proper pre-deployment step. Configure bindings and SSL. Go to Site Bindings and add an HTTPS binding with your certificate. If you are using a self-signed certificate for internal testing, you can generate one with the MakeCert tool or PowerShell's New-SelfSignedCertificate cmdlet, but this will cause browser warnings that can break AJAX calls if your application relies on HTTPS-only cookies or secure storage. For production, use a proper certificate from a trusted CA. The SNI setting matters if you are hosting multiple sites on the same IP and port combination.
Get the Full Details

Set up URL Rewrite rules. Install the URL Rewrite module from Microsoft if it is not already present. This is essential for clean URLs, SEO, and redirecting HTTP to HTTPS. A typical rule redirects all HTTP traffic to HTTPS: match the server variable HTTP_HOST, set the action type to Redirect, and use the pattern https://{HTTP_HOST}{REQUEST_URI}. The stop processing flag is important here because without it, IIS may continue evaluating subsequent rules even after the redirect has been issued. I once spent two hours debugging a redirect loop caused by a missing stop processing flag on an earlier rule in the sequence. Configure authentication methods. In IIS Manager, select your site and open Authentication. Disable Anonymous Authentication if you want to enforce Windows Authentication or another method. For Windows Authentication, make sure the provider is set correctly and that your application code handles the user principal properly. If you are using OAuth or JWT tokens instead, configure your application to handle authentication at the code level and set Anonymous Authentication to enabled so the application middleware can process the tokens. Enable logging and monitoring. IIS logging is configured at the site level through the Logging feature. Set the format to W3C and choose the fields you actually need. Common fields include date, time, c-ip, cs-method, cs-uri-stem, sc-status, and time-taken. Avoid logging everything because the log files grow quickly and make troubleshooting harder. I usually set the log file size limit to 100 MB with daily rotation. For deeper troubleshooting, Failed Request Tracing Rules can show you exactly what happened at each stage of the request pipeline, though this does add performance overhead.
Configure compression. In the Compression feature, enable both Static and Dynamic compression. Static compression caches compressed versions of files so they are only compressed once. Dynamic compression compresses on the fly, which uses more CPU but saves bandwidth. For text-based content like HTML, CSS, and JavaScript, compression typically reduces transfer size by 60 to 80 percent. This is a meaningful improvement for applications served over slower connections. Set up custom error pages. Default IIS error pages leak information about your server version and configuration. Configure custom error pages for common status codes like 404, 403, and 500. Map these to pages within your application rather than external URLs so they render consistently. In the Error Pages feature, set the path for each status code you want to override. Now, the part that usually causes problems. IIS and ASP.NET Core applications have different requirements. If you are running .NET Core or later under IIS, you need the AspNetCoreModuleV2 installed. This is not included by default when you install IIS through Server Manager. Without it, your application will return a 502.5 process failure. The fix is to download the .NET Core Hosting Bundle from Microsoft, which includes the runtime, the ASP.NET Core module, and the Windows Server hosting bundle. Run the installer on the server and restart IIS afterward.
Another issue that comes up frequently is handler mapping conflicts. If you have both ASP.NET and a custom HTTP handler registered, IIS evaluates them in a specific order and the first match wins. I encountered a situation where a third-party library registered a handler for all extensions, which intercepted requests intended for a custom routing module in my application. The solution was to adjust the handler mapping precedence in the web.config by specifying the verb and path restrictions more narrowly for the built-in handlers. Request filtering is another area that causes unexpected failures. By default, IIS has a maxUrl length of 260 characters and a maxQueryStringLength of 2048 characters. If your application uses long query strings or URLs, you will get 404.13 errors. These limits are configured in the requestFiltering section of web.config or in the Request Filtering feature of IIS Manager. I had a case where a client sending large base64-encoded parameters in the URL was getting blocked, and the logs showed nothing useful. Increasing the maxQueryStringLength to 4096 resolved it, though you should validate the input size at the application level rather than just raising the limit blindly. There are situations where IIS simply is not the right choice. If you are running a microservices architecture with dozens of independent deployments, the overhead of managing application pools, certificates, and bindings for each service becomes significant. Docker containers or a reverse proxy like nginx or Traefik is more practical. IIS excels when you are hosting .NET applications within a Windows environment, especially when you need tight integration with Active Directory for authentication or when you rely on Windows-specific features like Windows Authentication or IIS Express for local development.

Also, IIS does not handle concurrent connections as efficiently as some purpose-built servers. If you are serving high volumes of static content or running WebSocket-heavy applications, you may see better throughput with a dedicated static file server in front of IIS. A common pattern is to put nginx or Caddy in front of IIS, letting the front-end server handle static files and TLS termination while IIS processes the dynamic application logic. For maintenance, I usually check the Application Pool status daily through a simple PowerShell script that reports idle pools, crashed pools, and memory usage. The command Get-WebAppPoolState | Where-Object {$_.State -ne "Started"} gives you a quick overview. I also monitor the W3SVC event log for recurring errors. Most configuration problems leave a trace there within minutes of occurrence. If you need to migrate an existing site to IIS, export the configuration from the source server first. The appcmd utility can do this. Run appcmd list site /config:xml to get the full configuration, then import it on the target server. This preserves bindings, application pools, and handler mappings. I have seen people manually recreate configurations from scratch and end up with subtle differences that caused production errors days later.
One more thing about deployment. Use MSBuild or the dotnet publish command to create a deployable package, then copy it to the server's application directory. Do not deploy directly from Visual Studio's publish feature to a production server. The implicit file locking and permission changes that Visual Studio makes during publishing can corrupt the application directory or leave orphaned files. A proper CI/CD pipeline that builds the package and copies it with controlled permissions is much more reliable. The whole configuration process for a standard ASP.NET Core application under IIS usually takes about 30 to 45 minutes if you know what you are doing. The first time through, expect to spend a few hours, mostly because you will encounter one or two configuration issues that require investigation. The error messages in IIS are not always descriptive, so having the Windows Event Viewer and the Failed Request Tracing tools ready is important. I tend to recommend starting with a minimal configuration and adding features incrementally rather than enabling everything at once. This makes it easier to identify which setting caused a problem when something breaks. IIS has a reputation for being complicated, but most of the complexity comes from the sheer number of configurable options, not from any inherent difficulty in the core workflow.