Why "/" nginx location rule fails to catch some URLs? Isn't it supposed to go with all?

I have an nginx that serves as reverse proxy, and it redirects requests to an angular app or to a node js backend app depending on the request URL. There is also a rule location ~ /s/(cas)/(.*) that serves static content (although I'm seeing now that if "/" caught this route too, it would not be necessary to have that rule, as static content is also kept at backend:4000).

My concern is particular to the most general rule "/" that is supposed to catch all requests that did not fall into any other location, it is not applying correctly to some URLS causing nginx to send its 50x.html error page. In particular, my problem is that this redirection seems to not catch all traffic that didn't fit a previous rule. And is the one rule in charge of redirecting the traffic that should land on the angular app.

If I'm correct, this should fall under the "/" rule:

https://SUBDOMAIN.DOMAIN.es/user/trip/13925/instant?sharedToken=[REDACTED]

And these should at least be redirected correctly by the "/" rule, but also show the nginx fail page after a lot of timeout:

https://SUBDOMAIN.DOMAIN.es/user/trip/foo/instant?sharedToken=[REDACTED] # changed id for "foo"
https://SUBDOMAIN.DOMAIN.es/user/trip/instant?sharedToken=[REDACTED] # removed id segment of url
https://SUBDOMAIN.DOMAIN.es/user/instant?sharedToken=[REDACTED] # also removed "trip" segment of url

Any other variation of the url works fine and is redirected to https://backend:4000.

So, why aren't these rules caught by the location "/"?

This is the nginx config file. Domain and subdomain have been omitted on purpose:

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    expires $expires;
    add_header Strict-Transport-Security "max-age=15768000; includeSubDomains" always;
    server_name [SUBDOMAIN].[DOMAIN_NAME].es;
    ssl_certificate /etc/nginx/ssl/CERT.crt;
    ssl_certificate_key /etc/nginx/ssl/CERT.key;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
    ssl_session_cache shared:SSL:5m;
    ssl_session_timeout 1h;
    gzip on;
    gzip_disable "msie6";
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_buffers 16 8k;
    gzip_http_version 1.1;
    gzip_min_length 256;
    gzip_types text/plain text/css application/javascript application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml image/x-icon;

    location ~ /api(?<url>/.*)  {
        resolver 127.0.0.11;
        set $target http://backend:5000/api${url}$is_args$args;
        proxy_set_header X-Forwarded-Host $host;     # Relay whatever hostname was received
        proxy_set_header X-Forwarded-Proto $scheme;  # Relay either http or https
        proxy_set_header X-Forwarded-Server $host;   # Relay whatever hostname was received
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Prefix /api/;
        proxy_set_header Host "SUBDOMAIN.DOMAIN.es";

        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Max-Age 3600;
        add_header Access-Control-Expose-Headers Content-Length;
        add_header Access-Control-Allow-Headers Range;

    ## Websockets support 2/2
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    ## END Websockets support 2/2

        proxy_pass $target;
        client_max_body_size 10M;
    }

    location ^~ /_assets/ {
        alias /usr/share/nginx/html/assets/;
    }

    location ^~ /.well-known/acme-challenge/ {
        alias /usr/share/nginx/html/.well-known/acme-challenge/;
    }

    location ~ /s/(cas)/(.*) {
        add_header Pragma "no-cache";
        add_header Cache-Control "no-store, no-cache, must-revalidate, post-check=0, pre-check=0";
        proxy_pass http://backend:4000;
    }

    location / {
        #root /usr/share/nginx/html;
        proxy_pass http://backend:4000;
        expires -1;
        proxy_set_header X-Forwarded-Host "SUBDOMAIN.DOMAIN.es";
        proxy_set_header X-Forwarded-Server "SUBDOMAIN.DOMAIN.es";
        proxy_set_header Host "SUBDOMAIN.DOMAIN.es";

        add_header Pragma "no-cache";
        add_header Cache-Control "no-store, no-cache, must-revalidate, post-check=0, pre-check=0";

        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Max-Age 3600;
        add_header Access-Control-Expose-Headers Content-Length;
        add_header Access-Control-Allow-Headers Range;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

}

Solution 1:

And these should at least be redirected correctly by the "/" rule, but also show the nginx fail page after a lot of timeout:

This indicates that it is your application that is timing out. nginx error pages come immediately.

You should check your backend application's logs, what is happening when you make requests that take long and then show a failure page.

nginx error.log is also a good debug tool in this case.