If your ASP.NET Core 10 site lives at https://example.com/api or https://example.com/customerportal instead of the site root, IIS is doing its job and your app is probably not. Redirects jump to the wrong place, cookie Path values miss the virtual directory, static file URLs 404, and OpenID connect return URLs no longer match what you registered. The fix is not another rewrite rule first—it is teaching the app its PathBase.

Shared and multi-app Windows hosts lean on IIS sites, applications, and virtual directories so several .NET apps share one hostname. ASP.NET Core’s routing and link generation assume the app owns “/” unless you set the path base. Get that wrong and every environment that is not localhost:5xxx looks broken after Web Deploy.

#What IIS hands you vs what Kestrel/ANCM sees

In IIS Manager, a site can host several Applications, each with its own app pool and physical path. A request to /portal/account/login arrives at the portal application with the site-relative path still including /portal. With the ASP.NET Core Module (in-process or out-of-process), that full path is what your middleware pipeline sees by default. Route templates like account/login never match /portal/account/login, so you get 404s that look like a deploy problem.

Path base is the prefix IIS (or a reverse proxy) uses for the app. ASP.NET Core strips it for routing, then puts it back when it builds absolute paths, redirects, and cookie scopes. On .NET 10 this is the same contract as prior LTS releases; what changes on a host is how often apps are published into a non-root application path.

#Set PathBase early in the pipeline

Call UsePathBase before UseRouting, authentication, and endpoints. Hard-coding works when the virtual app path is fixed; reading it from configuration keeps the same build working at site root in staging and under /portal in production.

csharp
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// Match the IIS application path, e.g. /portal — empty at site root
var pathBase = builder.Configuration["PathBase"]; // "/portal"
if (!string.IsNullOrEmpty(pathBase))
{
    app.UsePathBase(pathBase);
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();

Prefer configuration over guessing from headers on shared IIS. X-Forwarded-Prefix can help behind extra proxies, but a virtual application path is an IIS fact you already know at deploy time. Pair PathBase with the usual ASPNETCORE_ENVIRONMENT value in the aspNetCore web.config section so each IIS app gets the right prefix without rebuilding.

xml
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <location path="." inheritInChildApplications="false">
    <system.webServer>
      <handlers>
        <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" />
      </handlers>
      <aspNetCore processPath="dotnet"
                  arguments=".\Portal.Web.dll"
                  stdoutLogEnabled="false"
                  hostingModel="inprocess">
        <environmentVariables>
          <environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Production" />
          <environmentVariable name="PathBase" value="/portal" />
        </environmentVariables>
      </aspNetCore>
    </system.webServer>
  </location>
</configuration>

#Cookies, static files, and external login callbacks

After PathBase is set, most middleware cooperates. A few spots still need an explicit check when you move an app from site root into a virtual application:

  • Cookie auth: set Cookie.Path to the virtual app path (or rely on PathBase-aware helpers) so the browser does not send portal cookies to sibling apps on the same host.
  • Data-protection and correlation cookies for external login: register redirect URIs that include the virtual path (https://host/portal/signin-oidc), not the site root.
  • Static files and SPA fallback: UseStaticFiles and MapFallbackToFile generate request paths under the path base; do not prefix ~/ or content root URLs a second time in Razor or JS.
  • LinkGenerator, Url.Action, and Tag Helpers: once PathBase is applied, they emit /portal/... automatically—avoid manual string concat of the prefix.

If you still use IIS URL Rewrite for HTTPS or canonical hosts, keep those rules at the site level and do not strip the application path before ANCM runs. Rewriting /portal away “to clean routes” undoes PathBase and reintroduces the 404 loop you just fixed.

#Deploy checklist on Windows hosts

Create the IIS Application under the site (not only a virtual directory without its own app pool) so the app can recycle independently. Point the physical path at the published folder for that app alone. Confirm the app pool runs the bitness you published (typically 64-bit for .NET 10), No Managed Code for the CLR setting, and that AspNetCoreModuleV2 is present on the server. Smoke-test three URLs after Web Deploy: a MVC/API route, a static file under wwwroot, and a login redirect—each must keep /portal in the browser bar.

For SQL-backed apps, connection strings do not change because of PathBase, but any absolute URLs you store (password-reset links, webhook endpoints, SSRS callbacks) must include the virtual path or be generated through LinkGenerator at send time. Generating those links with PathBase configured once is safer than find-and-replace across environments.

Practical takeaway: treat the IIS application path as first-class configuration. Set PathBase (or an equivalent config value) before routing, align cookie paths and OAuth return URLs with that prefix, and keep rewrite rules from eating the virtual directory. On a Windows host—whether one site root or several apps under one hostname—that single middleware call is what keeps .NET 10 links, auth, and static assets honest after deploy.