iOS/iPadOS file upload fails behind Nginx reverse proxy #2235
What went wrong?I am experiencing an issue with file uploads from iOS/iPadOS when Grimmory is accessed through an Nginx reverse proxy. The upload works correctly when accessing Grimmory directly on its exposed port, but fails when going through Nginx. The issue appears to be specific to iOS/iPadOS clients. The same upload works correctly from macOS. BehaviorThe request reaches Grimmory, but the backend reports that the multipart file part is missing: The failing request: returns: Nginx configurationNginx is configured as a standard reverse proxy: location / {
proxy_pass http://127.0.0.1:6060;
proxy_http_version 1.1;
proxy_set_header Host $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 https;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
client_max_body_size 200M;
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 86400;
proxy_send_timeout 86400;
}I also tested with different proxy settings:
The issue remains only on iOS/iPadOS behind Nginx. Additional informationThe Nginx access log shows: The request has: but Spring Boot reports that the Expected behaviorUploading files from iOS/iPadOS should work the same way whether Grimmory is accessed directly or through a reverse proxy. QuestionCould this be related to the way the frontend handles multipart uploads on iOS/iPadOS when running behind a reverse proxy? Are there any known requirements or additional proxy headers/configuration needed for Nginx deployments? How can we reproduce it?Reproduction
The same upload through the Nginx URL works correctly from macOS Safari. What Build of Grimmory are you on?Stable Your setupEnvironment
Screenshots or error messages (optional)No response Before submitting
|
Replies: 3 comments 6 replies
|
This sounds like it's not a bug in Grimmory, but in your configuration. I'm going to move this to a discussion. |
|
The giveaway is that it works direct and from macOS, but fails through Nginx from iOS/iPadOS — with the body arriving but the multipart iOS/iPadOS uploads send The fix is to make Nginx speak HTTP/1.1 to the backend and stop forwarding the Expect header: location /api/v1/files/upload { # or your general proxy location
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Expect ""; # let Nginx buffer & forward the body itself
proxy_request_buffering on; # default; ensures a clean Content-Length to the backend
client_max_body_size 100m; # size to your largest upload; a too-small limit is the other classic cause
proxy_pass http://grimmory_backend;
}Two things to double-check while you're in there, since they cause the same "part not present" symptom:
Start with |
|
Hi @rakesh, Here's my nginx configuration with your suggestions: Now, I am bypassing Cloudflare completely (the record is DNS only now) and running the test directly against Nginx over a clean connection. Here are the raw logs captured at both Nginx and local TCP level (tcpdump to Spring Boot):
Direct Connection Takeaways:Expect: 100-continue is absent: Expect:"-" in Nginx logs confirms iOS Safari is not sending a 100-continue header for this request. This should confirm Nginx is proxying the request transparently. The issue appears to originate directly in the web app execution under WebKit/Safari iOS, where appending the file object to FormData fails to attach/read the underlying binary stream, causing Safari to dispatch an empty request body (Content-Length: 0). |
You did the hard part here, and your evidence actually rules out what I said earlier — apologies, the
Expect: 100-continuetheory was wrong. Your logs settle it:Expect:"-"(no 100-continue),ReqLen:864(headers only),Content-Length: 0forwarded, and curl to the exact same URL succeeding. The body leaves the iPhone empty; Nginx is just faithfully forwarding an empty body. So it's client-side, exactly as you concluded.But it's not quite "Safari's FormData is broken" in general — the decisive clue is the one you almost dismissed: same device, same build, works on
https://<ip>:6060, fails onhttps://books.mastro35.com. Only one thing meaningfully differs between those two URLs, and it's no…