Skip to content

Fix: ASP.NET 500 Internal Server Error

FixDevs · (Updated: )

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=Development and 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_*.log if stdoutLogEnabled="true" in web.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, or DllNotFoundException.
  • 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-runtimes on 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.cs that dumps builder.Configuration.AsEnumerable() to a log file. A misconfigured environment variable or Key Vault binding makes the config read null, 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 > Application

Pro Tip: Set ASPNETCORE_ENVIRONMENT=Development in 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: localhost works 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=true requires the app pool identity to have database access.
  • Connection string not loaded: Verify the name matches between appsettings.json and 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 connect

Fix 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. Endpoints

The 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 false

For self-contained deployment (includes .NET runtime):

dotnet publish -c Release -o ./publish --self-contained true -r win-x64

Check 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 libgdiplus

For 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.json is 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 run in 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.config aspNetCore module reference. A typo in processPath or arguments (.\MyApp.dll vs .\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 .dll name.

  • 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. Run dotnet dev-certs https --trust on 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 MissingMethodException at 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.App 8.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 as FileLoadException or TypeLoadException, both of which surface to the client as a generic 500.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles