Skip to main content

Command Palette

Search for a command to run...

ASP.NET Core Web API Returns 404 on Valid Endpoints: Causes and Fixes

Updated
โ€ข10 min read
ASP.NET Core Web API Returns 404 on Valid Endpoints: Causes and Fixes

Getting a 404 response from an ASP.NET Core Web API endpoint you know exists is one of the most confusing experiences in .NET development. The route is registered, the controller is written, the application starts without error โ€” and yet every request comes back with 404 Not Found. The frustrating part is that there is no exception, no stack trace, and no obvious pointer to what went wrong. If you want to go beyond the causes below and see how routing, middleware ordering, and error handling fit together in a complete production API, the full implementation with annotated source code is on Patreon โ€” everything wired together the way enterprise teams actually build it.

Understanding REST API design and routing correctly from the start saves hours of debugging like this. Chapter 2 of the Zero to Production course walks through HTTP verb semantics, route naming, and API versioning inside a real production codebase โ€” so the context of when a 404 happens and why is always clear.

ASP.NET Core Web API: Zero to Production

This article covers every common cause of ASP.NET Core Web API returning 404 on a valid endpoint โ€” middleware ordering, missing registrations, route attribute mismatches, and more โ€” with a clear fix for each.


What Does a 404 Actually Mean in This Context?

When ASP.NET Core returns 404 on a known endpoint, it means the routing system did not find a match for the incoming request. The request reached the server, the pipeline executed, but no endpoint handler was selected. This is different from a resource-not-found 404 that your own code returns explicitly.

The router evaluates every registered endpoint against the incoming HTTP method, path, route constraints, and optionally API version. If none match, the framework returns 404. The implication is that at least one of those matching criteria is failing โ€” not necessarily the path.


Cause 1: app.MapControllers() Is Missing

The single most common cause. Your controller and routes are defined correctly, but you forgot to call app.MapControllers() in Program.cs. Without it, none of your controller routes are registered with the endpoint routing system.

Always verify this call exists. It must appear after any authentication/authorization middleware, but it must appear.

Fix: Add app.MapControllers() before app.Run().


Cause 2: Middleware Pipeline Order Is Wrong

ASP.NET Core's middleware pipeline is ordered, and several middleware components must appear in a specific sequence. Getting the order wrong can silently prevent routing from working.

The correct order for a Web API is:

  1. app.UseHttpsRedirection()

  2. app.UseRouting() (optional in .NET 6+ with endpoint routing, but sometimes needed explicitly)

  3. app.UseAuthentication()

  4. app.UseAuthorization()

  5. app.MapControllers()

If app.UseAuthorization() appears before app.UseAuthentication(), the authorization middleware has no identity to evaluate. Rather than throwing an error, it can silently reject the request with a 404 or 401 depending on the policy configuration.

Similarly, placing app.MapControllers() before app.UseAuthorization() in certain configurations means authorization middleware never runs for your controller endpoints, causing routing surprises.

Fix: Follow the middleware ordering above strictly. The ASP.NET Core Middleware Pipeline Checklist covers the correct order in full detail.


Cause 3: Missing or Mismatched Route Attribute

If a controller uses attribute routing but the [Route] attribute on the controller or the [HttpGet]/[HttpPost] attribute on the action is missing, mistyped, or inconsistent, the router cannot match the URL.

Common issues:

  • Controller has [Route("api/[controller]")] but you are calling /api/Product when the controller is ProductsController (plural vs singular matters)

  • Action is missing the HTTP verb attribute ([HttpGet], [HttpPost], etc.) entirely

  • Route template has a typo: [HttpGet("{od}")] when it should be [HttpGet("{id}")]

  • Route constraint blocks valid values: [HttpGet("{id:guid}")] when you are passing an integer

Fix: Use app.UseEndpoints diagnostics or the route debugger (Microsoft.AspNetCore.Routing.EndpointMiddleware at Debug log level) to list all registered routes and compare against what you are calling.


Cause 4: The [ApiController] Attribute Is Missing

The [ApiController] attribute on a controller class does more than just enable automatic model validation. It also enables certain routing behaviors. Without it in some project configurations, content negotiation and routing can behave differently, and endpoints can silently fail to match.

Fix: Ensure every Web API controller carries both [ApiController] and [Route("api/[controller]")] (or a specific route template). Inheriting from ControllerBase is correct for APIs; inheriting from Controller is for MVC views.


Cause 5: API Versioning Returns 404 for Unversioned Requests

If you have added API versioning (via the Asp.Versioning.Mvc package) and configured it to require explicit versioning, requests that do not include a version declaration return 404 rather than the endpoint.

By default, AssumeDefaultVersionWhenUnspecified is false. This means a request to /api/products when you have defined only /api/products?api-version=1.0 returns 404.

Fix: Either set AssumeDefaultVersionWhenUnspecified = true in your versioning options, or explicitly include the version in every request. The first option is safer during initial roll-out; the second is stricter and preferred for mature APIs.


Cause 6: CORS Preflight Returns 404

Cross-Origin Resource Sharing (CORS) preflight requests use the OPTIONS HTTP method. If your API does not have a CORS policy configured, the preflight OPTIONS request hits the routing system with no matching endpoint and returns 404. The actual GET or POST request may succeed directly but the browser blocks it after the failed preflight.

Fix: Add app.UseCors() with an appropriate named policy before app.UseAuthorization(). If you are building a fully accessible public API, a permissive development policy is acceptable. Production APIs should whitelist specific origins.


