Updated on

HTTP Error 500.31 means IIS could not start the .NET runtime our ASP.NET Core app targets. Usually that runtime isn’t installed on the server, and we fix it by installing the matching .NET Hosting Bundle and restarting IIS.

A misspelled DLL name in web.config produces the very same page, so we’ll learn how to tell the two apart, too.

To download the source code for this article, you can visit our GitHub repository.

Let’s start.

What Does HTTP Error 500.31 Mean?

We’ve just published our first ASP.NET Core application on IIS, and we’re eager to see how it looks. We navigate to the web server link and… oh no, we see this error:

HTTP Error 500.31 - Failed to load ASP.NET Core runtime page shown by IIS

HTTP Error 500.31 means IIS loaded the ASP.NET Core Module, but the module could not start the .NET runtime our app asked for. Every request gets the same page:

“HTTP Error 500.31 – Failed to load ASP.NET Core runtime. Common solutions to this issue: The specified version of Microsoft.NetCore.App or Microsoft.AspNetCore.App was not found.”

Microsoft’s troubleshooting guide for IIS names the usual culprit: “The most common cause of this startup failure is when the Microsoft.NETCore.App or Microsoft.AspNetCore.App runtime isn’t installed.”

The error comes from the in-process hosting model, the default when we publish to IIS. The module asks the .NET host to find the frameworks listed in our app’s runtimeconfig.json file. When that lookup fails, the module stops and answers with 500.31.

The page also tells us one thing for free. The ASP.NET Core Module renders it, so the module is installed and working. What it can’t find is the runtime, or the app it was told to load.

If you want to see how that model works inside the IIS worker process, we have a whole article on the in-process hosting model.

What Causes HTTP Error 500.31?

Let’s look at the most common reasons that may trigger this error.

Missing .NET Hosting Bundle

The .NET Hosting Bundle is the package that provides the infrastructure IIS needs to host and run ASP.NET Core applications. With each version of .NET, we get a corresponding Hosting Bundle that includes the following components:

  • .NET Runtime
  • ASP.NET Core Runtime
  • ASP.NET Core Module

That’s all we need to run our application successfully.

To quickly check if our machine has the bundle installed we can look inside the Control Panel:

In Programs and Features, the bundle shows up as Microsoft .NET 10.0.10 - Windows Server Hosting, with the patch version we installed in its name.

Another way to confirm the presence of the .NET Hosting Bundle is through the Registry Editor. We can find it by typing the regedit command in the Windows Search box and then navigate to Computer\HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Updates\.NET path:

Under that key, the bundle has its own entry, Microsoft .NET 10.0.10 - Windows Server Hosting (x86). The (x86) is part of the name.

If we installed the Hosting Bundle before we enabled IIS, the bundle is listed but IIS doesn’t know about the module, and we have to run the installer again to repair it.

Don’t worry if the bundle is not present, we’ll cover the installation steps in the next section.

If this is your first time on IIS, our guide to deploying an ASP.NET Core app to IIS walks through the whole setup.

Incompatible .NET Runtime Versions

Next, let’s analyze another potential cause that can trigger our error, the incompatibility between the .NET runtime version used by our application and the one installed on the machine running the IIS.

By default, .NET rolls an app forward only within its own major version. An app built for .NET 10 runs on the newest 10.0 patch the server has, such as 10.0.13, but it never moves to .NET 11 on its own.

That cuts both ways. If our application targets a newer version than the one installed on the server, we get HTTP Error 500.31. And if it targets an older major version, say .NET 8, a server that only has .NET 10 installed gives us the same error. That second case is easy to hit right after a server upgrade.

If our app targets .NET 8 or .NET 9, there’s a deadline too. Both leave support on November 10, 2026, so retargeting to .NET 10 is the fix that lasts.

First, let’s see which version our app asks for. After we publish, the site folder holds a <ProjectName>.runtimeconfig.json file, and its frameworks list names the minimum versions:

{
  "runtimeOptions": {
    "tfm": "net10.0",
    "frameworks": [
      {
        "name": "Microsoft.NETCore.App",
        "version": "10.0.0"
      },
      {
        "name": "Microsoft.AspNetCore.App",
        "version": "10.0.0"
      }
    ]
  }
}

Then, on the server, we can run dotnet --list-runtimes in Command Prompt or PowerShell to see what is installed:

PS C:\> dotnet --list-runtimes
Microsoft.AspNetCore.App 8.0.27 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 10.0.10 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 8.0.27 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 10.0.10 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]

Our app needs Microsoft.AspNetCore.App 10.0.0 or a newer 10.0 patch, and 10.0.10 is there, so this server can run it. If the Microsoft.AspNetCore.App line for our major version is missing, we’ve found our cause. The dotnet --info command shows the same list, along with the SDKs and other details.

We cover more ways to check which .NET runtimes are installed, on Windows and Linux, in a separate article.

Can we make an older app run on a newer runtime? Yes. Setting <RollForward>Major</RollForward> in the project file lets an app move to the next major version when its own is missing. It picks the lowest newer major, though: in our test, an app that asked for .NET 7 landed on .NET 8, not .NET 10. We’d rather install the runtime the app was tested on, because a major version can change behavior. The roll-forward rules list every option.

Web.Config Referencing an Incorrect DLLΒ 

Despite the factors representing the main reasons for our error to appear, there is another not-so-common cause, a misconfigured DLL reference inside the Web.Config file.

Once our project has been compiled, a DLL file is created, usually following the convention of <ProjectName>.dll, where <ProjectName> is the name of our project. The configuration file must correctly point to the DLL in order for the application to run correctly.

