Sizing a Windows VPS by vCPU count is the most expensive mistake in this workload family. A 4-core box serving a cached ASP.NET Core site handles thousands of requests per minute, while the same 4-core box running SQL Server with a badly sized buffer pool queues on storage and delivers worse throughput than a 2-core machine with faster disks.
Three profiles cover most hosted Windows deployments: stateless web front ends on IIS, stateful instances on SQL Server, and interactive sessions on Remote Desktop Services. Size the VPS against whichever profile is most constrained, not an average of all three.
Profile one: IIS front ends are thread-count and memory bound
An IIS pool serving a .NET 8 app is limited by the CLR thread pool and process working set, not raw cores once you pass four. Watch \ASP.NET Applications(__Total)\Requests In Application Queue and Processor(_Total)\Processor Queue Length: a sustained queue above 2 per logical core adds latency no extra bandwidth fixes.
A typical ASP.NET Core MVC app stabilises between 150 and 300 MB, plus 60 to 120 MB for a large startup cache. Three app pools need about 1 GB above the OS baseline; on a 4 GB box running Server 2022 Desktop Experience that leaves under 1.5 GB, which is why Server Core delivers roughly 30 percent more effective capacity.
- Rough rule: 1 vCPU per 8 to 12 concurrent ASP.NET Core requests in flight, capped at 4 cores for a single site.
- Reserve 512 MB of RAM per app pool plus the OS baseline plus 20 percent headroom.
- Move IIS logs off the system volume: a busy site writes 3 to 10 GB per month of W3C logs.
- Set the app pool Private Memory Limit as an alert, not a recycle trigger, or you will mask a leak.
Profile two: SQL Server is memory-configured and storage-bound
SQL Server claims every byte unless told otherwise. Run EXEC sp_configure max server memory (MB), 4096; RECONFIGURE; where the value is total RAM minus the OS baseline minus 1 GB. On 2022 and 2025, memory broker changes make under-provisioning worse than it was on 2019.
Storage latency dominates everything else with databases. Check sys.dm_io_virtual_file_stats for average stall time; above 10 to 20 ms on data files means the disk is oversubscribed or tempdb is writing too often. Splitting tempdb only helps across separate virtual disks — eight files on one slow volume makes contention worse, not better.
A 2 vCPU instance handles around 50 to 150 batch requests per second while the buffer cache holds the hot pages. Past that point extra cores mostly measure storage wait, so buy RAM first, then NVMe, then cores.
Profile three: RDS sessions scale linearly with user count
RDS Session Host capacity is genuinely linear: each interactive session on Server 2022 with Office and a browser consumes 120 to 250 MB and adds CPU during logon. Twenty sessions on a 16 GB host is comfortable; forty is not, because the 9 a.m. logon storm is the real load test.
Watch \Terminal Services Session(*)\Working Set, logon duration in the TerminalServices-LocalSessionManager/Operational log, and how many sessions sit disconnected because users close the RDP window instead of signing out. Those hold memory and licences nobody uses, and per-user CALs dominate hardware cost anyway.
Pick the binding constraint in fifteen minutes
Run this in an elevated session on the candidate VPS after a week of real traffic:
$s = Get-Counter -Counter "\Processor(_Total)\Processor Queue Length","\Memory\Available MBytes","\Memory\Pages/sec","\PhysicalDisk(_Total)\Avg. Disk sec/Transfer" -SampleInterval 5 -MaxSamples 60; $s.CounterSamples | Group-Object Path | ForEach-Object { [pscustomobject]@{ Counter = $_.Name; Max = ($_.Group | Measure-Object CookedValue -Maximum).Maximum; Avg = ($_.Group | Measure-Object CookedValue -Average).Average } }
Read it in order: Avg Disk sec/Transfer above 0.02 means storage is the constraint and no CPU upgrade helps. Available MBytes that never rises above 10 percent after a restart plus an hour means memory is. Queue length above two per core while CPU sits under 50 percent is an application lock, not capacity.
The headroom rule and why 40 percent is the number
Size so a normal peak hour sits at 40 to 60 percent of the binding resource. That covers patching, VSS backup snapshots and ordinary tail events. A box at 85 percent on a Tuesday morning falls over on the first Patch Tuesday reboot cycle.
It is also the cheapest scaling strategy: 45 percent leaves one comfortable increment, usually RAM or a disk tier change, before a new plan is needed. At 80 percent every incident becomes a migration. Track it monthly and treat a rising baseline as a signal to plan.
Where the profiles conflict on one box
Running IIS and SQL Server on one 8 GB VPS is normal for small deployments, as long as max server memory is 4 to 5 GB and the app pool idle-timeout keeps working sets in check. RDS on the same host is what breaks: one user opening fifteen browser tabs evicts part of the SQL buffer cache.
The split that holds up in practice: IIS plus SQL on one instance, RDS or heavy build workloads on a separate box. Two modest instances fail independently, which beats one large instance losing everything at once. Our Windows VPS comparison lists which plans include the RAM headroom this needs. If you must consolidate, cap the app pool memory so the database has a guaranteed floor.
Common sizing mistakes worth avoiding
The first is buying cores to solve an IO problem. The second is trusting a provider’s “4 vCPU” without checking whether those are shared or dedicated: burstable credits exhaust during a logon storm and the box freezes rather than degrading. Ask for a sustained-steal figure and test with a stress run.
The third is skipping the restart: monitoring a box that has been up sixty days measures accumulated leaks as if they were load. Restart, wait an hour, then baseline properly. Pair the profile with the deployment patterns in our guide to running IIS and SQL Server on a Windows VPS, and revisit the sizing decision every time a new app pool or database is added.
A worked example
An 8 GB, 4 vCPU VPS on Server 2022 with one ASP.NET Core 8 site and SQL Server 2022 Express: OS baseline 2.1 GB, SQL capped at 3 GB, app pool 0.4 GB, leaving roughly 2 GB slack. That sustains about 80 requests per second against a 3 GB database.
Add RDS for five users and you gain 1.2 GB, still inside the slack. Add twenty and you exceed the box during any logon period. The profile gives you that answer before users complain.



