Most 502s and redirect loops after a .NET 10 deploy are not ANCM failures. They are web.config collisions: Visual Studio or `dotnet publish` drops a fresh ASP.NET Core section and quietly discards the URL Rewrite rules you tuned last month.

On Windows Server 2025 with IIS 10.0, the Microsoft URL Rewrite module is still the right tool for host canonicalization, HTTPS enforcement, and blocking junk paths before they hit Kestrel. The fix is structural: own the rewrite section, merge it on every publish, and verify with PowerShell instead of guessing from the browser.

#What publish actually overwrites

ASP.NET Core’s web.config is generated for the AspNetCoreModuleV2 handler. If your project has no web.config, publish creates one. If you do have one, the SDK merges the `<aspNetCore>` element but will not invent rewrite rules for you. Rules that lived only in the site root on the server—and never in source—disappear on the next Web Deploy or FTP sync.

Keep a web.config in the project root (or use a transform / pipeline step) so rewrite configuration travels with the app. On shared Windows hosting you usually cannot install Helicon ISAPI_Rewrite; stick to the built-in Rewrite module and standard rule XML.

#A durable web.config pattern

The following skeleton forces HTTPS, collapses www, blocks common probe paths, and leaves the ANCM handler alone. Adjust host names and paths to match the site. Place it under the site root so it is part of every publish output.

xml
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <rewrite>
      <rules>
        <rule name="Block probes" stopProcessing="true">
          <match url=".*" />
          <conditions logicalGrouping="MatchAny">
            <add input="{REQUEST_URI}" pattern="(?i)/(\.env|wp-admin|phpmyadmin)" />
            <add input="{URL}" pattern="(?i)\.(php|asp)$" />
          </conditions>
          <action type="CustomResponse" statusCode="404" statusReason="Not Found" statusDescription="Not Found" />
        </rule>
        <rule name="HTTPS redirect" 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="Canonical host" stopProcessing="true">
          <match url="(.*)" />
          <conditions>
            <add input="{HTTP_HOST}" pattern="^www\.example\.com$" />
          </conditions>
          <action type="Redirect" url="https://example.com/{R:1}" redirectType="Permanent" />
        </rule>
      </rules>
    </rewrite>
    <handlers>
      <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
    </handlers>
    <aspNetCore processPath="dotnet"
                arguments=".\MyApp.dll"
                stdoutLogEnabled="false"
                stdoutLogFile=".\logs\stdout"
                hostingModel="inprocess" />
  </system.webServer>
</configuration>

Order matters: deny rules first, then HTTPS, then host canonicalization. `stopProcessing="true"` prevents a later rule from undoing an earlier redirect. Prefer `inprocess` hosting on current Windows hosts unless you have a documented need for out-of-process.

#Shared-host and load-balancer gotchas

  • If TLS terminates at a front-end and IIS only sees HTTP, `{HTTPS}` stays OFF and a naive HTTPS rule loops. Use `{HTTP_X_FORWARDED_PROTO}` (or the header your edge sets) in a condition, or disable the rule when the host already enforces TLS at the edge.
  • App pool identity must read the site folder; rewrite itself does not change that. After Web Deploy, confirm the pool is still .NET CLR “No Managed Code” for ASP.NET Core.
  • stdout logs belong under a logs folder with write ACL for the pool identity—never enable stdout long-term on production shared sites.
  • Do not mix Helicon-style .htaccess syntax into Microsoft Rewrite XML; translate conditions explicitly.

#Verify with PowerShell before the next deploy

On a Windows VPS or dedicated box you administer, confirm the Rewrite module and dump effective rules. On shared plans, module install is host-managed; still keep the same web.config in source so support can diff what you intended.

powershell
# Requires admin / elevated remote session on the server
Get-WebGlobalModule | Where-Object { $_.Name -match 'Rewrite' }

Import-Module WebAdministration
$site = 'Default Web Site'
Get-WebConfiguration -PSPath "IIS:\Sites\$site" -Filter 'system.webServer/rewrite/rules/rule' |
  Select-Object name, enabled, stopProcessing

# Quick HTTPS binding check (SNI)
Get-WebBinding -Name $site | Format-Table protocol, bindingInformation, certificateHash

If `Get-WebGlobalModule` shows no Rewrite module, rules in web.config are ignored and you will chase phantom 200s. Install “IIS URL Rewrite” via the platform’s module package or Web PI equivalent, recycle the pool, and re-test with `curl -I` against both http and www host names.

#Pipeline habit that prevents regressions

Treat web.config as application code. Store it in git, fail CI if the rewrite section is missing after publish, and smoke-test redirects in a staging site binding before production Web Deploy. For teams that generate web.config entirely from the SDK, add a post-publish MSBuild target or a small script that merges your rules XML into the output folder—do not paste rules only in the IIS GUI.

Practical takeaway: put HTTPS, host, and probe-blocking rules in a project-tracked web.config ahead of the `<aspNetCore>` handler, keep deny rules first with `stopProcessing`, and after every deploy confirm Rewrite is loaded and bindings still match the certificate. That sequence eliminates most post-publish redirect loops on IIS 10 under Windows Server 2025 without touching application code.