Most production pain around “it works on my machine” for Windows-hosted .NET is not a missing NuGet package—it is app pool bitness. A 64-bit worker process will refuse to load a 32-bit native dependency; a 32-bit pool will quietly hit a hard address-space ceiling long before the server looks busy. Get this setting wrong and you chase phantom OutOfMemoryException, BadImageFormatException, or flaky OLEDB failures instead of shipping.
On Windows Server 2025 with IIS 10.0, new app pools default to 64-bit. That is usually correct for ASP.NET Core / .NET 10 and modern .NET Framework 4.8.x apps. The exceptions are still common on shared and reseller floors: legacy data providers, older reporting runtimes, and some ISAPI-era COM components. Treat bitness as an explicit ops decision, not a leftover checkbox.
#When 32-bit is still required
Stay 64-bit unless you have a concrete dependency that is 32-bit only. Typical triggers on hosted Windows sites:
- Microsoft.Jet.OLEDB.4.0 or older Access/Excel drivers (ACE 64-bit exists—prefer it when you can).
- Vendor native DLLs shipped only as x86 (payment, document, or device SDKs).
- In-process COM objects registered under Wow6432Node that your app activates directly.
- Very old third-party HttpModules or ISAPI filters that never shipped x64 builds.
If the failure mode is BadImageFormatException naming a .dll under bin or a system provider, bitness is the first check—before connection strings, before “recycle the pool,” before rewriting data access.
#How .NET Framework and .NET 10 behave
For .NET Framework on IIS, the w3wp.exe bitness is controlled solely by the app pool setting Enable 32-Bit Applications. AnyCPU managed assemblies load in whichever bitness the worker is. Mixed-mode or explicitly x86/x64 assemblies must match the worker or load fails at startup or first P/Invoke.
For ASP.NET Core / .NET 10 with the ASP.NET Core Module (in-process or out-of-process), the published rid and the app pool must agree. An win-x64 publish into a 32-bit pool will not start cleanly; an win-x86 publish into a 64-bit pool fails the same way. Prefer win-x64 (or portable + 64-bit pool) unless a native dependency forces x86. Do not “fix” a mismatch by toggling the pool after every deploy—lock the pool and the publish profile together in your release notes.
#Inspect and set pool bitness
On a Windows VPS or dedicated box you control, read and set the flag with IISAdministration / WebAdministration. On shared hosting the control panel usually exposes “Enable 32-bit” per site or per pool; if it is locked, open a ticket with the exact native module that requires x86 rather than guessing.
Import-Module WebAdministration
$pool = 'YourAppPool'
# $true = 32-bit worker (WOW64)
# $false = 64-bit worker (default on modern IIS)
(Get-ItemProperty IIS:\AppPools\$pool).enable32BitAppOnWin64
Set-ItemProperty IIS:\AppPools\$pool -Name enable32BitAppOnWin64 -Value $false
Restart-WebAppPool $pool
# Confirm the running worker after traffic hits the site
Get-Process w3wp -ErrorAction SilentlyContinue |
Select-Object Id, @{n='Bits';e={if ($_.Handles) { if ([System.Environment]::Is64BitProcess) {'check Task Manager / Process Explorer for *32'} } }},
Path
In Task Manager, a 32-bit w3wp shows as w3wp.exe *32 on 64-bit Windows. Process Explorer and the Modules view are clearer when you need to see which native DLLs actually loaded. After flipping bitness, recycle once and hit a warm-up URL; do not leave two sites that disagree on bitness in the same pool.
#Memory, capacity, and noisy-neighbor reality
A 32-bit worker is not “lighter” in a useful way for modern .NET. User-mode virtual address space tops out near 2–3 GB depending on large-address awareness, so large JSON buffers, image processing, or big EF materializations fail earlier with OOM even when the machine still has free RAM. A 64-bit worker can grow further; on shared hosts that is exactly why private memory limits and periodic recycle still matter—you want a hard cap so one site cannot pin the box.
Practical capacity notes for hosting-floor pools:
- Prefer 64-bit + a private memory limit you measured under load over 32-bit “so it stays small.”
- One app per pool when bitness, identity, or recycle schedule must differ.
- After moving 32→64-bit, re-test native providers and any file paths under SysWOW64 vs System32.
- Watch for duplicate assemblies: x86 and x64 copies of the same native DLL in bin are a deploy smell—ship one RID.
Full Trust on Windows shared hosting does not change pointer size. It affects CAS-era restrictions and what your code may call; bitness is still a process-level property. If a library requires both Full Trust and 32-bit, document both in the runbook so the next deploy does not “simplify” the pool back to 64-bit defaults.
#A short decision checklist
- No native/COM/OLEDB constraints → 64-bit pool, win-x64 (or AnyCPU) publish.
- Proven x86-only dependency → 32-bit pool, matching publish, isolated pool.
- BadImageFormatException on startup → align pool and RID before rewriting code.
- OOM under 2–3 GB working set on 32-bit → migrate dependency or move to 64-bit.
Takeaway: fix bitness as a paired setting—app pool Enable32BitAppOnWin64 and your publish RID—then keep each site in a pool that matches its dependencies. Default to 64-bit on IIS 10.0 / Windows Server 2025 for .NET 10 and current .NET Framework apps; switch to 32-bit only with a named DLL or provider as evidence. Record the choice next to the connection string and recycle schedule so the next deploy does not undo it. On a Windows host where the panel exposes the flag, set it once, warm the site, and confirm w3wp bitness under real traffic before you call the change done.
Comments
No comments yet