Skip to content

nginx: allow large file uploads for /api/v4/files endpoint#184

Open
madest92 wants to merge 1 commit intomattermost:mainfrom
madest92:max-upload-size
Open

nginx: allow large file uploads for /api/v4/files endpoint#184
madest92 wants to merge 1 commit intomattermost:mainfrom
madest92:max-upload-size

Conversation

@madest92
Copy link
Copy Markdown

This prevents 413 (Request Entity Too Large) errors when uploading large files.

Key changes:

  • Added a separate location for the file upload endpoint
  • Removed request size limit at nginx level (client_max_body_size 0)
  • Increased client_body_timeout to support slow/large uploads
  • Simplified proxy configuration for this endpoint

Note:
File size limits are still enforced at the application level via Mattermost settings, where administrators can configure the maximum allowed upload size.
This change only removes nginx as a limiting factor and delegates control to the application.
Maximum File Size

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 25, 2026

📝 Walkthrough

Walkthrough

A new Nginx location block was added for /api/v4/files to handle GET/POST requests, routing to the backend upstream with custom timeout and body size constraints.

Changes

Cohort / File(s) Summary
Nginx Configuration
nginx/conf.d/default.conf
Added exact-match location block for /api/v4/files endpoint with no client body size limit (client_max_body_size 0), extended client body timeout (1200s), proxy read timeout (600s), and proxy headers configuration.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding support for large file uploads to the /api/v4/files endpoint by removing nginx size limits.
Description check ✅ Passed The description is directly related to the changeset, explaining the purpose of the nginx configuration changes, key modifications, and application-level controls.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
nginx/conf.d/default.conf (1)

113-126: Consider adding proxy_request_buffering off and proxy_send_timeout for large uploads.

The configuration correctly removes the nginx body size limit and increases timeouts. However, for large file uploads, consider these improvements:

  1. proxy_request_buffering off; – Streams the upload directly to the backend without buffering in nginx memory, reducing memory pressure for large files.

  2. proxy_send_timeout – Currently missing; defaults to 60s. For slow uploads of large files, this timeout could be reached while nginx sends the request body to the backend. Consider setting it to match proxy_read_timeout (600s) or higher.

