Capstone: Designing and Hardening a Production IIS Deployment

IIS In Depth

Chapter 10 · Capstone: Designing and Hardening a Production IIS Deployment

Web Servers Fundamentals' own capstone compared three servers across several scenarios. This one is different, by design — the same shape Nginx In Depth's and Apache In Depth's own capstones both used: a single, cohesive worked example, layering in each of this course's own eight prior chapters, one at a time, until one complete, production-shaped IIS deployment exists.

The Scenario

One IIS server hosts one site, www.example.com, serving a customer-facing ASP.NET Core storefront application at the site root, plus a small internal reporting API reachable at /api — used only by staff on the corporate network, never by customers. Both need to keep working reliably, and the internal API specifically must never be able to take the storefront down with it if something goes wrong.

Step 1 — Application Pool Design (Chapter 2)

appcmd add apppool /name:"StorefrontPool" /processModel.identityType:ApplicationPoolIdentity appcmd set apppool "StorefrontPool" /startMode:AlwaysRunning appcmd add apppool /name:"ReportingApiPool" /processModel.identityType:ApplicationPoolIdentity appcmd set apppool "ReportingApiPool" /recycling.periodicRestart.time:"1.05:00:00"

Two separate pools, each under its own dedicated ApplicationPoolIdentity — no shared built-in account, so a compromise or misconfiguration in one pool's identity carries no access to the other's resources. The storefront pool runs AlwaysRunning, since checkout traffic can't tolerate a cold-start latency spike. The reporting API pool keeps the default 29-hour scheduled recycle — it's genuinely low-traffic, so a brief overlapped recycle is a non-issue.

Step 2 — Locking the Configuration Model (Chapter 3)

<!-- applicationHost.config --> <location path="Storefront/api"> <system.webServer> <authentication> <windowsAuthentication enabled="true" /> </authentication> </system.webServer> </location>

The authentication section ships Deny-locked by default, exactly as Chapter 3 covered — so enabling Windows Authentication for /api specifically (needed in Step 7) has to happen via a <location> block in applicationHost.config, not from /api's own web.config. Every other section stays at its default overrideModeDefault, since neither application needs anything else locked down beyond this one deliberate exception.

Step 3 — Handler Mapping for ASP.NET Core (Chapter 4)

<system.webServer> <handlers> <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" /> </handlers> <aspNetCore processPath="dotnet" arguments=".\Storefront.dll" /> </system.webServer>

Both applications run in Integrated mode, the modern default from Chapter 4 — the storefront's own managed authentication and session modules see every request uniformly, static assets included, exactly the unified pipeline that would have been impossible under the old Classic mode's split native/ASP.NET handling.

Step 4 — Promoting /api to Its Own Application (Chapter 5)

appcmd add app /site.name:"Storefront" /path:/api /physicalPath:"C:\apps\reporting-api" appcmd set app "Storefront/api" /applicationPool:"ReportingApiPool"

/api is deliberately an application, not a plain virtual directory — precisely so it gets its own Application Pool and its own isolated .NET AppDomain, per Chapter 5. If the reporting API ever crash-loops into Rapid-Fail Protection, only ReportingApiPool goes offline — the storefront's own StorefrontPool is a completely separate worker process, untouched.

Step 5 — URL Rewrite for the Storefront (Chapter 6)

<rule name="Enforce HTTPS" stopProcessing="true"> <match url="(.*)" /> <conditions> <add input="{HTTPS}" pattern="^OFF$" /> </conditions> <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="Permanent" /> </rule> <rule name="Friendly Product URL"> <match url="^product/([0-9]+)$" /> <action type="Rewrite" url="product.aspx?id={R:1}" /> </rule>

Applied only to the storefront, not /api — a real customer-facing Redirect to enforce HTTPS, and an internal Rewrite for clean product URLs, exactly the Rewrite-vs-Redirect distinction Chapter 6 drew. No rule here ever points at a different backend server's URL — this stays entirely within-server rewriting, deliberately not reaching for ARR.

Step 6 — HTTPS Binding with SNI (Chapter 7)

netsh http add sslcert hostnameport=www.example.com:443 ` certhash=a909502dd82ae41433e6f83886b00d4277a32a7b ` certstorename=MY appid={4dc3e181-e14b-4a21-b022-59fc669b0914}

A single certificate, installed in the Windows Certificate Store and bound by thumbprint via SNI — /api shares this exact binding, since it's a path under the same site, not a separate site with its own certificate needs. A recurring calendar reminder is set for 30 days before expiry, specifically so the binding's thumbprint gets updated the moment the certificate is renewed, per Chapter 7's own renewal gotcha.

Step 7 — Authentication and Filtering for /api (Chapter 8)

setspn -A HTTP/www.example.com DOMAIN\svc-reporting <security> <ipSecurity allowUnlisted="false"> <add ipAddress="10.0.0.0" subnetMask="255.0.0.0" allowed="true" /> </ipSecurity> <requestFiltering> <fileExtensions> <add fileExtension=".config" allowed="false" /> </fileExtensions> </requestFiltering> </security>

