skip to content

The ASP.NET Core Module in IIS supports two hosting models, selected by the hostingModel attribute in web.config as inprocess or outofprocess. How do the two differ at runtime, and when would you choose each?

level: middleimportance: nice to knowfreq 38%

answer

  1. one process or two
  2. Kestrel behind a loopback proxy hop
  3. the runtime loaded inside w3wp
  4. in-process inherits the pool's everything
  5. 500.30 versus 502.5

basics

~20 s

Out-of-process, the IIS module launches your app as a separate process running Kestrel on a local port and reverse-proxies each request to it. In-process, it loads the runtime inside w3wp.exe and the app serves through IIS directly, which removes the extra hop and is faster.

solid answer

~50 s

With `hostingModel="outofprocess"`, the ASP.NET Core Module starts your application as its own process listening on a dynamic loopback port with Kestrel, and `w3wp.exe` proxies every request to it over HTTP — two processes, an extra hop per request, but an application that behaves exactly as it would anywhere else. With `hostingModel="inprocess"`, the module hosts the .NET runtime inside the worker process and the app uses the IIS-backed server implementation rather than Kestrel, so requests never leave `w3wp.exe`; throughput is markedly higher. The cost is coupling: the app inherits the pool's identity, bitness and recycling, its process is `w3wp.exe`, and a pool can host only one in-process application. Startup failures also surface differently — 500.30 in-process, 502.5 out-of-process. In-process has been the default since ASP.NET Core 2.2; pick out-of-process when you need the app in its own process.

code

xml · 11 lines
xml
<configuration>
  <system.webServer>
    <handlers>
      <add name="aspNetCore" path="*" verb="*"
           modules="AspNetCoreModuleV2" resourceType="Unspecified" />
    </handlers>
    <aspNetCore processPath="dotnet" arguments=".\Contoso.dll"
                hostingModel="outofprocess"
                stdoutLogEnabled="true" stdoutLogFile=".\logs\stdout" />
  </system.webServer>
</configuration>

go deeper

for a junior

Know that IIS does not run ASP.NET Core code by itself: a module either starts your app as a separate process and proxies to it, or loads it inside the IIS worker process.

for a middle

Explain the runtime difference — Kestrel on a loopback port versus the IIS-backed server inside w3wp.exe — and what in-process inherits from the application pool.

for a senior

Show judgment about when the extra process earns its cost: differing identity or bitness, several apps in one pool, or wanting the runtime process to match every other environment for diagnostics.

for a principal

Own it as a hosting-strategy question — whether IIS remains an application host at all or becomes only a TLS-terminating reverse proxy in front of standard Kestrel services, and what that means for portability.

## What the module is The ASP.NET Core Module, registered as `AspNetCoreModuleV2`, is a native IIS module that stands between the IIS pipeline and a .NET application. The published `web.config` wires it up as a catch-all handler and configures it in one element: ```xml <system.webServer> <handlers> <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" /> </handlers> <aspNetCore processPath="dotnet" arguments=".\Contoso.dll" hostingModel="inprocess" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" /> </system.webServer> ``` Because the runtime is loaded by this module rather than by IIS's ASP.NET integration, the application pool is configured with the .NET CLR version set to **No Managed Code** — a setting that surprises people who expect a .NET application to need a CLR version selected. ## Out-of-process: proxy to Kestrel The module launches the process named by `processPath` and `arguments`, passing it an environment variable that tells it which loopback port to listen on. The application starts Kestrel exactly as it would when run from a console. IIS then acts as a reverse proxy: every inbound request is forwarded over HTTP to `http://127.0.0.1:<port>`, and the response comes back the same way. What you get is separation. The application is an ordinary process with its own identity if you want one, its own bitness, its own lifetime, and behaviour identical to running it behind any other front end. What you pay is a second hop per request — serialising the request out and the response back through a loopback socket — which measurably reduces throughput, and a second process to reason about. ## In-process: the runtime inside w3wp Here the module hosts the .NET runtime directly inside the IIS worker process, and the application's server implementation is the IIS-backed one rather than Kestrel. Requests are handed across the native/managed boundary without ever touching a socket again. On request-heavy workloads this is significantly faster, and it is why in-process became the default for the ASP.NET Core project template from version 2.2 onwards. The consequences follow from there being only one process: - The application runs as the **application pool identity**, and cannot have its own. - The **bitness** is the pool's, governed by the pool's 32-bit setting. - Pool **recycling and idle time-out** are the application's lifecycle; there is no separate process to keep alive. - The process name and, without care, the working directory reported at runtime are those of `w3wp.exe`, which trips code that derives paths from the process rather than from the content root. - **Only one in-process application per pool.** A second one in the same pool fails with an ASP.NET Core Module error saying multiple in-process applications cannot share a process — a common surprise when several applications are consolidated into one pool for memory reasons. ## Reading the failure The two models fail visibly differently, and knowing which code belongs to which saves a lot of time: - **500.30** — the in-process application threw during startup. The process is `w3wp.exe`, so the exception detail goes to the module's stdout log or to the Windows Application event log, not to any file your app opened, because the app never got as far as configuring logging. - **502.5** — the out-of-process application failed to start or exited, so there was nothing to proxy to. Same investigation: enable `stdoutLogEnabled` temporarily and read the captured console output. In both cases `stdoutLogEnabled="true"` is a diagnostic switch, not a logging strategy — the file is never rotated and grows unbounded, so it goes back to `false` once the cause is found. ## Choosing Take in-process by default: it is the template default, it is faster, and there is one fewer moving part. Take out-of-process when the separation buys something concrete: several applications must share one pool; the application needs a different identity or bitness from the pool; you want the runtime process to be exactly what it is in every other environment, so that behaviour and diagnostics match; or you are deliberately keeping IIS as nothing more than a reverse proxy and TLS terminator in front of a normal Kestrel service. Either way the choice is a deployment property, not an application-design one — it lives in the published `web.config`, is set from the project file, and can be changed without touching code.

  • Two ASP.NET Core applications are moved into one IIS application pool and one of them stops working. What is the likely cause?
    Both are configured for in-process hosting, and only one in-process application can live in a worker process. The module refuses the second with an error stating that multiple in-process applications in the same process are not supported. Either give each application its own pool, or switch them to out-of-process, where each runs as a separate proxied process.
  • Why does an in-process ASP.NET Core application on IIS ignore the URLs or ports it configures for Kestrel?
    Because Kestrel is not the server. In-process hosting substitutes the IIS-backed server implementation, and the endpoints come from the site's IIS bindings. Configured URLs, ports and Kestrel endpoint options are simply not used. Out-of-process is the reverse — Kestrel does listen, but only on the loopback port the module assigns it.
  • An ASP.NET Core site on IIS returns HTTP 500.30 and the application's own log file is empty. Where do you look?
    The failure happened before logging was configured, so the app wrote nothing. Set `stdoutLogEnabled="true"` in web.config, reproduce, and read the captured console output; the Windows Application event log usually carries the same exception from the module. Turn stdout logging back off afterwards — it is unbounded and never rotated.

saying these in an interview costs you the question

  • Believes in-process still runs Kestrel underneath
  • Thinks IIS bindings are ignored in in-process hosting
  • Says several in-process apps can share one pool
  • Leaves stdout logging enabled as the app's log
  • Assumes the hosting model requires a code change

context