This cause is sneaky because the page doesn’t change. A wrong DLL name gives us the same HTTP Error 500.31 headline and the same “The specified version of Microsoft.NetCore.App or Microsoft.AspNetCore.App was not found.” line, even though every runtime is installed. Both failures come out of the same lookup inside the ASP.NET Core Module, so the page can’t tell them apart. We’ll see how we can do it ourselves in the next section.

How Do We Fix HTTP Error 500.31?

We fix it by giving the module the runtime our app asks for, or the right file to load. In order:

First, we open the app’s runtimeconfig.json file in the site folder. Its frameworks list names the minimum versions the app needs, for example Microsoft.AspNetCore.App 10.0.0.

Then, we run dotnet --list-runtimes on the server. Any 10.0 patch satisfies a 10.0.0 request, but a .NET 8 or .NET 9 runtime never does.

If our version is missing, we install the .NET Hosting Bundle for that major version and restart IIS. Microsoft’s Hosting Bundle documentation warns that “a manual IIS restart may be required” after the install.

If the runtime is there and the error stays, we check the arguments attribute in web.config. A misspelled DLL name gives us the same 500.31 page.

When we can’t install anything on the server, we retarget the app to a runtime the server already has, or publish it self-contained so it brings its own.

Finding Out Which Cause We Have

Before we install anything, let’s find out which of the two causes we’re dealing with. The browser page is the same for both, but the Windows Event Log isn’t.

In Event Viewer, under Windows Logs > Application, the ASP.NET Core Module writes one of two messages. A missing runtime gives us “Unable to locate application dependencies. Ensure that the versions of Microsoft.NetCore.App and Microsoft.AspNetCore.App targeted by the application are installed.” A wrong DLL path gives us “Provided application path does not exist, or isn’t a .dll or .exe.”

We can also make the page itself more specific. The module prints the full error only when detailed errors are on, so we add one environment variable to the aspNetCore element in web.config:

<aspNetCore processPath="dotnet" arguments=".\IisHttpError500-31.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="inprocess">
  <environmentVariables>
    <environmentVariable name="ASPNETCORE_DETAILEDERRORS" value="true" />
  </environmentVariables>
</aspNetCore>

Now the page gains a “Specific error detected by ANCM” section. For a missing runtime, it carries the .NET host’s own message, which names the framework and version it looked for and lists every version it found. That’s usually all we need to pick the right Hosting Bundle.

Detailed errors show our server’s paths to anyone who opens the site, so let’s remove the variable as soon as we’ve read the error.

Installing the Correct Version of the .NET Hosting Bundle

First of all, we should be aware of the .NET version used by our application. For this article, we’ll be using a .NET 10 web application, therefore we need to install the .NET 10 Hosting Bundle.

We can find the Hosting Bundle on the official Microsoft website, on the .NET 10 download page. In the ASP.NET Core Runtime section for Windows, the Hosting Bundle link downloads the installer for the newest 10.0 patch. Other versions have their own pages on the same site.

In most cases, the patch number doesn’t matter. Our app runs on any installed patch of its major version that is equal to or newer than the one its runtimeconfig.json names.

Once the download is completed, we can go ahead and install it.

Next, we restart IIS from an elevated command prompt, because the IIS worker processes may not see the newly installed runtime until we do:

net stop was /y
net start w3svc

Referencing the Valid DLL in Web.Config

As mentioned before, our Web.Config file should correctly reference the compiled application DLL. In order to verify this, let’s take a look at the root folder of our IIS website:

checking Iis-root for HTTP Error 500.31 dll file

As we can see, IisHttpError500-31.dll is the name of the DLL file, therefore let’s make sure the Web.Config invokes it correctly:

checking iis-dll-reference for the HTTP Error 500.31 path

Oops, it seems we have a typo, we’re pointing to .\IisHttpError500-31x.dll instead of .\IisHttpError500-31.dll, let’s fix it, and then, as usual, restart the IIS website.

For a self-contained app, the file to check is the processPath attribute instead, which points at .\IisHttpError500-31.exe, and there’s no DLL in arguments at all.

Publishing the App as Self-Contained

Sometimes we can’t install a runtime on the server. A shared host might not let us, or other apps on the box might need the server to stay as it is. In that case, we can ship the runtime with our app by publishing it self-contained:

dotnet publish -c Release -r win-x64 --self-contained true

The -r win-x64 option picks the target platform, and --self-contained true copies the .NET and ASP.NET Core runtimes into the publish folder. The module then starts the app from its own folder and never looks for an installed runtime.

If the publish step itself is new, we explain the difference between build and publish separately.

It isn’t free. Our small sample’s publish folder grew from 12 MB to 122 MB, and every security patch to .NET now means republishing the app ourselves.

The server still needs the ASP.NET Core Module, though. If it only hosts self-contained apps, we can install the Hosting Bundle with the OPT_NO_RUNTIME=1 and OPT_NO_SHAREDFX=1 options to skip the runtimes.

One more thing to avoid here: PublishSingleFile. In-process hosting can’t load a single-file app, and IIS answers that with a different error, 500.38.

Recycling the Application Pool

Even though the fixes above should take care of our error, sometimes it may persist because the worker process keeps serving the error from its first failed start. This can be solved by recycling our IIS application pool, thus leading to resource cleanup. To do this we can go to the Application Pools section inside the IIS, select the pool used by our website, and recycle it:

IIS-app-pool-recycle

Recycling only restarts our site’s worker process, though. After installing a Hosting Bundle, the full IIS restart from earlier is the safer step.

Conclusion

In this article, we’ve explored the HTTP Error 500.31 and enumerated some of the most common causes and how to fix them. We’ve also seen that the same page can hide a wrong DLL path, and that the Event Log or the detailed-errors switch tells the two causes apart. It’s important to note that there may be additional reasons triggering this error.Β 

Tested with .NET 10.0.10 (SDK 10.0.302): the sample app and the runtime lookups in this article.