I recently ran into one of those AWS issues that looks obvious in hindsight, but is surprisingly annoying while you are debugging it.
A request was going through CloudFront and API Gateway. The API Gateway route matched. Everything looked fine there. But the backend running in ECS still returned 404.
In the end, the issue was not whether API Gateway matched the route. It was about which path was actually forwarded to the backend.
In my case, the public URL looked something like this:
|
1 |
/api/endpoint |
API Gateway had a custom domain mapping for /api, and the actual route in API Gateway was:
|
1 |
/endpoint |
At first glance that looked correct. API Gateway matched /endpoint, and my backend also expected:
|
1 |
/endpoint |
But with a private integration, the path sent to the backend is not always the same as the path API Gateway uses for route matching. In practice, the backend could receive something like:
|
1 |
/api/endpoint |
And since the backend only knew about /endpoint, it returned 404.
To make this easier to reason about, I built a small debugging tool:
CloudFront API Gateway Integration Debugger
The tool lets you enter a browser URL and then change the relevant parts of the request chain step by step:
- CloudFront behavior path pattern
- CloudFront origin path
- optional CloudFront Function URI rewrite
- API Gateway custom-domain mapping
- API Gateway route matching
- private integration path handling
overwrite:path = $request.path
The goal is not to perfectly simulate every internal AWS detail. The goal is to make the path flow visible: what CloudFront receives, what API Gateway matches, and what the backend actually gets.
It also lets you compare different setups. For example, I previously had a CloudFront Function that removed /api before forwarding the request:
|
1 |
request.uri = request.uri.replace(/^\/api(?=\/|$)/, ''); |
That worked because the backend received /endpoint.
Later, when the /api handling moved to API Gateway custom-domain mapping, the route still matched, but the backend did not necessarily receive the same path anymore. That is the kind of subtle difference that is easy to miss.
The main lesson for me was that API Gateway route match and backend integration path are related, but not always identical.
Hopefully the tool can save someone else a bit of time the next time CloudFront and API Gateway look correct, but ECS still responds with 404.