One of the more useful debugging sessions in this period came from a simple symptom:

small uploads worked, larger uploads did not.

That sounds like a single setting problem. It was not.

The Real Shape Of The Problem

The upload path crossed multiple HTTP layers before the request reached Synapse.

That meant a request body could be rejected long before the application ever saw it.

So the real question was not:

"Does Matrix support this upload?"

It was:

"Which proxy hop is rejecting it first?"

Why This Took A Minute To Untangle

The failure changed as I fixed each layer.

That is exactly what makes this kind of issue deceptive:

  • first you hit a body-size rejection,
  • then you fix one layer and hit another one,
  • then you finally reach the application and expose a different mistake in the test itself.

That progression is actually a good sign. It means the request is moving deeper through the stack.

What The Stack Taught Me

In a layered reverse-proxy design, request-size limits are not a single config value.

They are a chain property.

If one layer allows larger bodies but the next layer does not, the stack still behaves as if uploads are broken.

That seems obvious in hindsight, but it is easy to forget when each proxy config looks reasonable in isolation.

The More Important Lesson

The other useful reminder was that a good negative result depends on a correct test.

Once the request finally reached the actual Matrix media API path, a different test mistake became visible.

That mattered because it prevented me from blaming auth or Synapse for a request that was now simply malformed for the target endpoint.

What I Changed In My Head After This

I now treat problems like this as two separate checks:

  1. Can the request survive the proxy chain?
  2. Is the final request actually valid for the application endpoint?

Mix those together and the logs become confusing. Separate them and the path usually becomes obvious.

This was one of those cases where the infrastructure design was still fine, but the operational cost of that design showed up in debugging complexity.