IIS 500 Internal Server Error After web.config Change
A step-by-step guide to diagnosing and fixing IIS 500 errors after editing web.config, using detailed errors, Failed Request Tracing, and logs to pinpoint the broken setting.
Rao Aadil, India 7 min read
Confirm the symptom: it’s a web.config problem
A 500 after a web.config edit usually means the change introduced a configuration error. Confirm the failure is consistent and not transient: request the site with a browser and with a command-line client. In PowerShell, use Invoke-WebRequest or curl.exe (not the curl alias, which may be a different command on Windows):
Invoke-WebRequest -Uri http://localhost -UseBasicParsing
curl.exe -I http://localhost
If you also manage an nginx site on Linux, the equivalent is curl -I http://localhost. For an IIS site, the content root is normally under C:\inetpub\wwwroot\<site>, while an nginx server block might live in /etc/nginx/sites-available/ on Linux or C:\nginx\conf\ on Windows.
If the site returns HTTP 500 on every request, rule out other causes by restoring the previous web.config and requesting again. On Windows:
Copy-Item C:\inetpub\wwwroot\site\web.config.backup C:\inetpub\wwwroot\site\web.config -Force
The Linux equivalent for an nginx config is cp /etc/nginx/sites-available/site.conf.backup /etc/nginx/sites-available/site.conf. If reverting makes the 500 disappear, the web.config change is responsible.
A generic 500 hides the real error. IIS often returns a sub-status code that narrows it down. 500.19 means the configuration data itself is invalid (malformed XML, a locked section, or an invalid attribute); 500.21 indicates a module referenced in <modules> is missing or not installed; 500.22 points to an ASP.NET module invalid for the current managed pipeline mode. The detailed sub-status is visible in the response body when detailed errors are enabled, or in the IIS logs.
Test configs before they go live, read the right log, and reload safely, on nginx, IIS or Tomcat.
“the site is throwing 502, it was fine yesterday”
It reads the real state of the server before it says anything, shows you the exact command, and waits for your approval. Windows and Linux, over the SSH access you already have.
Find the cause: which web.config setting broke the site
The fastest way to see the actual error is to enable detailed error messages temporarily. Edit the site's web.config and set the error mode:
<configuration>
<system.webServer>
<httpErrors errorMode="Detailed" />
</system.webServer>
</configuration>
IIS Manager does the same: open the site, double-click Error Pages, click Edit Feature Settings, and choose Detailed errors. This shows the module and line number to anyone who can reach the site, so turn it off as soon as you have what you need. In production, enable detailed errors only for a local test or a short window with restricted access.
Failed Request Tracing (FREB) gives a full trace of the request pipeline—the most reliable way to identify the exact module and configuration line. In IIS Manager, select the site, open Failed Request Tracing Rules, and add a rule for status code 500. Trace all content, or at least the path that fails, and include the WWW Server, ASP, and ASP.NET providers. Trigger the request again; the newest trace log lands under %SystemDrive%\inetpub\logs\FailedReqLogFiles\W3SVC*. Open it. The trace shows each module that ran, and the module that fired the 500 includes an error description, a configuration line number, and often the exact property that is invalid.
IIS logs and Windows Event Viewer provide the same error without a full trace. Request logs are under %SystemDrive%\inetpub\logs\LogFiles\W3SVC*; search for the failed request by status code:
Get-ChildItem C:\inetpub\logs\LogFiles\W3SVC1\*.log | Select-String -Pattern " 500 "
The Event Viewer entry is under Windows Logs > Application, usually with a source of "IIS-W3SVC-WP" or "Microsoft-Windows-IIS-W3SVC". The event message often repeats the 500.19 sub-status and the configuration error.
Use appcmd or PowerShell to see the effective configuration and spot locked sections or invalid values:
appcmd list config "SiteName/"
Get-WebConfiguration -PSPath 'IIS:\Sites\SiteName' -Filter 'system.webServer/*'
Look for a section that failed to load or a property with an invalid value. If the error message mentions a locked section, compare the site's web.config with the same section in applicationHost.config or machine.config. System-level files often set overrideModeDefault="Deny" for sensitive sections such as system.webServer/modules.
Log-reading tools shorten this step: they find and tail the IIS log or event log entry for the failing request instead of forcing a manual search through several W3SVC log files. If you also manage nginx, the same approach applies: check /var/log/nginx/error.log on Linux or C:\nginx\logs\error.log on Windows, and use nginx -t to validate the nginx config before reloading.
Fix it: correct the web.config and restore the site
Open web.config and find the line the error from FREB or Event Viewer reports. Common causes of a 500 after an edit include:
- Malformed XML: a missing closing tag, an unescaped
&, or a stray character. - Invalid attribute values: a numeric setting that is out of range, or a path that does not exist.
- Missing modules or handlers: a
<modules>or<handlers>entry that refers to a DLL not installed on the server. - Duplicate keys in
<appSettings>,<connectionStrings>, or other collections that do not allow duplicates. - Locked sections: editing
system.webServer/modulesor another section that is locked inapplicationHost.config. - Invalid file paths: a rewrite rule, log directory, or virtual directory that points to a nonexistent location.
Correct the specific setting or remove the change. If the error is in a child web.config but the site root loads, the problem is often a configuration inheritance conflict: a setting in the parent site or in applicationHost.config overrides or locks the child. Use appcmd list config "SiteName/childApp" to see the merged configuration and identify where the conflicting value comes from.
Test again with Invoke-WebRequest or curl.exe. If the site still returns 500, go back to the error discovery step. Once the site responds, restart the IIS app pool to clear cached state:
Restart-WebAppPool -Name "YourAppPool"
If you manage nginx on the same server or a separate one, validate and reload nginx instead:
nginx -t && nginx -s reload
On Windows nginx, use the same command if nginx.exe is in your PATH, or the full path C:\nginx\nginx.exe -t && C:\nginx\nginx.exe -s reload. Management tools that run these operations through wrapper scripts remove the risk of a raw shell mistake and ensure the reload only happens after a successful test.
Keep a copy of the previous web.config before each change, and use version control. If a fix is not obvious, revert to the last known good version and apply changes incrementally, testing after each one.
Prevent it: validate web.config before it goes live
XML syntax errors are the easiest to catch before deployment. In PowerShell, use .NET's XML parser:
try {
[xml](Get-Content C:\inetpub\wwwroot\site\web.config) | Out-Null
Write-Host "web.config is well-formed XML"
} catch {
Write-Error "Invalid XML: $_"
}
On Linux, use xmllint for an XML-based config file such as a web.config for a cross-platform app, or an nginx config (though nginx uses nginx -t for its own syntax):
xmllint --noout /path/to/web.config
IIS has no built-in pre-flight validation command like nginx -t. The only reliable pre-deployment checks are XML well-formedness and a staging environment that mimics production. Use IIS Manager's Configuration Editor to test changes on a staging site before applying them to production.
If you also manage nginx, a config validation tool runs nginx -t before any reload, catching syntax errors before they take effect. The same principle applies to IIS: validate the XML, review the diff, and deploy from source control with a rollback path.
General prevention: keep web.config in source control, review changes with a diff before applying, and use a deployment process that can roll back quickly. Test-Path verifies that any file paths in the config actually exist on the server before deployment:
Test-Path C:\inetpub\wwwroot\site\bin\SomeModule.dll
The Linux equivalent is test -f /path/to/file.