♻️ Suggested improvement
 location = /api/v4/files {
     client_max_body_size 0;
     client_body_timeout 1200s;
+    proxy_request_buffering off;
     proxy_set_header Connection "";
     proxy_set_header Host $http_host;
     proxy_set_header X-Real-IP $remote_addr;
     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
     proxy_set_header X-Forwarded-Proto $scheme;
     proxy_set_header X-Frame-Options SAMEORIGIN;
     proxy_set_header Early-Data $ssl_early_data;
+    proxy_send_timeout 600s;
     proxy_read_timeout 600s;
     proxy_http_version 1.1;
     proxy_pass http://backend;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nginx/conf.d/default.conf` around lines 113 - 126, In the location block
matching "= /api/v4/files" add proxy_request_buffering off to stream request
bodies to the backend (avoid nginx buffering large uploads) and set
proxy_send_timeout (e.g., 600s to match proxy_read_timeout) so nginx won't time
out while sending the request body to the upstream; update the location =
/api/v4/files block to include these two directives alongside the existing
proxy_read_timeout and proxy_http_version settings.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@nginx/conf.d/default.conf`:
- Line 113: The nginx config currently has an exact match location "location =
/api/v4/files" which raises client_max_body_size for legacy single-file uploads
but does not cover resumable/chunked uploads under "/api/v4/uploads"; if your
Mattermost uses resumable uploads add a corresponding location block for the
uploads endpoints (e.g. a prefix or regex location for "/api/v4/uploads" and
"/api/v4/uploads/*") and set the same client_max_body_size value as used for
"location = /api/v4/files" so chunk initialization (POST /api/v4/uploads) and
chunk POSTs (POST /api/v4/uploads/{upload_id}) are not rejected. Ensure the new
block(s) use the same directives (client_max_body_size and any other relevant
proxy/fastcgi settings) and test upload flows after deploying the change.

---

Nitpick comments:
In `@nginx/conf.d/default.conf`:
- Around line 113-126: In the location block matching "= /api/v4/files" add
proxy_request_buffering off to stream request bodies to the backend (avoid nginx
buffering large uploads) and set proxy_send_timeout (e.g., 600s to match
proxy_read_timeout) so nginx won't time out while sending the request body to
the upstream; update the location = /api/v4/files block to include these two
directives alongside the existing proxy_read_timeout and proxy_http_version
settings.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d9d0e957-bc4c-4666-8d44-4818d6f8e68a

📥 Commits

Reviewing files that changed from the base of the PR and between 1423a77 and c6252ba.

📒 Files selected for processing (1)
  • nginx/conf.d/default.conf

Comment thread nginx/conf.d/default.conf
@madest92
Copy link
Copy Markdown
Author

@coderabbitai approve

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 25, 2026

✅ Actions performed

Comments resolved and changes approved.

@madest92
Copy link
Copy Markdown
Author

madest92 commented Apr 2, 2026

@hanzei Maybe you can take a look at this PR?

@madest92
Copy link
Copy Markdown
Author

madest92 commented Apr 9, 2026

@NARSimoes hi!
Perhaps you could review MR?

Copy link
Copy Markdown

@aminvakil aminvakil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This requires maintainers to follow api version and change this if it would be changed to /api/v5/ or some other endpoint.

I think 50M suffices many use cases and you can change it locally for yourself (as I did).

This better be documented on how to increase file upload limit size IMHO.

@madest92
Copy link
Copy Markdown
Author

This requires maintainers to follow api version and change this if it would be changed to /api/v5/ or some other endpoint.

Yes. You're right. But now users are forced to find a way to increase the file upload limit.
If we specify this in the documentation, then it will also need to be supported =)

I think 50M suffices many use cases and you can change it locally for yourself (as I did).

In 99% of cases, yes. But in the remaining 1%, files are larger than 50 MB.
And users either upload them through a third-party resource or increase the limits on all requests(not a good).

Plus, the file is stored in Git, which complicates updates if there are conflicts with the pull.

@mattermost-build
Copy link
Copy Markdown

This PR has been automatically labelled "stale" because it hasn't had recent activity.
A core team member will check in on the status of the PR to help with questions.
Thank you for your contribution!

@hanzei hanzei requested a review from NARSimoes April 27, 2026 08:25
@hanzei hanzei added 2: Dev Review Requires review by a core committer and removed Lifecycle/1:stale labels Apr 27, 2026
@hanzei
Copy link
Copy Markdown
Contributor

hanzei commented Apr 27, 2026

@NARSimoes Are you the right person to review this PR?

Copy link
Copy Markdown
Member

@lieut-data lieut-data left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@edgarbellot, curious if you have any input for why we would need to enforce a body payload size at the proxy server? It would be ideal if we could just remove the setting below.

Comment thread nginx/conf.d/default.conf
}

location / {
client_max_body_size 50M;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to understand why we even have this limit at all. Should we just lift this instead?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's 1M by default (https://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size).

Setting it to 0 makes it pretty easy for a malicious actor to fill up the system resources even without authentication. I'd rather not to go into details in a public comment :)

Copy link
Copy Markdown
Author

@madest92 madest92 Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's why I only removed the body size limitation from one method(uploading files). Because there's an additional check at the backend level.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bad default, and we're going to change it locally if this gets merged.

But at the end of the day I'm not maintainer in this project which reminds me this meme :)

image

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case, I'm wondering if we just raise the default to 100MB to match the Mattermost default, and clarify in docs that if you're using this and tweaking the configuration, you'll want to tweak your proxy as well.

Note too that /api/v4/files is one file upload path, but there may be other endpoints (e.g. plugins).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case, I'm wondering if we just raise the default to 100MB to match the Mattermost default,

That would be a better default IMHO as well, as it may raise confusion right now in case a user sees that mattermost default is 100MB, but they can not upload something larger than 50MB right now.

and clarify in docs that if you're using this and tweaking the configuration, you'll want to tweak your proxy as well.

Sounds like a plan.

Note too that /api/v4/files is one file upload path, but there may be other endpoints (e.g. plugins).

Thanks for bringing that up, I was wondering later versions in /api/v5/ and so on in #184 (review) , seems like there is already a problem with just changing /api/v4/files.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clarify in docs that if you're using this and tweaking the configuration, you'll want to tweak your proxy as well.

Agree, documenting this is definitely useful 👍
But there’s a responsibility boundary aspect: Mattermost and proxy are often managed by different teams. Even a simple limit change may require coordination between both, which adds unnecessary friction.

So it might be better, where possible, to keep such settings on the Mattermost side, allowing admins to manage them without depending on infrastructure changes.

Note too that /api/v4/files is one file upload path, but there may be other endpoints (e.g. plugins).

According to the code, there is currently only one place for message attachments. I’m addressing a narrow issue: the attachment size setting in the admin UI is misleading if frontend/proxy limits are stricter.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@madest92 makes a valid point about operational boundaries - forcing infra and app teams to stay in sync on every MaxFileSize change can create some friction. That said, client_max_body_size 0 alone doesn't safely delegate enforcement to Mattermost. With nginx's default proxy_request_buffering on (not overridden anywhere in this config), nginx fully buffers the request body to disk before forwarding - so Mattermost's MaxBytesReader only fires after nginx has already written the entire payload. An attacker can exploit that to exhaust disk with unbounded payloads to /api/v4/files, which is the risk @aminvakil flagged.

proxy_request_buffering off would fix that, but it introduces a streaming mode not used anywhere else in this config and goes beyond what this PR needs to solve.

I think @lieut-data's suggestion is the right call: raise client_max_body_size from 50M to 100M in location / to match Mattermost's MaxFileSize default, remove the specific location = /api/v4/files block, and document that the two values should be kept in sync. It's consistent with the existing config pattern, covers plugin endpoints and future API paths, and keeps the DoS surface bounded. I think the coupling between nginx and Mattermost config is a reasonable tradeoff when it's explicitly documented.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, the buffering and DoS risk arguments make sense — I agree the limit should be enforced at the nginx level.

So let’s raise it to 100 MB globally, add information to the "Maximum file size" tooltip and to the documentation.

At the same time, it might be useful to keep /api/v4/files explicitly defined with the same limit — this simplifies administration. In that case, increasing the limit is just changing client_max_body_size, without introducing additional location blocks.

How about this as a compromise?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @madest92 -- what's the rationale for distinguishing /api/v4/files after the global uplift?

I'd advocate for keeping the configuration as simple as possible: if an admin wants to take on the complexity of managing which endpoints can accept larger payloads, they should opt into that instead of assuming /api/v4/files is the only place that would matter. (It's probably the primary place, but I underscore that this could have knock on effects for other parts of the system and I wouldn't recommend it as the default.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2: Dev Review Requires review by a core committer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants