The Azure App Service default hostname problem (and the web.config fix)
You added a custom domain, pointed everyone at the login page, and shipped. Then someone pastes the old *.azurewebsites.net URL into a chat and lands straight on the raw app you thought you'd hidden. The default hostname never left — and it's still a front door.
This one bites Windows App Service teams running a classic ASP.NET Framework app — the kind with a web.config at the root. You stand up the App Service, it comes with a public address like yourappname.azurewebsites.net, you add your real domain, and you wire your links and DNS to the domain. Job done, you think. But the platform-issued default hostname is still live, still public, and still routes to the exact same app. Anyone who has that URL — an old bookmark, a cached search result, a screenshot — reaches your app on it and skips whatever front door your custom domain was supposed to enforce.
Why the "obvious" fixes are dead ends
Before the real fix, two roads that look right and aren't — so you don't spend an afternoon on them:
- "I'll just delete the default hostname." You can't. The
*.azurewebsites.netaddress is assigned by the platform and can't be removed. Custom domains are additive; the default is permanent for the life of the app. - "I'll make the App Service private." Locking public access down (access restrictions, private endpoint) does hide the default hostname — but it hides everything, including your custom domain, because they front the same app. You take the whole site down to close one door.
Both dead ends share a root cause: the default hostname and your custom domain are two labels on one app. You can't remove one label, and you can't block traffic to one without blocking the other. So you stop trying to close the default door and instead make it send people through the right one.
The fix: redirect the default hostname in web.config
Because this is a classic ASP.NET app on Windows App Service, IIS is already in the request path — and it ships the URL Rewrite module. That's all you need. Add one rule that inspects the incoming {HTTP_HOST}, and when it sees the default hostname, redirects to your custom domain. Here's the canonical rule — swap in your own app name, custom domain, and (if you want) login path:
<rule name="Redirect Azure default domain" stopProcessing="true"> <match url="(.*)" /> <conditions> <add input="{HTTP_HOST}" pattern="yourappname.azurewebsites.net" /> </conditions> <action type="Redirect" url="https://your-custom-domain.com/{R:0}" redirectType="Permanent" appendQueryString="false" /> </rule>
Lives inside <system.webServer><rewrite><rules> in your root web.config
A few things worth knowing about that rule:
{R:0}is the whole matched path from<match url="(.*)">, so a visitor hitting/pricing?ref=xlands onyour-custom-domain.com/pricing— the path carries through. If you'd rather funnel everyone to one place regardless of where they came in, drop{R:0}and hardcode the route:url="https://your-custom-domain.com/login".stopProcessing="true"plus placement matters. Put this rule where it won't fight your existing rules, and let it short-circuit the pipeline once it fires. If you already have canonicalization or HTTPS rules, order them deliberately.- Hostnames aren't case-sensitive, so you don't need to worry about
YourAppNamevsyourappnamein the condition.
Where to edit it — no redeploy required
You don't need a pipeline run to change one config file. Edit it in place through Kudu:
- Portal → your App Service → Advanced Tools → Go (that's Kudu).
- Debug console → CMD, then browse to
site/wwwroot. - Open
web.configwith the pencil icon, add the rule inside<system.webServer><rewrite><rules>, and save.
Saving web.config recycles the app automatically — the change is live within seconds, no build, no deploy. That also makes it a fast, low-risk place to iterate while you get the rule right.
The gotcha that will waste your afternoon: patternSyntax
This is the real teaching moment, and it fails silently — the rule saves fine, the app recycles fine, and nothing redirects. The culprit is the patternSyntax of the surrounding <rules> block. URL Rewrite conditions can be matched as ECMAScript regex (the default) or as Wildcard. If your rules block is set to Wildcard — which plenty of scaffolded configs are — then a regex-style pattern with escaped dots never matches, because in Wildcard mode the dot is already a literal character and \ is just a stray backslash.
<!-- WRONG in Wildcard mode: escaped dots never match --> <add input="{HTTP_HOST}" pattern="printscan\.azurewebsites\.net" /> <!-- RIGHT in Wildcard mode: the dot is already literal --> <add input="{HTTP_HOST}" pattern="printscan.azurewebsites.net" />
Same intent, opposite outcome — the difference is one patternSyntax setting
So before you touch the pattern, check the opening tag of the rules block. If it reads <rules patternSyntax="Wildcard">, drop the backslashes. If there's no patternSyntax attribute or it says ECMAScript, escaped dots are correct. The canonical rule above uses an unescaped host precisely so it's safe under Wildcard; if your block is regex, either escape the dots or leave them — a literal . still matches a real dot, it just also matches any character, which for a fixed hostname you control is harmless.
The rule that "does nothing" is almost never wrong logic. It's the pattern being read in a dialect you didn't know the block was speaking.
Testing it without hard-caching a mistake
A 301 is permanent, and browsers cache permanent redirects aggressively — get it wrong and you'll be fighting your own cache. So test in stages:
- Test in a private / incognito window every time, so you're never reading a cached response.
- While iterating, set
redirectType="Found"(a 302, temporary). If the rule is wrong, nothing gets stuck in a cache you can't clear. - Once the default hostname redirects cleanly to the right place, switch back to
redirectType="Permanent"(301) for the SEO and correctness win. - Finally, load your custom domain directly and confirm it still serves normally — the whole point is that the rule fires only on the default hostname and leaves real traffic alone.
Redirecting the default hostname is the classic-ASP.NET flavour of "App Service isn't behaving." Its ASP.NET Core cousin looks nothing like it but shows up just as often: Decoding HTTP 500.30 — the ASP.NET Core startup failure. Two common App Service faceplants, different app classes — worth knowing both on sight.