An IIS site that answers on every hostname your VPS responds to is a common and avoidable misconfiguration. On a Windows Server VPS the default site binds to *:80 — all unassigned IPs, all hostnames. That is fine on a laptop. In production it means https://windows-vps.org/blog/-style canonical hostnames, an IP address, and random subdomains all serve the same content, which splits crawl budget, breaks cookies, and leaks internal hostnames to scanners.
For a working reference architecture, see the hosting context on windows-vps.org. This article is the technical how-to: one bindings model that makes every request resolve to exactly one canonical hostname, then redirect anything that does not match.
Why a catch-all binding is a real problem
When IIS has no bindings that match the incoming Host header, it falls back to the first site that has a matching IP and port. Attackers and crawlers exploit this consistently:
- Duplicate content:
https://203.0.113.10/,https://www.example.com/, andhttps://example.internal/all return HTTP 200 with identical HTML. - Cookie leakage: cookies scoped to
.example.comare sent to any subdomain pointing at the same server, including ones you never intended to expose. - TLS misrouting: with SNI, a request for an unknown hostname may land on the wrong certificate and produce a name-mismatch warning that looks like your fault.
- SEO dilution: search engines see several URLs for one page and split ranking signals between them.
Step 1: Inventory what IIS is actually serving
Start in an elevated PowerShell session. Enumerate every site and binding before you change anything:
Import-Module WebAdministration
Get-Website | Select-Object Name, State, PhysicalPath, Bindings |
Format-List
Get-WebBinding | Select-Object ItemXPath, protocol, bindingInformation |
Sort-Object bindingInformation
Look for bindings where the hostname portion (the third field after the IP and port) is empty. An empty hostname is the catch-all. On a fresh VPS template you will usually see one site named Default Web Site with *:80: and nothing else.
Step 2: Create explicit bindings for the canonical host only
Decide on exactly one canonical hostname — the one you will publish, link, and put in your sitemap. Pick one form and never publish the other. Then add explicit bindings:
New-WebBinding -Name "MyApp" -Protocol http -Port 80 -HostHeader "app.example.com"
New-WebBinding -Name "MyApp" -Protocol https -Port 443 -HostHeader "app.example.com" `
-SslFlags 1 # 1 = SNI required
The SslFlags 1 parameter is important on any VPS hosting more than one TLS site: it forces Server Name Indication so IIS selects the certificate by hostname rather than by IP address alone.
Step 3: Catch everything else and return 444 or a redirect
The cleanest pattern is three layers: the canonical site, a redirect site for the alternate form (www to apex, or the reverse), and an empty “reject” site bound to the catch-all binding that terminates unmatched traffic rather than serving content.
| Incoming Host | Matching site | Result |
|---|---|---|
| app.example.com | MyApp | 200 — canonical, served |
| www.app.example.com | RedirectSite | 301 to https://app.example.com |
| 203.0.113.10 (raw IP) | RejectSite | Connection closed / 404 |
| unknown.example.com | RejectSite | Connection closed / 404 |
IIS has no native 444 like nginx, but the equivalent behaviour is a catch-all site whose root is an empty folder holding a bare web.config that returns HTTP 404 with a null body and no server banner. Attackers who scan by IP stop learning anything useful about your deployment.
Step 4: Handle the port-443 blind spot
Catch-all HTTPS is subtler than HTTP. A client requesting an unknown hostname over TLS will fail during the handshake before IIS can route it, unless a default certificate is configured. Bind the catch-all HTTPS site to a self-signed or generic certificate so the handshake completes and your rejection site can answer cleanly. Keep the certificate out of any path the public canonical site uses.
HTTP/3 and the host-header edge case
If you enable HTTP/3 on IIS (Windows Server 2022 and later), the QUIC listener binds per-site and interacts with SNI differently from TCP. A site with a catch-all binding and HTTP/3 enabled can behave inconsistently across clients — some browsers reach the reject site by accident. Keep HTTP/3 enabled only on the canonical site, and re-run the host-header tests over both TCP and UDP after any protocol change.
Keeping the canonical set consistent across layers
IIS bindings are only one of three places a hostname is enforced. A hostname change that is not propagated everywhere creates exactly the redirect chains you set out to eliminate. Audit all three whenever you add a domain:
| Layer | What to check | Tool |
|---|---|---|
| DNS | A/AAAA records point only at serving hosts | Resolve-DnsName |
| IIS | Explicit host-header bindings, SNI flag | Get-WebBinding |
| App | Generated absolute URLs use canonical host | App config / runtime test |
An application that generates redirect URLs from the incoming Host header rather than a configured value is the most common source of accidental non-canonical URLs. Pin the public base URL in configuration so an attacker cannot force your app to emit links to a hostname they control.
Verification checklist
- Request the canonical host — expect HTTP 200 and correct content.
- Request the alternate (
wwwor apex) — expect a single 301, never a chain of redirects. - Request the raw IP — expect a rejection or 404, not your site.
- Request a made-up hostname with
curl -H "Host: bogus.example.com" https://your-ip/ -k— expect rejection. - Confirm
curl -I https://canonical/returns no redirect and the expectedStrict-Transport-Securityheader.
Host-header hygiene is one of those fixes that costs twenty minutes and closes an entire class of duplicate-content and information-disclosure issues. Do it before you point a real domain at the box, and it never becomes a problem later.