Cause 7: Reverse Proxy Strips or Modifies the Path

When your API sits behind a reverse proxy such as Nginx, Azure Front Door, or an AWS Application Load Balancer, the proxy may rewrite the incoming path. If your application expects requests at /api/products but the proxy strips the /api prefix and forwards only /products, routing fails and returns 404.

Fix: Use ForwardedHeaders middleware and configure PathBase correctly if the proxy removes a path prefix. Alternatively, configure the proxy to preserve the full path. Check app.UsePathBase("/api") if your app is mounted at a sub-path.


How Do You Quickly Diagnose a 404 Caused by Routing?

The fastest diagnostic is to enable endpoint routing logging at Debug level in your appsettings.Development.json:

"Logging": {
  "LogLevel": {
    "Microsoft.AspNetCore.Routing": "Debug"
  }
}

When using a tool like the Bogus package or simply curl, look for "No candidates found" or "Matched endpoint" messages in the log output. If the correct endpoint is listed but not matched, the issue is in constraints or middleware. If the endpoint is absent entirely, the registration is missing.

The development environment's endpoint explorer middleware (accessible at /_endpoints in recent .NET versions with MapGet("/_endpoints", ...)) can also list every registered route and its metadata.


How to Prevent This from Recurring

A few structural practices prevent 404 routing surprises from reaching production:

Integration tests are the primary guard. A single integration test using WebApplicationFactory that calls every controller route once โ€” even with incorrect payloads โ€” will catch missing registrations, wrong verb configurations, and middleware order issues in CI. See the 7 Common ASP.NET Core Dependency Injection Mistakes article for how DI misconfiguration can indirectly cause routing failures.

Use conventional routing sparingly. Attribute routing is explicit: every route is written on the controller. Conventional routing relies on convention matching and can silently exclude actions if the convention does not match. For APIs, attribute routing is almost always the right choice.

Keep Program.cs middleware order reviewed. Treat middleware ordering as an architectural contract. Document the intended order in a comment or teams wiki. Middleware insertion during feature additions is the most common way order bugs creep in.

Apply API versioning early and consistently. Retrofitting versioning onto an existing API repeatedly causes 404 regressions. If versioning is in your roadmap, introduce the package early with permissive default settings so it is never the surprise cause of a 404.


FAQ

Why does my ASP.NET Core API return 404 in production but work fine in development?

The most common cause is environment-specific middleware configuration. In development, app.UseDeveloperExceptionPage() can catch and reroute certain requests, masking a routing misconfiguration. In production, the actual routing failure surfaces. Also check whether ASPNETCORE_ENVIRONMENT is set correctly in production โ€” missing this environment variable defaults to Production, which may skip MapControllers() registration if you have it under an if (app.Environment.IsDevelopment()) block.

Does the HTTP method (verb) matter for 404 vs 405 errors?

Yes, and the distinction matters. If a route exists but the wrong HTTP verb is used, ASP.NET Core returns 405 Method Not Allowed (not 404) since .NET 7+. If you see 404, the entire route pattern is not matching โ€” not just the verb. Seeing 405 narrows the problem to the verb attribute ([HttpGet] vs [HttpPost] etc.).

Can a missing DI registration cause a 404?

Yes, indirectly. If a controller constructor has a dependency that is not registered with the DI container, IControllerActivator throws an exception during controller instantiation. If this exception is unhandled or swallowed by a global error handler configured to return 404 instead of 500, you see 404 with no visible error. Check your global exception handler configuration and ensure it returns appropriate status codes per exception type.

Does adding [Authorize] to a controller cause 404s?

Normally no โ€” unauthorized access returns 401 (or redirects to login for MVC apps). However, if UseAuthentication() or UseAuthorization() is missing from the pipeline, authorization short-circuits in unexpected ways. In some configurations, missing authentication middleware causes the request to never reach the endpoint, appearing as 404. Always follow the correct middleware order.

My Minimal API endpoint returns 404 but no exception. What should I check?

Minimal API endpoints registered with app.MapGet(...), app.MapPost(...), etc. require that app.Build() is called on the WebApplication builder before app.MapGet() is called, and app.Run() is invoked after all mappings. Check that the route "/api/resource" does not have a trailing slash conflict (route /api/resource/ is different from /api/resource). Also verify the root path โ€” Minimal API routes are case-insensitive by default but are sensitive to the leading slash.

Does SSL or HTTPS redirect cause a 404?

Indirectly. app.UseHttpsRedirection() redirects HTTP requests to HTTPS. If the HTTPS certificate is not configured correctly, the redirect may fail and the client receives 404. If you are testing locally over plain HTTP and the redirection fails to resolve, the final response after the failed redirect appears as 404. Use HTTPS in development by default to avoid this class of problem.

Can a misconfigured route constraint silently produce 404?

Yes. Route constraints like {id:int} or {id:guid} silently reject requests where the value does not match, returning 404 without a validation error. If you define [HttpGet("{id:int}")] and call the endpoint with a string value, the constraint fails and the router returns 404. This is by design but frequently catches developers off-guard. Always verify the constraint type matches what your client sends.

More from this blog

C

Coding Droplets

244 posts

Coding Droplets is your go-to resource for .NET and ASP.NET Core development. Whether you're just starting out or building production systems, you'll find practical guides, real-world patterns, and clear explanations that actually make sense.

From beginner-friendly tutorials to advanced architecture decisions. We publish fresh .NET content every day to help you grow at every stage of your career.