24

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?

6
  • It is not clear what are you trying to achieve. There is also no @rails_app defined in your config.
    – AlexD
    Apr 17, 2018 at 18:58
  • 1
    YAGNI. Just get rid of the extra location. Apr 17, 2018 at 19:15
  • @AlexD thanks for noticing it. Sorry, I was generalizing the config and missed a spot. Now I use @app throughout the file.
    – tompave
    Apr 17, 2018 at 23:48
  • @MichaelHampton but what If I want to add extra config for that /api/ location?
    – tompave
    Apr 17, 2018 at 23:50
  • 1
    Then you can try_files $uri @app; the same as the rest of the server block. Apr 18, 2018 at 2:18

3 Answers 3

10

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.

0
43

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...)

7
  • This is a step up from using error_page — thanks! Any ideas how to combine this approach with conditionals (if)?
    – intelfx
    Sep 23, 2019 at 1:59
  • 3
    Unfortunately @intelfx, try_files and if don't play well together. The main problem is the behavior of if is not what most of us would expect (it should be seem more as another location block and not as real logical branching). The widely known "If is Evil" blog post (nginx.com/resources/wiki/start/topics/depth/ifisevil) helps to understand why. You can always try it and cover your configuration with tons of tests to validate (as you should anyways), but I wouldn't recommend going against the advise of Nginx itself. Sep 23, 2019 at 10:01
  • Uh-huh. So that post even lists the error_page hack as an idiom for conditional redirection. Oh well. Anyhow — thanks!
    – intelfx
    Sep 24, 2019 at 5:37
  • "idiom" with big quotes around it, right? Just a nicer name for a workaround... Sep 24, 2019 at 12:02
  • 4
    This is awesome. Note that it also works with variable named locations! For example, combining this with map $http_cookie $upstream to set $upstream to @named_location allows you to jump locations based on cookie values.
    – Miles R
    Apr 11, 2020 at 23:24
15

Several third-party module directives allows direct jump to named location, e.g. ngx.exec from the lua-nginx-module or echo_exec from the echo-nginx-module (both modules are bundled with the OpenResty package). However using the "vanilla" nginx the only way to do it is to use the try_files directive trick. Unfortunately the @VictorSchröder assumption of using /dev/null as the try_files argument will eliminate the performance penalty due to the system stat call is wrong, it is the /webroot/dev/null file that will be stat'ed in that case. Everyone can check it and see for themselves using the following instruction. So it makes no difference with the commonly used solution like

try_files /nonexistent @app;

However it is possible to pass an empty sting as the try_files parameter:

try_files "" @app;

This will not eliminate the stat system call. However it is the webroot directory that will be stat'ed in this case and checked to be an existing physical file on your filesystem, what it most likely isn't (well, at least unless you point your server or location root to such a file, but why would you do such a wild thing?) By my assumption, modern kernels should cache stat system call results for the existing filesystem objects more effectively than for non-existing ones. This is only an assumption, so if someone reading this knows better how kernel behaves in this situation, I'll be glad to listen for any clarifications.

I think the last part needs some clarification. Why the try_files directive checks the web root to be a physical file and not a directory? That's because the "" argument is not ended with a slash. An argument ended with a slash makes try_files testing it to be a directory, and an argument not ended with a slash makes try_files testing it to be a file. That is, the try_files "/" ... first check will always succeed (and an explicit or implicit index directive will take a job for an index file searching on the next request processing stage, assuming the location has static content handler) if the location root is pointing to the existing directory, and the try_files "" ... first check will always fail in that case.

It is also possible that the server webroot would be a non-existent directory, for example when nginx is used for reverse proxying only. Like many other nginx directives the root directive, if not being inherited from the previous configuration levels, has a default value of /html relative to the nginx prefix. That prefix is specified during the build time and can be checked with the nginx -V command (see the --prefix=... configure argument). If by example this prefix is equal to /etc/nginx (a common case), the default server webroot will be /etc/nginx/html which is unlikely to be an existing directory for most environments.

Still I'd prefer try_files "" ... rather that try_files /nonexistent .... If by any mean the nonexistent file will appear in the webroot directory (think about some kind of defacing hacker attack), the first one will continue to work while the second one will not.

There can be use cases for the try_files "" ... construction when the webroot directory is specified using the alias directive, especially for the exact or regex matching locations. However I can't see any possible use case for it being placed inside the location using root one, so I think nginx development team may consider to update the try_files module source code to totally eliminate that extra stat call in case of such a usage. It will made possible "unconditional" location jumps without any extra performance penalty at all while keeping compatibility with the any existing configuration.

0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .