skip to content

An application hosted on IIS gets Access Denied both when writing to a local folder and when reading a UNC file share, though the same code works when you run it interactively as yourself. Which identity is the request actually running under, and how do you grant each of those accesses correctly?

level: seniorimportance: should knowfreq 54%

answer

  1. it is not running as you
  2. IIS APPPOOL\<PoolName>, a virtual account
  3. local principal, no network identity
  4. off-box it goes as the computer account
  5. static files may be checked as IUSR

basics

~20 s

By default the code runs as the application pool's virtual account, named IIS APPPOOL<PoolName>. Grant local folder access to that name directly. It has no network identity, so a UNC share must instead be granted to the server's computer account — or the pool switched to a domain account.

solid answer

~50 s

Since IIS 7.5 an application pool's default `identityType` is `ApplicationPoolIdentity`, a virtual account created per pool and referred to as `IIS APPPOOL\ContosoPool`. Locally it is a real security principal: `icacls` accepts that name, and the account is also a member of `IIS_IUSRS`, which is how the framework grants the baseline rights every pool needs. Off the machine it has no identity at all, so any outbound SMB or integrated authentication leaves as the computer account — `DOMAIN\SERVER$` — which is what you grant on the share, and the same is true of the built-in `NetworkService`. If the resource cannot be granted to a machine account, the IIS-side change is to set the pool identity to a specific user. One more twist: for static files the check may be made as the anonymous user `IUSR`, not the pool identity, unless anonymous authentication is configured to use the application pool identity.

go deeper

for a junior

Know that IIS does not run your code as you: by default it runs as the application pool's own account, written IIS APPPOOL followed by the pool name, and file permissions must name that account.

for a middle

Explain the difference between the pool identity, the anonymous IUSR account and an impersonated caller, and be able to grant the right one with icacls rather than guessing.

for a senior

Demonstrate that you know the virtual account is local-only, so off-box access authenticates as the computer account, and that you diagnose from the 401.3 substatus rather than escalating privileges until it works.

for a principal

Own the identity model across a hosted fleet — one account per pool as the isolation boundary, what may ever be granted to IIS_IUSRS or a machine account, and how deployment keeps ACLs in step with pool lifecycle.

## Three identities can be in play When an IIS request touches the file system, the access check can be made under any of three principals, and diagnosing an Access Denied starts with working out which one. **The application pool identity.** Set on the pool, not the site, under `processModel/identityType`. Since IIS 7.5 the default is `ApplicationPoolIdentity`: a virtual account synthesised per pool with the name `IIS APPPOOL\<PoolName>`. Managed code that opens a file with no impersonation runs as this account. **The anonymous user.** For anonymous requests IIS by default impersonates `IUSR`, a built-in low-privilege account, before handing over — including for static content served by the native file handler. The setting lives in the application's authentication configuration; leaving `userName` empty means "use the application pool identity" instead: ```xml <system.webServer> <security><authentication> <anonymousAuthentication enabled="true" userName="" /> </authentication></security> </system.webServer> ``` This single empty attribute is the difference between granting your folder to `IIS APPPOOL\ContosoPool` and having it work, or granting it and watching it still fail. **The authenticated caller.** With Windows authentication and impersonation enabled, the request runs as the end user, and file access succeeds or fails per user. That is why "it works for me and not for them" appears. IIS records which one failed: an access check refused by an ACL is logged with HTTP substatus `401.3`, distinct from `401.1` for bad credentials and `401.2` for a server configuration problem. ## Granting the local folder The virtual account is a genuine principal on that machine and can be typed straight into `icacls` or the security tab, even though it will not appear in a directory search: ``` icacls "C:\inetpub\apps\contoso\uploads" /grant "IIS APPPOOL\ContosoPool:(OI)(CI)(M)" ``` The name is bound to the pool name; rename the pool and the SID changes, silently invalidating the ACE. That is a real production trap when someone "tidies up" pool names. Every pool identity is also a member of the local group `IIS_IUSRS`, which already carries the rights needed to read the application's own content directory. Granting a shared folder to `IIS_IUSRS` is a shortcut that works, but it grants every pool on the machine, which defeats the isolation the per-pool account exists to provide. ## Why the UNC share is different A virtual account exists only on the local machine; it has no representation anywhere else. When a process running as `IIS APPPOOL\ContosoPool` opens `\\fileserver\reports`, the SMB authentication does not use that account — the outbound connection authenticates as the **computer account**, written `DOMAIN\SERVER$`. The same is true of `NetworkService`. So the fix is either to grant the share and its NTFS permissions to that computer account, or to give the pool an identity that means something on the network. If a machine account is unacceptable, the IIS-side change is to set `identityType` to a specific user and supply credentials; a related option is `virtualDirectory`'s own `userName`/`password`, which makes IIS connect to a UNC-backed virtual directory with fixed credentials regardless of the pool identity. Provisioning that account, and what it is allowed to do in the directory, is a Windows administration question rather than an IIS one — but recognising the boundary, and knowing that the answer is not "grant Everyone", is what the question is testing. ## Isolation, and why this is worth caring about The reason `ApplicationPoolIdentity` is the default is blast radius. Before it, pools commonly ran as `NetworkService`, so every application on the server shared one identity and therefore one set of file and network permissions; a compromise of the least important site inherited the access of the most important one. A per-pool virtual account means the ACL is the isolation boundary and it lines up exactly with the process boundary. That only holds if you keep it. Setting a pool to `LocalSystem` because "it fixes the permissions" hands a web-facing worker process full machine authority and is the single worst configuration change available in IIS Manager. If a request keeps failing, the answer is to find which of the three identities is being checked and grant that one the minimum it needs. ## Two secondary effects worth knowing `loadUserProfile` on the pool, enabled by default for the application pool identity, gives the worker a real user profile — which is where per-user temporary files and DPAPI-protected data live. Turning it off saves a little memory and breaks anything that protects secrets with the user key or writes to the user temp directory. And because the identity is the pool's, moving an application to a different pool changes what it can reach, without any code or ACL change. Deployment scripts that recreate pools should recreate the ACEs alongside them.

  • Why does granting the folder to IIS_IUSRS work but is usually the wrong answer?
    Every application pool identity is a member of `IIS_IUSRS`, so granting the group does unblock the application. It also grants every other pool on that server, which erases the per-pool isolation that the virtual account exists to provide. Grant `IIS APPPOOL\<PoolName>` instead, and keep `IIS_IUSRS` for rights that genuinely belong to all hosted applications.
  • The application still gets Access Denied on a static file after you granted IIS APPPOOL\ContosoPool. What would you check next?
    Whether the access check is being made as `IUSR` rather than the pool identity. Anonymous authentication impersonates `IUSR` by default, and the native static-file handler serves under that token. Either grant `IUSR`, or set the anonymous authentication user name to empty so IIS uses the application pool identity — then a single ACE covers both paths. IIS logs the refusal as substatus 401.3.
  • Someone renames an application pool and file access starts failing. Why?
    The virtual account's name and SID derive from the pool name, so `IIS APPPOOL\OldName` and `IIS APPPOOL\NewName` are different principals. The existing ACEs still reference the old SID, which now resolves to nothing, and the running worker matches none of them. Re-apply the ACLs under the new name — and treat pool renames as a change that requires it.

saying these in an interview costs you the question

  • Says the application runs as the logged-in administrator
  • Fixes it by setting the pool identity to LocalSystem
  • Expects IIS APPPOOL accounts to work across machines
  • Ignores IUSR when anonymous authentication is enabled
  • Grants Everyone rather than finding the real principal

context