Skip to content

Fix: ASP.NET 500 Internal Server Error

FixDevs ·

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 error means the server threw an unhandled exception. In production, ASP.NET hides the details for security. The exception could be anything: a null reference, a database connection failure, a missing dependency injection registration, or a middleware misconfiguration.

The first step is always to see the actual exception. Everything else follows from that.

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 async deadlocks. Calling .Result or .Wait() on async methods can deadlock the request thread, causing a timeout that results in 500.

  • 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. 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.

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