{"id":895,"date":"2026-10-19T22:51:15","date_gmt":"2026-10-19T22:51:15","guid":{"rendered":"https:\/\/windows-vps.org\/blog\/?p=895"},"modified":"2026-10-19T22:51:15","modified_gmt":"2026-10-19T22:51:15","slug":"iis-case-insensitive-url-collision-debugging","status":"publish","type":"post","link":"https:\/\/windows-vps.org\/blog\/iis-case-insensitive-url-collision-debugging\/","title":{"rendered":"IIS Case-Insensitive URL Collisions: Debugging Duplicate Content and Rewrite Loops"},"content":{"rendered":"<div style=\"padding:20px\">\n<p>IIS routes URLs through NTFS, and NTFS treats <code>\/Products<\/code> and <code>\/products<\/code> as the same file. That is convenient on a case-insensitive filesystem and a source of subtle bugs when URL rewrite rules, canonical-host redirects, and application routes are written with case-sensitive assumptions copied from Linux. The result is often a rewrite loop that returns 500.19 or a duplicate-content problem search engines see but you do not.<\/p>\n<p>Debugging it starts with knowing which layer made the decision. On a <a href=\"https:\/\/windows-vps.org\/#providers\">Windows VPS running IIS 10 and URL Rewrite 2.1<\/a>, the answer is usually visible in the FREB trace within two requests once you know which fields to read.<\/p>\n<h2>Why IIS URL Comparison Is Case-Blind<\/h2>\n<p>Two separate mechanisms normalise case. The file provider resolves physical paths through the Windows API, which is case-insensitive for NTFS volumes, and URL Rewrite inherits that behaviour when it evaluates conditions on <code>{REQUEST_FILENAME}<\/code> or checks for file existence. Application-level routes in ASP.NET Core are case-insensitive by default as well.<\/p>\n<p>The collision appears when one layer is case-sensitive and another is not. A rewrite rule that maps <code>^\/Old-Path$<\/code> with a case-sensitive match will not fire for <code>\/old-path<\/code>, but IIS will still serve the file if one exists, so you get two live URLs for the same content with no redirect between them.<\/p>\n<ul>\n<li>File system: case-insensitive, preserves the case used at creation.<\/li>\n<li>URL Rewrite: case-insensitive by default unless the rule sets <code>ignoreCase=\"false\"<\/code>.<\/li>\n<li>ASP.NET Core routing: case-insensitive unless <code>RouteOptions.LowercaseUrls<\/code> forces output case.<\/li>\n<li>Windows Authentication and AD lookups: case-insensitive for account names.<\/li>\n<\/ul>\n<h2>Reproducing the Collision with curl and Test-Path<\/h2>\n<p>Reproduce before changing configuration. Request both casings against the live site and compare status codes, <code>Location<\/code> headers, and body length. A 200 on both casings with identical length is a duplicate; a 301 on one and 200 on the other is a canonical redirect; a 500 with a rewrite-related subcode is a loop.<\/p>\n<pre><code>curl.exe -sSI https:\/\/example.com\/Products | Select-String \"HTTP\/\",\"Location\",\"Content-Length\"\ncurl.exe -sSI https:\/\/example.com\/products | Select-String \"HTTP\/\",\"Location\",\"Content-Length\"\nTest-Path C:\\inetpub\\wwwroot\\Products\nTest-Path C:\\inetpub\\wwwroot\\products<\/code><\/pre>\n<p>On the server, list the directory with <code>Get-ChildItem<\/code> and note the actual case on disk. If two directories differing only by case exist, they came from an archive extracted with case preserved, and the second one is dead weight that will still be matched by IIS.<\/p>\n<h2>Reading the FREB Trace for Rewrite Decisions<\/h2>\n<p>Failed Request Tracing logs the rewrite module&#8217;s evaluated rules, the matched pattern, and the resulting URL for each pass. Enable it for the URL in question with a 200-599 status range and a 100 MB limit, then read the <code>REWRITE_DETAILS<\/code> block in the XML trace.<\/p>\n<p>The key field is <code>RewrittenUrl<\/code>. A trace showing the same rule firing twice with the URL flipping between two forms is the definition of a loop, and the second <code>REWRITE_START<\/code> after a <code>REWRITE_END<\/code> confirms the server did not send the first redirect as a terminal response.<\/p>\n<pre><code>Set-WebConfigurationProperty -PSPath 'MACHINE\/WEBROOT\/APPHOST' `\n  -Filter \"system.webServer\/httpTracing\/providers\" -Name \".\" -Value @{guid='{00000000-0000-0000-0000-000000000000}'}\nnetsh http show servicestate | Select-String \"Request queue\",\"Process ID\"<\/code><\/pre>\n<h2>Common Rewrite Loop Patterns and Their Fixes<\/h2>\n<p>The most common loop is a HTTP-to-HTTPS rule that also matches the HTTPS request because the condition tests <code>{HTTPS}<\/code> incorrectly. URL Rewrite reports the condition as <code>off<\/code> unless the scheme is exactly https, so a rule without that condition redirects an already-secure request to itself until IIS stops it.<\/p>\n<p>The second pattern is a trailing-slash canonical rule that appends a slash, then a second rule that strips it. The third is a case-normalisation rule that lowercases the path but re-enters the rewrite pipeline because the output is not marked as a redirect with a terminal flag.<\/p>\n<table>\n<tr>\n<th>Pattern<\/th>\n<th>Symptom<\/th>\n<th>Fix<\/th>\n<\/tr>\n<tr>\n<td>HTTPS rule without scheme condition<\/td>\n<td>ERR_TOO_MANY_REDIRECTS on https<\/td>\n<td>Add <code>{HTTPS} = off<\/code> condition<\/td>\n<\/tr>\n<tr>\n<td>Slash add and strip conflict<\/td>\n<td>Alternating 301 between two URLs<\/td>\n<td>Keep one rule, scope by segment<\/td>\n<\/tr>\n<tr>\n<td>Lowercase rule with rewrite action<\/td>\n<td>500.19, repeated REWRITE_START<\/td>\n<td>Use <code>redirectType=\"Permanent\"<\/code><\/td>\n<\/tr>\n<tr>\n<td>Case-variant static files<\/td>\n<td>Two 200s, no redirect<\/td>\n<td>Remove duplicate directory, add canonical rule<\/td>\n<\/tr>\n<\/table>\n<h2>Adding Case-Insensitive Rewrite Rules Deliberately<\/h2>\n<p>When you want a canonical lowercase URL, write one rule at the top of the chain and make it terminal. The rule below matches any URL containing an uppercase character and redirects to the lowercased form with a 301, stopping further evaluation. Because URL Rewrite evaluates conditions case-insensitively by default, the uppercase check needs the <code>[A-Z]<\/code> pattern with a case-sensitive regex option.<\/p>\n<pre><code>&lt;rule name=\"Lowercase URLs\" stopProcessing=\"true\"&gt;\n  &lt;match url=\"(.*[A-Z].*)\" \/&gt;\n  &lt;action type=\"Redirect\" url=\"{ToLower:{R:1}}\" redirectType=\"Permanent\" \/&gt;\n&lt;\/rule&gt;<\/code><\/pre>\n<p>Place it before the HTTPS rule so the lowercase decision happens once. Note that <code>{ToLower:}<\/code> only lowercases the path portion you capture; query strings pass through unchanged and should not be normalised unless your application treats them case-insensitively.<\/p>\n<h2>Preventing Duplicate Content at the Application Layer<\/h2>\n<p>Search engines treat <code>\/Products<\/code> and <code>\/products<\/code> as distinct URLs even when IIS serves the same bytes. Add a canonical link tag with the lowercase form and keep a single 301 rule, rather than relying on the crawler to work it out. In ASP.NET Core, set <code>LowercaseUrls<\/code> and <code>LowercaseQueryStrings<\/code> in <code>RouteOptions<\/code> so links generated by the app never produce mixed case.<\/p>\n<p>For static sites, also fix the source. Files copied from a Linux build often carry lowercase names while hand-written links use title case, and only the Windows-hosted copy will resolve both.<\/p>\n<ul>\n<li>Enable <code>lowercaseUrls<\/code> in routing and in the URL Rewrite outbound rules.<\/li>\n<li>Run a link checker against the rendered HTML, not the source templates.<\/li>\n<li>Keep <code>web.config<\/code> under source control so rule order changes are reviewable.<\/li>\n<\/ul>\n<h2>Diagnosing 500.19 and 404.3 Around Case Fixes<\/h2>\n<p>A 500.19 after adding a rule usually means the configuration section is locked at the applicationHost level or a schema attribute is invalid. Read the subcode in the error page: 500.19 with subcode 5 points at a locked section, subcode 7 at a duplicate collection entry. Release the lock with <code>appcmd set config -section:system.webServer\/rewrite\/rules -overrideModeDefault:Allow<\/code> only if the provider permits it.<\/p>\n<p>A 404.3 means the handler mapping did not match the extension, which appears when a case-normalisation rule rewrites to a path with a different extension than the handler expects. Check the <code>REWRITE_END<\/code> value against <code>Get-WebHandler<\/code> output rather than guessing.<\/p>\n<pre><code>Get-WebConfiguration -PSPath 'MACHINE\/WEBROOT\/APPHOST' -Filter \"system.webServer\/rewrite\/globalRules\" | Select-Object name\nappcmd list config \/section:system.webServer\/rewrite\/rules | Select-String \"name=\"<\/code><\/pre>\n<h2>Checklist Before You Deploy a Canonical Rule<\/h2>\n<p>Test the rule against a matrix of casings and schemes: uppercase path over HTTP, uppercase path over HTTPS, mixed case with a query string, and a deep path with three segments. Each must produce exactly one 301 to the lowercase HTTPS form, with no chain longer than one hop.<\/p>\n<p>Then confirm the physical directory has a single casing, the canonical tag matches the redirect target, and the trace shows one <code>REWRITE_END<\/code> per request. If the <a href=\"https:\/\/windows-vps.org\/\">IIS hosting plans you use<\/a> lock the rewrite section, ask for it before deploying rather than discovering it in production.<\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>IIS routes URLs through NTFS, and NTFS treats \/Products and \/products as the same file. That is convenient on a case-insensitive filesystem and a source of subtle bugs when URL rewrite rules, canonical-host redirects, and application routes are written with case-sensitive assumptions copied from Linux. The result is often a rewrite loop that returns 500.19 &#8230; <a title=\"IIS Case-Insensitive URL Collisions: Debugging Duplicate Content and Rewrite Loops\" class=\"read-more\" href=\"https:\/\/windows-vps.org\/blog\/iis-case-insensitive-url-collision-debugging\/\" aria-label=\"Read more about IIS Case-Insensitive URL Collisions: Debugging Duplicate Content and Rewrite Loops\">Read more<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"iawp_total_views":0,"footnotes":""},"categories":[5],"tags":[],"class_list":["post-895","post","type-post","status-publish","format-standard","hentry","category-tutorials-guides"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v26.1 (Yoast SEO v26.1) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>IIS Case-Insensitive URL Collisions: Debugging Duplicate Content and Rewrite Loops - Windows VPS Blog<\/title>\n<meta name=\"description\" content=\"IIS routes URLs through NTFS, and NTFS treats \/Products and \/products as the same file. That is convenient on a case-insensitive filesystem and a source\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/windows-vps.org\/blog\/iis-case-insensitive-url-collision-debugging\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"IIS Case-Insensitive URL Collisions: Debugging Duplicate Content and Rewrite Loops\" \/>\n<meta property=\"og:description\" content=\"IIS Case-Insensitive URL Collisions: Debugging Duplicate Content and Rewrite Loops\" \/>\n<meta property=\"og:url\" content=\"https:\/\/windows-vps.org\/blog\/iis-case-insensitive-url-collision-debugging\/\" \/>\n<meta property=\"og:site_name\" content=\"Windows VPS Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-10-19T22:51:15+00:00\" \/>\n<meta name=\"author\" content=\"windows-vps\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@myxiechengxuan\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"windows-vps\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"6 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/windows-vps.org\/blog\/iis-case-insensitive-url-collision-debugging\/\",\"url\":\"https:\/\/windows-vps.org\/blog\/iis-case-insensitive-url-collision-debugging\/\",\"name\":\"IIS Case-Insensitive URL Collisions: Debugging Duplicate Content and Rewrite Loops - Windows VPS Blog\",\"isPartOf\":{\"@id\":\"https:\/\/windows-vps.org\/blog\/#website\"},\"datePublished\":\"2026-10-19T22:51:15+00:00\",\"author\":{\"@id\":\"https:\/\/windows-vps.org\/blog\/#\/schema\/person\/44caceed916d0db318aa08d5623a7a58\"},\"description\":\"IIS routes URLs through NTFS, and NTFS treats \/Products and \/products as the same file. That is convenient on a case-insensitive filesystem and a source\",\"breadcrumb\":{\"@id\":\"https:\/\/windows-vps.org\/blog\/iis-case-insensitive-url-collision-debugging\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/windows-vps.org\/blog\/iis-case-insensitive-url-collision-debugging\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/windows-vps.org\/blog\/iis-case-insensitive-url-collision-debugging\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/windows-vps.org\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"IIS Case-Insensitive URL Collisions: Debugging Duplicate Content and Rewrite Loops\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/windows-vps.org\/blog\/#website\",\"url\":\"https:\/\/windows-vps.org\/blog\/\",\"name\":\"Windows VPS Blog\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/windows-vps.org\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/windows-vps.org\/blog\/#\/schema\/person\/44caceed916d0db318aa08d5623a7a58\",\"name\":\"windows-vps\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/windows-vps.org\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/3f2573db5afcd1a6ab9abcc5d48fc8e42584bc87ab9d98cc156e5b2097766dd9?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/3f2573db5afcd1a6ab9abcc5d48fc8e42584bc87ab9d98cc156e5b2097766dd9?s=96&d=mm&r=g\",\"caption\":\"windows-vps\"},\"sameAs\":[\"https:\/\/windows-vps.org\/blog\",\"https:\/\/x.com\/myxiechengxuan\"],\"url\":\"https:\/\/windows-vps.org\/blog\/author\/myxiechengxuan\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"IIS Case-Insensitive URL Collisions: Debugging Duplicate Content and Rewrite Loops - Windows VPS Blog","description":"IIS routes URLs through NTFS, and NTFS treats \/Products and \/products as the same file. That is convenient on a case-insensitive filesystem and a source","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/windows-vps.org\/blog\/iis-case-insensitive-url-collision-debugging\/","og_locale":"en_US","og_type":"article","og_title":"IIS Case-Insensitive URL Collisions: Debugging Duplicate Content and Rewrite Loops","og_description":"IIS Case-Insensitive URL Collisions: Debugging Duplicate Content and Rewrite Loops","og_url":"https:\/\/windows-vps.org\/blog\/iis-case-insensitive-url-collision-debugging\/","og_site_name":"Windows VPS Blog","article_published_time":"2026-10-19T22:51:15+00:00","author":"windows-vps","twitter_card":"summary_large_image","twitter_creator":"@myxiechengxuan","twitter_misc":{"Written by":"windows-vps","Est. reading time":"6 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/windows-vps.org\/blog\/iis-case-insensitive-url-collision-debugging\/","url":"https:\/\/windows-vps.org\/blog\/iis-case-insensitive-url-collision-debugging\/","name":"IIS Case-Insensitive URL Collisions: Debugging Duplicate Content and Rewrite Loops - Windows VPS Blog","isPartOf":{"@id":"https:\/\/windows-vps.org\/blog\/#website"},"datePublished":"2026-10-19T22:51:15+00:00","author":{"@id":"https:\/\/windows-vps.org\/blog\/#\/schema\/person\/44caceed916d0db318aa08d5623a7a58"},"description":"IIS routes URLs through NTFS, and NTFS treats \/Products and \/products as the same file. That is convenient on a case-insensitive filesystem and a source","breadcrumb":{"@id":"https:\/\/windows-vps.org\/blog\/iis-case-insensitive-url-collision-debugging\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/windows-vps.org\/blog\/iis-case-insensitive-url-collision-debugging\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/windows-vps.org\/blog\/iis-case-insensitive-url-collision-debugging\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/windows-vps.org\/blog\/"},{"@type":"ListItem","position":2,"name":"IIS Case-Insensitive URL Collisions: Debugging Duplicate Content and Rewrite Loops"}]},{"@type":"WebSite","@id":"https:\/\/windows-vps.org\/blog\/#website","url":"https:\/\/windows-vps.org\/blog\/","name":"Windows VPS Blog","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/windows-vps.org\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/windows-vps.org\/blog\/#\/schema\/person\/44caceed916d0db318aa08d5623a7a58","name":"windows-vps","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/windows-vps.org\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/3f2573db5afcd1a6ab9abcc5d48fc8e42584bc87ab9d98cc156e5b2097766dd9?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/3f2573db5afcd1a6ab9abcc5d48fc8e42584bc87ab9d98cc156e5b2097766dd9?s=96&d=mm&r=g","caption":"windows-vps"},"sameAs":["https:\/\/windows-vps.org\/blog","https:\/\/x.com\/myxiechengxuan"],"url":"https:\/\/windows-vps.org\/blog\/author\/myxiechengxuan\/"}]}},"_links":{"self":[{"href":"https:\/\/windows-vps.org\/blog\/wp-json\/wp\/v2\/posts\/895","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/windows-vps.org\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/windows-vps.org\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/windows-vps.org\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/windows-vps.org\/blog\/wp-json\/wp\/v2\/comments?post=895"}],"version-history":[{"count":1,"href":"https:\/\/windows-vps.org\/blog\/wp-json\/wp\/v2\/posts\/895\/revisions"}],"predecessor-version":[{"id":896,"href":"https:\/\/windows-vps.org\/blog\/wp-json\/wp\/v2\/posts\/895\/revisions\/896"}],"wp:attachment":[{"href":"https:\/\/windows-vps.org\/blog\/wp-json\/wp\/v2\/media?parent=895"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/windows-vps.org\/blog\/wp-json\/wp\/v2\/categories?post=895"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/windows-vps.org\/blog\/wp-json\/wp\/v2\/tags?post=895"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}