Fix: ASP.NET 500 Internal Server Error
Part of: C# and .NET Errors
Quick Answer
Fix ASP.NET 500 Internal Server Error by enabling developer exception pages, fixing DI registration, connection strings, and middleware configuration.
The Error
Your ASP.NET application returns a blank page or a generic error:
HTTP Error 500 - Internal Server Error
The server encountered an unexpected condition that prevented it from fulfilling the request.Or in the browser:
An unhandled exception occurred while processing the request.Without detailed error information, debugging feels impossible.
Why This Happens
A 500 response means the host accepted the request and the application threw an unhandled exception while processing it (or failed before the request ever reached your code). The status code is intentionally vague — production hosts hide the real exception from users to avoid leaking stack traces, connection strings, and internal paths. The actual cause sits in the logs, but where those logs live depends entirely on how you deploy: console output for Kestrel-only, stdout files for the ASP.NET Core Module under IIS, the Windows Event Log for in-process IIS hosting, the systemd journal for Linux deployments, and Application Insights or CloudWatch for cloud-hosted apps.
Within ASP.NET Core specifically, the most common root cause isn’t a per-request bug — it’s a startup failure. If Program.cs throws during builder.Build() or while the host is starting Kestrel, every request returns 500 because the pipeline never reached the point where it could handle one. Startup failures come from missing DI registrations, malformed appsettings.json, broken configuration providers (Key Vault, environment variables), or a database that can’t be reached during the host’s startup health check. The symptom is identical to a per-request crash, but the fix is completely different.
A second common class of failure is hosting-model mismatch. ASP.NET Core can run as a standalone Kestrel process, behind IIS in-process (loaded into w3wp.exe), behind IIS out-of-process (with IIS as a reverse proxy to Kestrel), or behind Nginx/Apache on Linux. Each has different config files, different log locations, and different failure modes. A web.config with hostingModel="InProcess" running on a server where the ASP.NET Core Hosting Bundle isn’t installed produces a 500.30 on startup and zero useful information unless you know to look at stdoutLog or the Windows Event Log.
Diagnostic Timeline
Use this order. Most ASP.NET 500 errors resolve at minute 3.
- Minute 0 — Check the exact status code in the browser dev tools, not just “500.” ASP.NET Core distinguishes 500.0 (generic app crash), 500.30 (startup failure), 500.31 (failed to load .NET), 500.32 (failed to load DLL), 500.34 (mixed hosting model), and 500.35 (multiple in-process apps). The sub-status code points at the cause before you read a single log line.
- Minute 1 — Set
ASPNETCORE_ENVIRONMENT=Developmentand reproduce. The developer exception page shows the full stack trace, the failing middleware, and the offending route. If the page still doesn’t render, the failure is happening before the exception-handling middleware was registered — that’s a startup error, not a request error. - Minute 2 — Locate the log. For Kestrel: console output (
dotnet run). For IIS:<contentRoot>\logs\stdout_*.logifstdoutLogEnabled="true"inweb.config. For Windows Event Log: filter by source “IIS AspNetCore Module V2.” For Linux systemd:journalctl -u myapp --since "5 minutes ago". For Docker:docker logs <container>. - Minute 3 — Read the first exception in the log, not the last. ASP.NET Core often throws a cascade — the real cause is the first stack trace; everything after is the host trying to recover. Look for
System.InvalidOperationException,SqlException,FileNotFoundException, orDllNotFoundException. - Minute 4 — Test the app outside its host. Stop IIS, open a terminal in the deployment directory, and run
dotnet MyApp.dll. Kestrel starts directly and prints any startup error to the console. This bypasses the IIS module entirely and isolates whether the bug is in your app or in the hosting layer. - Minute 5 — Confirm the runtime is installed.
dotnet --list-runtimeson the server. If you targeted .NET 8 but the server has only .NET 6, you get 500.31. The Hosting Bundle from Microsoft installs both the runtime and the IIS module; partial installs are common after a server image change. - Minute 6 — Check the configuration providers loaded at startup. Add temporary code to
Program.csthat dumpsbuilder.Configuration.AsEnumerable()to a log file. A misconfigured environment variable or Key Vault binding makes the config readnull, which then crashes the first service that needs it. Configuration errors look identical to DI errors at the surface.
Fix 1: Enable the Developer Exception Page
In development, ASP.NET shows detailed errors by default. If you’re not seeing them, check your Program.cs:
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}If you’re in production and need to see the error temporarily:
// Temporarily enable in production (remove after debugging!)
app.UseDeveloperExceptionPage();Or check the logs. Configure logging in appsettings.json:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}View logs:
# IIS
%SystemDrive%\inetpub\logs\LogFiles
# Kestrel (console output)
dotnet run
# Windows Event Viewer
eventvwr.msc > ApplicationPro Tip: Set
ASPNETCORE_ENVIRONMENT=Developmentin your environment variables when debugging on a remote server. This enables the developer exception page without code changes. Remove it when done.
Fix 2: Fix Connection Strings
Database connection failures are the most common cause of 500 errors in ASP.NET:
// appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=mydb;User Id=sa;Password=pass;TrustServerCertificate=True"
}
}Common issues:
- Wrong server name:
localhostworks in development but not in production. Use the actual server hostname or IP. - Missing TrustServerCertificate: SQL Server 2022+ requires explicit certificate trust.
- Windows Authentication vs SQL Authentication: Using
Integrated Security=truerequires the app pool identity to have database access. - Connection string not loaded: Verify the name matches between
appsettings.jsonand your code.
// Verify the connection string is loaded
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
if (string.IsNullOrEmpty(connectionString))
{
throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
}
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(connectionString));Test the connection independently:
using var connection = new SqlConnection(connectionString);
connection.Open(); // Throws if it can't connectFix 3: Fix Dependency Injection Registration
Missing DI registrations cause InvalidOperationException at runtime:
System.InvalidOperationException: Unable to resolve service for type 'IMyService'
while attempting to activate 'MyController'.Register all services in Program.cs:
// Register services
builder.Services.AddScoped<IMyService, MyService>();
builder.Services.AddSingleton<ICacheService, RedisCacheService>();
builder.Services.AddTransient<IEmailSender, SmtpEmailSender>();Common mistakes:
// Forgot to register the service
// builder.Services.AddScoped<IOrderService, OrderService>(); // Missing!
// Registered with wrong lifetime
builder.Services.AddSingleton<IDbContext, AppDbContext>(); // Singleton holding a scoped DbContext
// Registered interface but injecting concrete type
builder.Services.AddScoped<IMyService, MyService>();
// Controller asks for MyService instead of IMyService
public MyController(MyService service) { } // Fails!Common Mistake: Registering a service as Singleton that depends on a Scoped service (like DbContext). This causes a captive dependency — the Singleton holds onto a disposed DbContext. Use AddScoped for services that depend on scoped resources.
Fix 4: Fix Middleware Order
ASP.NET middleware runs in the order it’s registered. Wrong order causes 500 errors:
var app = builder.Build();
// Correct order
app.UseExceptionHandler("/Error"); // 1. Catch exceptions first
app.UseHsts(); // 2. Security headers
app.UseHttpsRedirection(); // 3. HTTPS redirect
app.UseStaticFiles(); // 4. Serve static files
app.UseRouting(); // 5. Route matching
app.UseCors(); // 6. CORS (after routing, before auth)
app.UseAuthentication(); // 7. Authentication
app.UseAuthorization(); // 8. Authorization
app.MapControllers(); // 9. EndpointsThe most common ordering mistake is putting UseAuthorization() before UseRouting(), or UseCors() after UseAuthorization().
If UseExceptionHandler isn’t first, exceptions in early middleware aren’t caught and produce raw 500 errors.
Fix 5: Fix CORS Configuration
Misconfigured CORS causes 500 errors when the middleware throws instead of returning proper headers:
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(policy =>
{
policy.WithOrigins("https://frontend.example.com")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
// Apply CORS middleware
app.UseCors(); // Must come after UseRouting() and before UseAuthorization()Common CORS errors that manifest as 500:
// This throws at startup — can't use AllowAnyOrigin with AllowCredentials
policy.AllowAnyOrigin()
.AllowCredentials(); // InvalidOperationException
// Fix: specify exact origins when using credentials
policy.WithOrigins("https://frontend.example.com")
.AllowCredentials();Fix 6: Fix Missing Deployment Dependencies
The application works locally but throws 500 in production because dependencies are missing:
# Publish with all dependencies
dotnet publish -c Release -o ./publish --self-contained falseFor self-contained deployment (includes .NET runtime):
dotnet publish -c Release -o ./publish --self-contained true -r win-x64Check for missing native dependencies:
System.DllNotFoundException: Unable to load shared library 'libgdiplus'Install missing packages on the server:
# Ubuntu/Debian
sudo apt install libgdiplus libc6-dev
# CentOS/RHEL
sudo yum install libgdiplusFor IIS deployments, ensure the ASP.NET Core Hosting Bundle is installed on the server.
Fix 7: Fix Kestrel and IIS Configuration
Different hosting modes have different failure points:
Kestrel (direct):
builder.WebHost.ConfigureKestrel(options =>
{
options.ListenAnyIP(5000);
options.Limits.MaxRequestBodySize = 100 * 1024 * 1024; // 100MB
});IIS (reverse proxy):
In web.config:
<aspNetCore processPath="dotnet" arguments=".\MyApp.dll"
stdoutLogEnabled="true"
stdoutLogFile=".\logs\stdout"
hostingModel="InProcess">
</aspNetCore>Enable stdout logging to see errors:
stdoutLogEnabled="true"
stdoutLogFile=".\logs\stdout"Create the logs folder manually — IIS won’t create it automatically.
Common IIS issues:
- App pool not set to “No Managed Code”: ASP.NET Core doesn’t use the .NET CLR integration in IIS. Set the app pool to “No Managed Code.”
- Wrong bitness: If your app targets x64, the app pool must enable 32-bit applications = false.
- Missing write permissions: The app pool identity needs write access to the content directory for logging.
Fix 8: Configure Proper Logging
Without proper logging, you’re debugging blind. Configure structured logging:
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
builder.Logging.AddDebug();
// Add file logging with Serilog
builder.Host.UseSerilog((context, config) =>
{
config.ReadFrom.Configuration(context.Configuration)
.WriteTo.File("logs/app-.log", rollingInterval: RollingInterval.Day)
.WriteTo.Console();
});Add global exception handling:
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
var exception = context.Features.Get<IExceptionHandlerFeature>()?.Error;
var logger = context.RequestServices.GetRequiredService<ILogger<Program>>();
logger.LogError(exception, "Unhandled exception");
context.Response.StatusCode = 500;
await context.Response.WriteAsJsonAsync(new { error = "Internal server error" });
});
});For API projects, add a global exception filter:
builder.Services.AddControllers(options =>
{
options.Filters.Add<GlobalExceptionFilter>();
});Still Not Working?
Check for null reference crashes in controller actions. A 500 with no log message often comes from an action that hit a null dereference before it could log anything. Add a try/catch wrapper temporarily to capture the exception.
Verify
appsettings.jsonis copied to output. Check that the file’s Build Action is “Content” and Copy to Output Directory is “Copy if newer.”Check Windows Event Log for request cancellations. Application errors that crash the process appear in Event Viewer > Application. Filter by source “ASP.NET Core.”
Test with
dotnet runin the deployment directory. This reveals startup errors that IIS might swallow.Check for port conflicts. If another process is using the same port, Kestrel fails to start.
Review the health check endpoint. Add
app.MapHealthChecks("/health")and test it to isolate whether the issue is app-wide or route-specific.Inspect the
web.configaspNetCoremodule reference. A typo inprocessPathorarguments(.\MyApp.dllvs.\MyApp\MyApp.dll) means IIS launches the wrong file or no file at all. The error is 500.30 with “Failed to start application.” Confirm the path matches the actual published.dllname.Check the HTTPS dev certificate on first-run. A fresh deploy of a Development-environment app that calls
app.UseHttpsRedirection()without a trusted cert can throw at startup. Rundotnet dev-certs https --truston the dev box, or set the environment to Production where the redirect is disabled by default.Verify the cannot-convert-type errors caught at build time aren’t returning. A hot-reload or partial deploy can leave older DLLs alongside newer ones. The runtime picks the wrong overload and throws
MissingMethodExceptionat the first call. Clean the publish directory before redeploying.Confirm the App Service / container has read access to
appsettings.{Environment}.json. Azure App Service deployments occasionally lose ACLs on transformed config files after slot swaps. The app starts, the JSON file is unreadable, and configuration silently falls back to defaults that don’t include your connection strings.Look for assembly-binding mismatches. A NuGet package built against
Microsoft.AspNetCore.App8.0.0 will fail at first use on a host with only 8.0.4 if assembly-binding redirects aren’t in place. The error appears asFileLoadExceptionorTypeLoadException, both of which surface to the client as a generic 500.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: C# async deadlock — Task.Result and .Wait() hanging forever
How to fix the C# async/await deadlock caused by Task.Result and .Wait() blocking the synchronization context in ASP.NET, WPF, WinForms, and library code.
Fix: C# Cannot implicitly convert type 'X' to 'Y'
How to fix C# cannot implicitly convert type error caused by type mismatches, nullable types, async return values, LINQ result types, and generic constraints.
Fix: C# TaskCanceledException: A task was canceled
How to fix C# TaskCanceledException A task was canceled caused by HttpClient timeouts, CancellationToken, request cancellation, and Task.WhenAll failures.
Fix: joblib Not Working — Parallel Backends, Memory Cache, and Pickling Errors
How to fix joblib errors — Parallel n_jobs slower than expected, Memory cache miss, backend loky vs threading vs multiprocessing, pickling lambda not supported, dump load file size, and pytest interference.