On Windows shared hosting and small Windows Server boxes, disk pressure shows up before CPU does. Sites fail with 500.19 or generic 500s, Web Deploy stops mid-sync, and SQL backups refuse to write—often because logs, Temporary ASP.NET Files, stdout captures, or user uploads quietly ate the volume. You do not need a cloud autoscaler to fix this; you need known write paths, retention, and a few IIS settings you can change from web.config or the host panel.

This is floor-level capacity work for ASP.NET Framework and ASP.NET Core 10 apps on IIS 10.0: where files land, what you can relocate under Full Trust, and how to stop a chatty logger from taking the site down overnight.

#Map every directory that grows

Before you delete anything, list the writers. On a typical Windows IIS site the usual suspects are site-local logs, ANCM stdout files, framework compile caches, and content your app creates at runtime.

  • IIS site logs (W3C): often under the account’s LogFiles folder or a path the host assigns—not always inside wwwroot.
  • ASP.NET Core stdout: enabled via web.config aspNetCore stdoutLogEnabled; defaults next to the app if stdoutLogFile is relative.
  • App file logs (Serilog, NLog, custom): whatever path you configured—App_Data\logs is common and easy to forget.
  • Temporary ASP.NET Files (Framework apps): machine-level compile cache under the .NET Framework folder; large sites and frequent bin changes grow it fast.
  • Uploads, exports, PDF scratch, image thumbs: folders your code creates under the site root or a virtual directory.

On shared Windows plans you rarely get a second data volume. Treat the site home directory as finite: prefer App_Data (or a host-provided data path) over scattering files beside controllers and wwwroot static assets.

#Turn stdout into a diagnostic switch, not a sink

stdout logging from the ASP.NET Core Module is excellent for startup failures and then dangerous if left on. Each worker process can keep writing; recycles create new files; nobody rotates them for you.

xml
<aspNetCore processPath=".\MyApp.exe"
            arguments=""
            stdoutLogEnabled="false"
            stdoutLogFile=".\logs\stdout"
            hostingModel="inprocess">
  <environmentVariables>
    <environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Production" />
  </environmentVariables>
</aspNetCore>

Keep stdoutLogEnabled false in normal production. Flip it true only while reproducing a cold-start or ANCM error, pull the files, then disable it again. Point stdoutLogFile at a logs folder under the site, create that folder in the deploy, and grant the app pool identity modify rights on that folder only—not on the whole tree.

#Bound application logs by path, size, and retention

Framework and Core apps both fail the same way when a file sink has no ceiling. Configure rolling files with a hard cap and a short retention window. Prefer writing under a dedicated folder so a host-side disk cleanup or your own script can target one path.

csharp
// Program.cs — ASP.NET Core 10 example
builder.Logging.ClearProviders();
builder.Host.UseSerilog((ctx, services, cfg) => cfg
    .ReadFrom.Configuration(ctx.Configuration)
    .WriteTo.File(
        path: Path.Combine(ctx.HostingEnvironment.ContentRootPath, "App_Data", "logs", "app-.log"),
        rollingInterval: RollingInterval.Day,
        retainedFileCountLimit: 14,
        fileSizeLimitBytes: 32 * 1024 * 1024,
        rollOnFileSizeLimit: true,
        shared: true));

Match that with a web.config or appsettings rule that never logs bodies, connection strings, or cookies at Information in Production. Verbose request logging belongs behind a temporary flag, not the default template. If you still run ASP.NET Framework, keep customErrors and health endpoints from dumping stack traces to disk on every 404 bot hit.

#Uploads, temp compile output, and recycle side effects

User content is the other silent filler. Enforce size limits at IIS and in the app so one multipart POST cannot pad the volume. requestLimits maxAllowedContentLength and ASP.NET Core MultipartBodyLengthLimit should agree; document the smaller number as the real ceiling.

xml
<system.webServer>
  <security>
    <requestFiltering>
      <requestLimits maxAllowedContentLength="20971520" />
    </requestFiltering>
  </security>
</system.webServer>

Store uploads outside wwwroot when the host allows a data directory, or under App_Data with explicit static-file opt-in so binaries are not directly browsable. Schedule deletion for incomplete chunks and expired exports. For Framework sites that thrash Temporary ASP.NET Files after every publish, reduce unnecessary bin churn, prefer precompiled views where you already do, and avoid touching web.config on every CI run unless the transform actually changed—each touch can recycle the pool and force fresh compile work.

App pool private memory and virtual memory limits do not free disk, but a crashing pool that restarts in a loop can multiply log and crash-dump growth. If the host exposes private memory limit settings, set a ceiling that matches real steady-state working set plus headroom, then fix the leak rather than raising the limit forever. On shared hosts, assume noisy neighbors and keep your own file growth deterministic.

#A monthly five-minute disk pass

  • Confirm stdout logging is off; delete old stdout_*.log files.
  • Check App_Data\logs (or your sink path) against retainedFileCountLimit and total folder size.
  • Purge staging uploads, report scratch, and package export folders older than your business window.
  • Review IIS site log retention with the host or your own task if you control the server.
  • After large publishes on Framework apps, note compile latency; repeated cold compiles can signal cache or permission issues worth fixing once.

Practical takeaway: treat disk like a production dependency. Disable perpetual stdout logging, roll application logs with size and count limits under one folder, align IIS and app upload ceilings, and keep generated content out of wwwroot. On Windows shared hosting and lean Windows Server boxes those habits prevent more outages than another round of micro-optimizing T-SQL—because a full volume stops every request at once.