3

I'm trying to find the best way to rewrite this rule so it will work on nginx:

   RewriteCond %{REQUEST_URI} !^/clip-art/.*$
   RewriteCond %{REQUEST_URI} !^/Clip-Art/.*$
   RewriteCond %{REQUEST_URI} !^/cgi-bin/.*$
   RewriteCond %{REQUEST_URI} !^/images/.*$
   RewriteCond %{REQUEST_URI} !/invitation/
   RewriteCond %{REQUEST_URI} !/designer/
   RewriteRule ^/(.*)/(.*)/(.*)/(.*)$ /clip-art/$1/$2/$3/$4 [r=301,nc,l]

I know I can do something like:

   if ($request_uri !~ "/designer") {
       rewrite ^/(.*)/(.*)/(.*)/(.*)$ /clip-art/$1/$2/$3/$4 permanent;
   }

But obviously that only works with one of the folder names. How do I make it work with multiple folder names?

1
  • 1
    Avoid if wherever possible in Nginx. Read an Nginx rewrite tutorial, use a regex help site, and give it a go. If you can't get there tell us what you come up with and we can steer you in the right direction.
    – Tim
    Commented Aug 12, 2017 at 8:32

2 Answers 2

3

Regular expressions are relatively inefficient and generally discouraged in Nginx configurations when other methods would suffice. There are a few cases here that don't require regexes:

location /clip-art/ {
    # ...
}

location /Clip-Art/ {
    # ...
}

location /images/ {
    # ...
}

location /cgi-bin/ {
    # ...
}

If you're just serving static images from at least three of those, we can make it even simpler. The ^~ prefix means that these rules will take precedence over regex-based rules, which would otherwise take precedence. While this has many uses, it can act as a security feature when dealing with directories containing only static resources. If someone manages to upload a PHP file or some other executable code that would normally be handled by a regex location, the pre-regex location (with ^~) will win, and the file won't be executed. I've also omitted the typical $uri/ because it's not really applicable to static resource directories.

location ^~ /clip-art/ { try_files $uri =404; }
location ^~ /Clip-Art/ { try_files $uri =404; }
location ^~ /images/   { try_files $uri =404; }

location /cgi-bin/ {
    # ...
}

As for the remaining conditions, Nginx is designed to match starting from the beginning of each URI. Ideally, you can handle these sorts of scenarios in your application, rather than within Nginx. However, in this specific instance, a relatively efficient regex can be written:

rewrite "^((?:/(?!designer|invitation)[^/]+){4})/?$" /clip-art$1 permanent;
2
  • thanks. Unfortunately, these are dynamic URL's I'm trying to exclude, so try_files won't work. I agree that ideally it would be dealt with from inside the script, but this isn't an option here (as I'm just moving the site over - I don't want to have to rewrite the whole CMS to work like that). I'm wondering if maybe I could do something based on your suggestion: location /cgi-bin/ { set dont_rewrite = 1 }, and then do an if () {} looking at that flag Commented Aug 12, 2017 at 9:40
  • 2
    The basic technique outlined here will still work. Just ignore the middle code block; the rest is still applicable. You don't need to use set or if. (if in particular is extremely difficult to use correctly; it has subtle issues that come back to bite you.)
    – Zenexer
    Commented Aug 12, 2017 at 9:42
2

https://stackoverflow.com/questions/406230/regular-expression-to-match-a-line-that-doesnt-contain-a-word contains an example how to use regex for finding strings that do not contain certain words.

Use this regex approach in your location directive, and then use your rewrite statement inside the location block.

Furthermore, I suggest you use .+ (one or more matches) instead of .* (zero or more matches) as your wildcard in the rewrite directive.

1
  • 2
    While that's a pretty handy regex trick, it does have a significant caveat: it performs quite poorly. Granted, Nginx is rarely the bottleneck, but it's worth noting. In this scenario, it's possible to write a specialized regex that performs reasonably well, though that's not always the case.
    – Zenexer
    Commented Aug 12, 2017 at 9:40

You must log in to answer this question.

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