phpdockernginx

Nginx `try_files` directive uses an incorrect fallback URI


When i am trying to connect one of my services i get 404 Not Found nginx. I've configured dockerfile and docker-compose well, but have some issues on nginx configs side

the project structure is

./backend/subscription_service
./backend/subscription_service/Dockerfile
./nginx/nginx.conf
./docker-compose.yaml

Nginx log:

2025/06/08 09:24:32 [error] 22#22: *2 open() "/etc/nginx/html/index.php" failed (2: No such file or directory), client: 172.18.0.1, server: localhost, request: "POST /subscription/subscriptions/ HTTP/1.1", host: "localhost" 172.18.0.1 - - [08/Jun/2025:09:24:32 +0000] "POST /subscription/subscriptions/ HTTP/1.1" 404 153 "-" "PostmanRuntime/7.44.0" "-"

Nginx

server {
    listen 80;
    server_name localhost;

location /subscription {
        alias /var/www/html/public;
        index index.php index.html index.htm;

        try_files $uri $uri/ /subscription/index.php?$query_string;

        location ~ \.php$ {
            include fastcgi_params;
            fastcgi_pass subscription_service:9000;
            fastcgi_index index.php;
            fastcgi_param SCRIPT_FILENAME $request_filename;
        }

        location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
            expires max;
            log_not_found off;
        }
    }
}

Dockerfile

...
WORKDIR /var/www/html
COPY . /var/www/html

...

EXPOSE 9000

docker-compose.yaml

  subscription_service:
    build:
      context: ./backend/subscription_service
      dockerfile: Dockerfile
    container_name: subscription_service
    depends_on:
      - subscription_db
    volumes:
      - ./backend/subscription_service:/var/www/html
      - ./backend/subscription_service/bootstrap/cache:/var/www/html/bootstrap/cache
      - ./backend/subscription_service/storage:/var/www/html/storage
...
  nginx:
    image: nginx:alpine
    container_name: nginx
    ports:
      - "80:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
      - ./backend/subscription_service:/var/www/html:ro
    depends_on:
      - subscription_service

the project is laravel. index.php is located in ./backend/subscription_service/public folder


Solution

  • You've come upon a well-known issue related to the combined use of the try_files and alias directives. Here's a detailed explanation of the root cause of this problem.

    When processing each argument of try_files directive, the ngx_http_try_files_module performs an additional conditional operation — but only if the argument contains variables. If the parent location where the try_files directive is defined is a prefix location and uses the alias directive instead of root to define the document root, and if the beginning of the string obtained after variable interpolation matches the URI prefix used in the parent location, that prefix is stripped from the interpolated string. This transformation is essential for the correct functioning of the code that follows, where the argument value is mapped to a physical file system path — especially when arguments like $uri or $uri/ are used in the try_files directive. However, the check for whether the argument is the last one in the try_files directive — that is, whether it defines the fallback action — occurs after the above-described transformation has taken place.

    So, in your case, instead of the expected fallback URI /subscriptions/index.php?..., nginx ends up using /index.php?.... Since there's no defined location for handling that URI in your configuration, the so-called null location is used. The document root in that case, unless specified explicitly at the outer level, defaults to the root directive's built-in default <prefix>/html. The <prefix> value is set at compile time and can be viewed with the nginx -V command. Typically, it is either /etc/nginx (as in your case) or /usr/share/nginx.

    This issue does not affect cases where the fallback URI is written without variables. For example, the following configuration — common for React-based applications — works just fine:

    location /app {
        alias /path/to/app;
        try_files $uri /app/index.html;
    }
    

    There are several popular approaches to solve this issue, and to make the answer as comprehensive as possible, I'll try to list them all.