I could not decide the best name for the question.
Essentially what I want to achieve is to set a custom allowed body size for a specific location on the webserver.
On the other hand, I was able to achieve the necessary result already with duplicate code, so I am really looking for a way how to make the code reusable and to better understand the observed behavior.
The server reverse-proxies all API requests to the backend service.
In global nginx config /etc/nginx/nginx.conf
I set the rule for max allowed body size like so client_max_body_size 50k;
.
Then, in individual server config /etc/nginx/conf.d/example.com
I have the following config (simplified):
server {
listen 80;
listen [::]:80;
server_name api.example.com www.api.example.com
location ~* /file/upload {
client_max_body_size 100M;
# crashes without this line
proxy_pass http://localhost:90;
#proxy_pass http://localhost:90/file/upload; # also works
}
location / {
# does not work
#location ~* /file/upload {
# client_max_body_size 100M;
#}
proxy_pass http://localhost:90;
}
}
I am trying to override the max body size for file upload endpoint. See that there is 1 proxy_pass
for location /file/upload
and another proxy_pass
for location /
pointing to the same internal service.
Question 1. If I remove the proxy_pass
from the location /file/upload
then error is returned by the server. (no status code in chrome debugger). Why is this happening? Shouldn't request be propagated further to location /
?
Question 2. Why is it not possible to define the sublocation with body size override inside the /
location as in commented section in example above? If I set it like this, then 413 error code is returned, which hints that the client_max_body_size
rule is ignored..
Question 3. Finally, is it possible to tell nginx, after the request hits the /file/upload
location - to apply all the rules from the /
section? I guess one solution to this problem would be to move the common configuration into separate file and then import it in both sections.. I was thinking if there is any solution that does not require creating new files?
Here is the reusable config I am talking about basically:
location / {
#.s. kill cache. use in dev
sendfile off;
# kill cache
add_header Last-Modified $date_gmt;
add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
if_modified_since off;
expires off;
etag off;
# don't cache it
proxy_no_cache 1;
# even if cached, don't try to use it
proxy_cache_bypass 1;
proxy_pass http://localhost:90;
client_max_body_size 100M;
proxy_http_version 1.1;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $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 Host $http_host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_pass_request_headers on;
}
This is not the final version. If I had to copy this piece of code to 2 sections this would not be very friendly approach. So, it would be nice to hear some nginx
lifehacks on how accomplish what I try to accomplish in a most friendly way and get some explanations for observed behavior.
Answer 1
If I remove the
proxy_pass
from the location/file/upload
then error is returned by the server. (no status code in chrome debugger). Why is this happening?
Every location have a so-called content handler. If you don't specify content handler explicitly via proxy_pass
(fastcgi_pass
, uwsgi_pass
, etc.) directive, nginx will try to serve the request locally.
Shouldn't request be propagated further to location
/
?
Of course not. What makes you think it should?
Answer 2
Why is it not possible to define the sublocation with body size override inside the
/
location as in commented section in example above? If I set it like this, then 413 error code is returned, which hints that theclient_max_body_size
rule is ignored..
I'd rather expect you'll get the same error as in the first case since your nested location does not have an explicitly specified content handler via the proxy_pass
directive. However the following config is worth to try:
location / {
# all the common configuration
location /file/upload {
client_max_body_size 100M;
proxy_pass http://localhost:90;
}
proxy_pass http://localhost:90;
}
Answer 3
Finally, is it possible to tell nginx, after the request hits the
/file/upload
location - to apply all the rules from the/
section?
No, unless you use a separate file via include
directive in both locations. However you can try to move all the upstream related setup directives one level up to the server
context:
server {
...
# all the common configuration
location / {
proxy_pass http://localhost:90;
}
location /file/upload {
client_max_body_size 100M;
proxy_pass http://localhost:90;
}
}
Note that some directives (e.g. add_header
, proxy_set_header
) are inherited from the previous configuration level if and only if there are no those directives defined on the current level.
Very often dynamic settings for different locations can be achieved using the map
block in a following way:
map $uri $max_body_size {
~^/file/upload 100M;
default 50k;
}
server {
location / {
...
client_max_body_size $max_body_size;
...
}
}
Unfortunally not every nginx directive accepts variable as its argument. Usually when nginx documentation doesn't explicitly states that some directive can accept variables, it means it cannot, and the client_max_body_size
is exactly that kind of directive, so the above configuration won't work.