nginx: directly send from location to another named location

In my nginx 1.12.2 conf file, I have:

upstream app {
  server unix:/tmp/app.sock  fail_timeout=0;
}

server {
  listen 443 deferred;

  root /some/dir;

  try_files $uri @app;

  # If the request is for the naked domain, just serve the index bundle
  # without going through Rails
  #
  location = / {
    try_files /index.html =404;
  }

  # If the request if for the /api prefix, go directly to Rails.
  # This location block is not strictly required, but it could be a handy
  # customization hook for extra headers and settings.
  #
  location /api/ {
    # Extra conf!
    try_files @app;
  }

  # The location directory allows very creative configurations.
  # http://nginx.org/en/docs/http/ngx_http_core_module.html#location
  #
  # This is just a named location to be used in try_files.
  location @app {
    proxy_pass_request_headers on;
    proxy_set_header ...
    proxy_pass http://app;
  }
}

There, this is not really right because it has a single argument:

  location /api/ {
    # Extra conf!
    try_files @app;
  }

...but it communicates well what I'm trying to achieve. I suppose I could get try_files to work by adding a non-esitent file before the last argument.

Is try_files the only way to do it, or is there another more idiomatic directive?


Solution 1:

Your scheme will not work. When nginx settles on the final location block to process the request, it will use the the "settings and headers" that are in scope, which may be inherited from a surrounding block, but will not include any "extra headers and settings" from sibling blocks - irrespective of the process taken to find the final location block. See this document for more.

If you have common statements applicable to a number of locations, you can offload them to a separate file and include them where necessary. For example:

location / {
    try_files $uri @app;
}
location /api/ {
    # Extra conf!
    include my/proxy/conf;
}
location @app {
    include my/proxy/conf;
}

See this document for more.

Solution 2:

We have a pretty complex Nginx configuration and we wanted to use named locations to keep things a bit more DRY. Because of this requirement, we had to do exactly what you are trying to.

The sad part is: we couldn't find any direct way of doing it, but the good news is that you can trick Nginx with a try_files directive without performance penalties, pointing the first argument to /dev/null:

try_files /dev/null @the_named_location;

It's definitely a workaround and usually I would burn any pull request containing this hack in flames and carefully explain the developer the horrors of not following the standards, etc... but after you get convinced and accept this sad reality, it actually makes your configuration look so much nicer. Really nicer!

We decided that this "hack" is "acceptable" as it proved to bring a big improvement, in terms of code quality in general.

(but it still hurts my eyes sometimes...)