A single IIS server eventually hits a ceiling. The two ways past it are scale up (a larger VPS) or scale out (several nodes behind a load balancer). On Windows Server the native scale-out answer is an IIS Web Farm fronted by Application Request Routing (ARR) with URL Rewrite. This walkthrough stands up a two-node farm on separate Windows VPS instances, configures ARR as a reverse proxy, and adds health probes so a failed node drops out without manual intervention. Provisioning the nodes is the first step — the comparison of Windows VPS providers lists plans with matching regions, which matters when nodes must share a low-latency network. When one box is no longer enough, that page is the starting point for choosing the front-end and backend hosts.
1. Architecture and Prerequisites
- Front end — one small Windows Server with IIS, ARR, and URL Rewrite. Terminates TLS, balances, and runs health checks.
- Backends — two or more IIS nodes serving identical content.
- Shared state — sessions and content must be shared or stateless, or users bounce between nodes and lose carts and logins.
Place all nodes in the same region so intra-farm latency stays under 2 ms. Cross-region backends introduce TLS renegotiation and session lag that health probes mask but users feel.
2. Install ARR and URL Rewrite
Install the IIS role, then add Application Request Routing 3.0 and URL Rewrite 2.1 from Microsoft. Verify both modules loaded:
Get-WebGlobalModule | Where-Object Name -like '*ARR*'
The output should list ApplicationRequestRouting and its provider modules. If nothing returns, the install did not register — reinstall with the Web Platform Installer (WebPI) or the MSI directly.
3. Create the Server Farm and Probes
In IIS Manager, open Server Farms > Create Server Farm. Add both backend addresses. On the Health Test tab, set a URL the app exposes cheaply (a static /health.txt is ideal — do not probe a database-backed endpoint), an interval of 10 seconds, and a failure threshold of 3. That means a bad node is removed within 30 seconds.
| Probe setting | Recommended value | Effect |
|---|---|---|
| URL | /health.txt (static) | Cheap, avoids DB load per probe |
| Interval | 10 s | Detects failure within 30 s at threshold 3 |
| Timeout | 5 s | Rejects slow nodes before they cascade |
| Failure threshold | 3 | Prevents flapping on a single dropped packet |
4. Load Balancing Algorithm and Affinity
ARR defaults to weighted round-robin. For stateless ASP.NET Core apps that is correct. For classic ASP.NET sessions backed by InProc, either move to StateServer/SQLServer or enable client affinity — but affinity undermines the point of a farm, so fix session storage instead. Set a sticky cookie only for short-lived migration windows.
5. X-Forwarded Headers
Behind ARR, every request appears to originate from the proxy. Rewrite the client identity headers so logs and rate-limiting see the real IP:
- Enable
X-Forwarded-ForandX-Forwarded-Protoin the farm’s proxy settings. - In ASP.NET Core, add ForwardedHeaders middleware and clear
KnownNetworks/KnownProxiesdefaults to trust only your front-end. - In classic ASP.NET, read the header in
Application_BeginRequestand updateRequest.UserHostAddresscarefully — do not trust it from arbitrary clients.
6. Verifying Failover
Test rather than trust. Stop the Application Pool on one backend, then poll the farm URL and watch which node answers. A correctly configured farm returns 200 throughout, with the dead node showing Unhealthy in the ARR farm view within the threshold window. If requests still land on the dead node, the probe URL is wrong or the failure threshold is too high.
7. Handling Sessions on a Web Farm
Round-robin balancing exposes any session state held in-process. Three options, in order of preference:
- Stateless tokens — for ASP.NET Core, store state in a JWT or a signed cookie and skip server sessions entirely.
- Distributed cache — Redis or a SQL-backed session store shared by all nodes; adds a dependency but scales cleanly.
- Client affinity — sticky sessions via the ARR affinity cookie. Simplest, but a node failure logs out its users and uneven traffic follows.
For file uploads, do not write to local disk on one node — the next request may hit another. Use a shared SMB share, Azure Blob, or object storage consistent across nodes.
Common Failure Modes
| Symptom | Likely cause | Check |
|---|---|---|
| 502 from the front end | Backend unreachable or probe says down | Farm health view, backend firewall |
| Node flapping | Probe timeout too tight | Raise timeout to 5 s, threshold to 3 |
| Random logouts | InProc session state | Move to distributed cache |
| Wrong client IP in logs | Forwarded headers not enabled | X-Forwarded-For settings |
| Stale content on one node | Deploy missed a node | Deployment script targets all nodes |
Cost vs Complexity
An ARR farm reuses the IIS skills and licensing you already have, which makes it the cheapest scale-out route on Windows. The trade-off is that ARR itself is a single point of failure until you add a second front end or a DNS-level failover. Size the backends for peak-single-node load so a surviving node can carry the traffic during a failure — with the current pricing laid out on the Windows VPS plan comparison, that headroom is a line-item decision, not a guess.