/api uses Windows Authentication (enabled via Step 2's own <location> override) with an SPN explicitly registered for www.example.com against the reporting service account — done up front here specifically to avoid Chapter 8's own silent-NTLM-fallback trap. ipSecurity restricts /api to the corporate network's own subnet, deny-by-default. Request Filtering blocks .config requests site-wide, so neither application can ever accidentally expose its own web.config contents.

Step 8 — Performance, Logging & a Pre-Flight Tracing Check (Chapter 9)

<urlCompression doStaticCompression="true" doDynamicCompression="true" /> <caching> <profiles> <add extension=".css" policy="CacheForTimePeriod" kernelCachePolicy="CacheForTimePeriod" duration="01:00:00" /> </profiles> </caching> <tracing> <traceFailedRequests> <add path="/api/*"> <failureDefinitions statusCodes="401,403,500-599" /> </add> </traceFailedRequests> </tracing>

Static assets (.css, images) get kernel-mode caching — genuinely eligible, since they're anonymous and untouched by URL Rewrite. The storefront's own product pages stay out of kernel-mode caching deliberately, since Step 5's own Rewrite rule applies to them, exactly the eligibility gotcha Chapter 9 named. Failed Request Tracing is scoped tightly to /api alone and only to genuine failure status codes — enabled for the initial rollout window to verify the new Windows Authentication and IP restriction rules actually behave as intended, with a plan to disable it once that's confirmed rather than leaving it running indefinitely.

Capstone StepChapter It Draws From
Step 1 — Application Pool identities & recyclingChapter 2
Step 2 — Locking authentication via <location>Chapter 3
Step 3 — Handler mapping & Integrated modeChapter 4
Step 4 — /api as its own isolated applicationChapter 5
Step 5 — URL Rewrite (HTTPS enforcement, friendly URLs)Chapter 6
Step 6 — HTTPS binding with SNIChapter 7
Step 7 — Windows Authentication, SPN, IP restriction, filteringChapter 8
Step 8 — Compression, caching, scoped Failed Request TracingChapter 9
Three capstones, one full journey
Web Servers Fundamentals' own capstone answered "which server fits this job." This one answers "given IIS was already chosen, how do you actually configure it well" — the same relationship Nginx In Depth's and Apache In Depth's own capstones have to that same earlier course. Together, all four capstones across the Web Servers subject cover the full journey from choosing a web server to running a genuinely production-shaped configuration of whichever one was chosen.
This deployment is a solid foundation, not a complete go-live checklist
Every setting above reflects real material from this course, but a genuine production rollout still needs steps beyond any single configuration pass: a staged rollout rather than switching all traffic at once, real alerting layered on top of the logging configured here, and a documented certificate-renewal process so Chapter 7's own thumbprint gotcha never becomes an actual outage. Treat this chapter's deployment as a strong, correct starting point, not a substitute for an actual production rollout process.

Hands-On Exercises

Exercise 1

Explain why /api was deliberately made its own application in Step 4 rather than left as a plain subfolder under the storefront's own application, referencing both Chapter 2 and Chapter 5's own material.

📄 View solution
Exercise 2

A colleague suggests also adding the storefront's own /product.aspx pages to the kernel-mode caching profile used for .css files in Step 8, to speed things up further. Explain why this wouldn't actually work as intended, using this chapter's own Step 5 and Step 8 together.

📄 View solution
Exercise 3

Write a short chapter-attribution summary (2-3 sentences) explaining how this capstone's own shape differs from Web Servers Fundamentals' own capstone, and why that difference makes sense given what each course actually covers.

📄 View solution
Course Complete

IIS In Depth — 10 of 10 chapters complete. The Web Servers subject now has its comparative foundation, its Nginx deep dive, its Apache deep dive, and its IIS deep dive.

Chapter 10 Quick Reference

  • This capstone builds one cohesive production deployment for a public storefront and an isolated internal API under the same site, layering in Chapters 2 through 9 step by step — the same deliberate shape as Nginx In Depth's and Apache In Depth's own capstones, and a different one from Web Servers Fundamentals' own multi-scenario decision framework
  • Promoting /api to its own application (Chapter 5) with its own Application Pool (Chapter 2) is what keeps a reporting-API crash from ever taking the storefront down with it
  • Kernel-mode caching only applies to content untouched by URL Rewrite or non-anonymous authentication (Chapter 9) — deliberately excluded here for the storefront's own rewritten product pages
  • Registering the SPN and restricting Windows Authentication and IP access to /api up front avoids both of Chapter 8's own named gotchas — silent NTLM fallback and an open internal endpoint
  • A working deployment is a foundation, not a complete production rollout — staged traffic cutover, real alerting, and a documented certificate-renewal process still matter beyond this chapter's own scope