How to do an nginx redirect excluding an IP address and a path?

I need to redirect to a URL unless the user is coming from within our internal IP address or if they are accessing a particular path.

I've tried this:

location ~* /foobar {
    break;
}

location ~* / {
    if ($remote_addr != 111.222.333.444) {
        rewrite ^ https://external.tld/
    }
}

But that doesn't seem to work. That particular configuration causes a 404 when going to https://mydomain.tld/foobar and a "Welcome to nginx!" page when accessing it from within our IP address. However, accessing https://mydomain.tld/foobar outside of our IP address does properly redirect to https://external.tld/


Solution 1:

Since you don't want (or are not allowed) to show your full server block, I'll make some guesses. What most likely happened with your configuration? You created two additional location blocks which are used to process any incoming request. Check the location directive documentation. The only two location types that can overtake a request from regex matching location are exact location (location = /uri { ... }) and do-not-check-regex prefix location (location ^~ /prefix { ... }). Otherwise, the first regex matching location will be chosen.

Every server block has its root directory, even if it isn't specified explicitly using the root directive. It is chosen using prefix (specified at the compilation time, can be checked with the nginx -V command) and /html suffix. Lets assume your nginx prefix is /usr/share/nginx. Then your default root will be at the /usr/share/nginx/html directory, unless explicitly specified using the root /some/path directive.

Default nginx behavior when serving a request can be described as try_files $uri $uri/ =404.

Lets assume we have a https://mydomain.tld/foobar request. To serve it your first location ~* /foobar { break; } will be chosen as the first matched regex location. The first directive break means that processing the current set of ngx_http_rewrite_module directives will be stopped. But there are none of those directives (except the break one) inside this location block! Yes, that means that this break directive is useless here. Next, nginx will check for the existence of a /usr/share/nginx/html/foobar file, an index file from the /usr/share/nginx/html/foobar/ directory (default is index.html), and after both checks fails, return an HTTP 404 Not Found error.

Now lets assume we have a https://mydomain.tld/ request. Your first location block won't match it, so the second one location ~* / { ... } is chosen to process this request (this location block will match any possible valid request). If an IP address check won't pass, a redirection will take its place. But if it would pass, nginx will process your request the same way as described above, returning your default welcome index.html file from the /usr/share/nginx/html/ directory.

Looks like you don't understand how directives from the ngx_http_rewrite_module are processed. Read again the very first part of the documentation:

The break, if, return, rewrite, and set directives are processed in the following order:

  • the directives of this module specified on the server level are executed sequentially;
  • repeatedly:
    • a location is searched based on a request URI;
    • the directives of this module specified inside the found location are executed sequentially;
    • the loop is repeated if a request URI was rewritten, but not more than 10 times.

You can place your checks at the server block level:

if ($uri = /foobar) {
    break;
}
if ($remote_addr != 111.222.333.444) {
    rewrite ^ https://external.tld/
}

The above break directive will work as expected, an execution of ngx_http_rewrite_module directives on the server level will be stopped. That means that any other directive from that module (like set, if, return, rewrite etc.), if any, should be placed before the above block. That does not apply to the ngx_http_rewrite_module directives placed at the location level.

There is one more possible caveat. If your https://mydomain.tld/foobar page makes use of any assets (scripts, styles, images etc.), access to those assets should be allowed too. For example, you can change the condition to allow anything started with /foobar prefix using if ($uri ~* ^/foobar) { break; } block. If their addresses cannot be checked with a single regex, you can use several if (...) { break; } blocks.