Skip to content

Instantly share code, notes, and snippets.

@anaetrezve
Forked from esfand/nginx1subapp.md
Created May 27, 2021 12:00
Show Gist options
  • Save anaetrezve/bdbc554aef1f59cfd5df7c6958c261c3 to your computer and use it in GitHub Desktop.
Save anaetrezve/bdbc554aef1f59cfd5df7c6958c261c3 to your computer and use it in GitHub Desktop.
Nginx Location Ruels

Serving multiple webapps from different folders

November 29, 2011 developmentNginx

Few days ago I had to add a wordpress installation within the same environment where a Codeigniter app was already running happily and undisturbed.

It took me a while to figure out how to keep separate folders on the filesystem, and serve the blog from a subfolder of the main domain:

it ended up that the solution is super simple, but apparently I am not the only one who had similar problems.

Symptoms of a bad installation usually result in “no input file specified” messages or, even worse, downloading the php source code with all your precious database passwords shown in clear.

So the premise being:

the webapps need to live in sibling folders to keep tidy our github repo, in the example below will be named as /home/ubuntu/repo/webapp (codeigniter) and /home/ubuntu/repo/blog (wordpress)

the main webapp needs to respond to all the requests, while wordpress needs to catch only requests starting with /blog.

there might be better and more elegant solutions, but this is working for me, including pretty permalinks on wordpress:

server {
    server_name your.domain.com;
 
    access_log /home/ubuntu/repo/logs/access.log;
    error_log /home/ubuntu/repo/logs/error.log;
 
    # main root, used for codeigniter
    root /home/ubuntu/repo/webapp;
    index index.php index.html;
 
    # links to static files in the main app, mainly for dev purposes as this is 
    # unlikely to be triggered when using a CDN with absolute URLs to assets
    location ~* ^/(css|img|js|flv|swf)/(.+)$ {
        root /home/ubuntu/repo/webapp/application/public;
    }
 
    # most generic (smaller) request
    # most of the times will redirect to named block @ci
    location / {
        try_files $uri $uri/ @ci;
    }
 
    # create the code igniter path and perform 
    # internal redirect to php location block
    location @ci {
        if (!-e $request_filename)
        {
            rewrite ^/(.*)$ /index.php/$1 last;
            break;
        }
    }
 
    # now the meaty part, execute php scripts
    location ~ \.php {
        include /etc/nginx/fastcgi_params;
 
        # default path of our php script is the main webapp
        set $php_root /home/ubuntu/repo/webapp;
 
        # but we might have received a request for a blog address
        if ($request_uri ~ /blog/) {
            # ok, this line is a bit confusing, be aware 
            # that path to /blog/ is already in the request
            # so adding a trailing /blog here will 
            # give a "no input file" message
            set $php_root /home/ubuntu/repo;
        }
 
        # all the lines below are pretty standard
        # notice only the use of $php_root instead of $document_root
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
 
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
 
        fastcgi_param SCRIPT_NAME $fastcgi_script_name;
        fastcgi_param SCRIPT_FILENAME $php_root$fastcgi_script_name;
 
        fastcgi_pass unix:/var/run/php-fastcgi/php-fastcgi.socket;
        fastcgi_index index.php;
    }
 
    # now the blog, remember this lives in a sibling directory of the main app
    location ~ /blog/ {
        # again, this might look a bit weird, 
        # but remember that root directive doesn't drop
        # the request prefix, so /blog is appended at the end
        root /home/ubuntu/repo;
        if (!-e $request_filename)
        {
            rewrite ^/(.*)$ /index.php/$1 last;
            break;
        }
    }
}

please feel free to add comments and suggestions, hope this helps.

Finally using nginx's "try_files" directive

http://michaelshadle.com/2009/03/19/finally-using-nginxs-try-files-directive

OLD:

error_page 404 = /wordpress/index.php?q=$request_uri;

or:

if (!- e $request_filename) {
   rewrite ^/(.*) /wordpress/index.php?uri=$request_uri last;
}

NEW:

try_files $uri $uri/ /wordpress/index.php?q=$uri&$args;

Why make life more complicated if you don't need to?

Also thanks to Igor's patch you can have multiple of these, i.e.:

location /wordpress {
   try_files $uri $uri/ /wordpress/index.php?q=$uri&$args;
}

location /anotherapp {
   try_files $uri $uri/ /anotherapp/controllerfile.php?q=$uri&$args;
}

etc.

This is good, as Drupal, WordPress and many other packages (including anything I develop anymore) use the Front Controller pattern of development, which makes deploying applications a lot simpler and helps support a consistent framework across the entire application.

The Rule of nginx location Match

Source

location might be the most fundamental config directive of nginx, but the rule about which url should match which location config section has always been a mystery. In fact, the rule is much less complicated than the online documentation says about it.

Syntax

I state the rule in my own way.

Syntax: location [ = | ~ | ~* | ^~ ] uri { ... }

Between location and uri there can be one of these four operators =, ~, *, ^, or there can be no operator at all. For brevity I'll say the operator is "none" in the case where there is no operator at all.

NOTE The "operator", as I call it, is actually called "prefix" in nginx documentation. I intentionally avoid "prefix" and use "operator" instead, to make it sound more natural and less confusing.

If the operator is =, ^~ or none, the uri part in the directive is a string literal and is matched against request uri or the beginning part of request uri (i.e. its prefix). Operator = indicates an exact match, which means the uri part in the directive should be exactly the same as request uri. Operator ^~ or none indicates a prefix match, which means the uri part in the directive can be the whole request uri or only the beginning part of it.

NOTE When referring to request uri, I only talk about the part after hostname and before query string (looks like an absolute file path).

If the operator is ~ or ~*, the uri part in the directive is a regular expression, with operator ~ for case sensitive match and operator ~* for case insensitive match.

The Matching Rule

The actual matching rule goes as follows.

Considering all string literal uris, find the longest matching one against request uri:

  1. If the longest matching one has the operator = or ^~, the whole matching phase is finished and that one is the final result (which means nginx will get on with its directives in { ... }).

  2. If the longest matching one has the operator none, nginx will continue to test all regular expression uris one by one (following the order they appear in the config file)

    2a. If nginx find one regular expression uri that matches request uri, the whole matching phase is finished and this regular expression uri is the final result.

    2b. If no regular expression uri matches request uri, the whole matching phase is finished and the original longest matching string literal uri is the final result.

  3. If no string literal uri matches request uri, nginx will continue to test all regular expression uris one by one (following the order they appear in the config file)

    3a. If nginx find one regular expression uri that matches request uri, the whole matching phase is finished and this regular expression uri is the final result.

    3b. If no regular expression uri matches request uri, we conclude that not a single location directive matches request uri and 404 Not Found should be returned.

Test Case

A test config file like the following might be helpful to understand the matching rule:

location = /a {
    return 500;
}
location ^~ /a/b {
    return 501;
}
location /a/b/c {
    return 502;
}
location ~ b {
    return 503;
}
location ~* c {
    return 504;
}

Various Prefixes for Ngxin’s Location Directive

SOURCE

Often we need to create short, more expressive URLs. If you are using Nginx as a reverse proxy, one easy way to create short URLs is to define different locations under the respective server directive and then do a permanent rewrite to the actual URL in the Nginx conf file as follows:

http { 
    ....
    server {
        listen          80;
        server_name     www.agilefaqs.com agilefaqs.com;
        server_name_in_redirect on;
        port_in_redirect        on; 
 
        location ^~ /training {
            rewrite ^ http://agilefaqs.com/a/long/url/$uri permanent;  
        }
 
        location ^~ /coaching {
            rewrite ^ http://agilecoach.in$uri permanent;  
        }
 
        location = /blog {
            rewrite ^ http://blogs.agilefaqs.com/show?action=posts permanent;  
        }
 
        location / {
            root   /path/to/static/web/pages;
            index   index.html; 
        }
 
        location ~* ^.+\.(gif|jpg|jpeg|png|css|js)$ {
            add_header Cache-Control public;
            expires max;
            root   /path/to/static/content;
        }
    } 
}

I’ve been using this feature of Nginx for over 2 years, but never actually fully understood the different prefixes for the location directive.

If you check Nginx’s documentation for the syntax of the location directive, you’ll see:

location [=|~|~*|^~|@] /uri/ { ... }

The URI can be a literal string or a regular expression (regexp).

For regexps, there are two prefixes:

  • “~” for case sensitive matching
  • “~*” for case insensitive matching

If we have a list of locations using regexps, Nginx checks each location in the order its defined in the configuration file. The first regexp to match the requested url will stop the search. If no regexp matches are found, then it uses the longest matching literal string.

For example, if we have the following locations:

location ~* /.*php$ {
   rewrite ^ http://content.agilefaqs.com$uri permanent; 
}
 
location ~ /.*blogs.* {
    rewrite ^ http://blogs.agilefaqs.com$uri permanent;    
}  
 
location /blogsin {
    rewrite ^ http://agilecoach.in/blog$uri permanent;    
} 
 
location /blogsinphp {
    root   /path/to/static/web/pages;
    index   index.html; 
}

If the requested URL is http://agilefaqs.com/blogs/index.php, Nginx will permanently redirect the request to http://content.agilefaqs.com/blogs/index.php. Even though both regexps (/.*php$ and /.blogs.) match the requested URL, the first satisfying regexp (/.*php$) is picked and the search is terminated.

However let’s say the requested URL was http://agilefaqs.com/blogsinphp, Nginx will first consider /blogsin location and then /blogsinphp location. If there were more literal string locations, it would consider them as well. In this case, regexp locations would be skipped since /blogsinphp is the longest matching literal string.

If you want to slightly speed up this process, you should use the “=” prefix. .i.e.

location = /blogsinphp {
    root   /path/to/static/web/pages;
    index   index.html; 
}

and move this location right at the top of other locations. By doing so, Nginx will first look at this location, if its an exact literal string match, it would stop right there without looking at any other location directives.

However note that if http://agilefaqs.com/my/blogsinphp is requested, none of the literal strings will match and hence the first regexp (/.*php$) would be picked up instead of the string literal.

And if http://agilefaqs.com/blogsinphp/my is requested, again, none of the literal strings will match and hence the first matching regexp (/.blogs.) is selected.

What if you don’t know the exact string literal, but you want to avoid checking all the regexps?

We can achieve this by using the “^~” prefix as follows:

location = /blogsin {
    rewrite ^ http://agilecoach.in/blog$uri permanent;    
}
 
location ^~ /blogsinphp {
    root   /path/to/static/web/pages;
    index   index.html; 
}
 
location ~* /.*php$ {
   rewrite ^ http://content.agilefaqs.com$uri permanent; 
}
 
location ~ /.*blogs.* {
    rewrite ^ http://blogs.agilefaqs.com$uri permanent;    
}

Now when we request http://agilefaqs.com/blogsinphp/my, Nginx checks the first location (= /blogsin), /blogsinphp/my is not an exact match. It then looks at (^~ /blogsinphp), its not an exact match, however since we’ve used ^~ prefix, this location is selected by discarding all the remaining regexp locations.

However if http://agilefaqs.com/blogsin is requested, Nginx will permanently redirect the request to http://agilecoach.in/blog/blogsin even without considering any other locations.

To summarize:

  1. Search stops if location with “=” prefix has an exact matching literal string.
  2. All remaining literal string locations are matched. If the location uses “^” prefix, then regexp locations are not searched. The longest matching location with “^” prefix is used.
  3. Regexp locations are matched in the order they are defined in the configuration file. Search stops on first matching regexp.
  4. If none of the regexp matches, the longest matching literal string location is used.

Even though the order of the literal string locations don’t matter, its generally a good practice to declare the locations in the following order:

  • start with all the “=” prefix,
  • followed by “^~” prefix,
  • then all the literal string locations
  • finally all the regexp locations (since the order matters, place them with the most likely ones first)

BTW adding a break directive inside any of the location directives has not effect.

Named Location

The prefix "@" specifies a named location. Such locations are not used during normal processing of requests, they are intended only to process internally redirected requests (see error_page, try_files).

@location is a named location. Named locations preserve $uri as it was before entering such location. They can be reached only via error_page, post_action and try_files.

Nginx location and rewrite configuration

Source

Okay guys, so as many of you know, we offer both Apache and Nginx servers here as part of our standard shared hosting packages. There is no better web server out there for reliable performance in a high-traffic environment. One thing that I frequently go through with the new staff here are nginx location / rewrite rules because they can be a bit confusing.

The best way to think of things is that as a request comes in, Nginx will scan through the configuration to find a “location” line that matches the request. There are TWO modes that nginx uses to scan through the configuration file: literal string matching and regular expression checks. Nginx first scans through ALL literal string location entries in the order that they occur in the configuration file, and secondly scans through ALL the regular expression location entries in the order that they occur in the configuration file. So be aware – location ordering order DOES matter.

Now there’s a few ways of interrupting that flow:

location = /images { } (Note: does not work for regular expressions) The “=” is the important character here. This matches a request for “/images” ONLY. This also halts the location scanning as soon as such an exact match is met.

location ^~ /images {} (Note: does not work for regular expressions) The “^~” results in a case sensitive match for the beginning of a request. This means /images, /images/logo.gif, etc will all be matched. This also halts the location scanning as soon as a match is met.

location ~ /images {} location ~* /images {} (case insensitive version) This causes a case (in-)sensitive match for the beginning of a request. Identical to the previous one, except this one doesn’t stop searching for a more exact location clauses.

That’s IT! Yes it really is that simple. Now there’s a few variations for case-insensitive matches or named-locations, but don’t worry about those for now.

Now all of the above examples are literal string examples. If you replace /images with a regular expression then suddenly you have altered the order of the rules (remember ALL literal strings get checked first, and THEN regular expressions – regardless of the order you have them in your configuration).

An examples of a regular expression match is:

location ~ .(gif|jpg|jpeg)$ { } This will match any request that ends in .gif, .jpg, or .jpeg.

So now that we’ve discussed the foundations of the location rules, we can move into rewrites. There are TWO kinds of rewrites – URL redirects (HTTP301/HTTP302), or an internal rewrite (mangles the request before it is processed).

URL Redirects are the simplest to understand:

location /admin {
    rewrite ^/admin/(.*)$ http://admin.example.com/$1 permanent;
}

This example will redirect any request matching the location rule (see earlier) as a HTTP 301 permanent redirection to http://admin.example.com/. e.g. http://www.example.com/admin/index.html now gets HTTP redirected to http://admin.example.com/index.html. Note the regular expression and the $1 replacement in the URL. If you want the redirect to be a HTTP 302 (temporary redirection), just change the word “permanent” to “redirect”.

Internal rewrites are a little more complicated:

location /admin {
    rewrite ^/admin/(.*)$ /$1 break;
}

The key word here is “break”. This causes the rewrite processing to stop. If this word was “last”, it would then go back to scanning location entries as per our discussions earlier – but now with the rewritten URL.

I hope that clears up nginx configuration. The documentation is really good over at the nginx wiki (http://wiki.nginx.org/NginxModules). I think this was the only part that sometimes confuses some of us here. let us know if you think I missed anything, otherwise I hope to put up some of our nginx rewrites for some o the more popular forums/blogs in the weeks to come!

Rewrite Old Domain to New Domain

You have decided to change the domain name from beta_domain.com to bedom.com which is shorter and has a cool ring to it. There is no reason to pass this down to Rails and it’s very easy to handle in Nginx.

server { listen 80; server_name beta_domain.com www.beta_domain.com; rewrite ^ $scheme://www.bedom.com$request_uri permanent; # or # rewrite ^ $scheme://www.bedom.com ; }

This is a simple block that does quite a bit. Any requests matching the old domain are caught and redirected. The first redirect line with a $request_uri means “when we redirect, copy the URL part” so when they visit http://www.beta_domain.com/about they will be redirected to http://www.bedom.com/about. The permanent means for the browser to remember this redirect forever (status 301) can be a little dangerous as we will show below. The second version, which is commented out, is a simpler “redirect everything to the homepage” which might be better if you revamped your site in addition to renaming it. This second version also returns a temporary redirect (status 302) so the browser will check this URL again in the future. This block is where you could add common “misspellings” domain that you registered.

Add or Remove www From Domain

So, now your SEO guy is saying you need to add www to the domain and make sure all requests include it since . Or maybe today, thanks to Twitter, you’re told to remove it since “Shorter names are now sexy”. Well, we have easy solutions for you here.

Add a www with this block

server { listen 80; server_name bedom.com; rewrite ^(.*)$ $scheme://www.bedom.com$1; }

Remove a www with this block instead

server { listen 80; server_name www.bedom.com; rewrite ^(.*)$ $scheme://bedom.com$1; }

Or, if you prefer to drop the www from multiple host names, you can add into the server block that would catch the host names (note: where possible the above is better).

if ($host ~* www.(.)) { set $host_without_www $1; rewrite ^(.)$ $scheme://$host_without_www$1 permanent; #1 #rewrite ^ $scheme://$host_without_www$1request_uri permanent; #2 }

This will check if the host name has a www in it and drop it. You can now have a server line like:

server_name www.bedom.com _ ;

or

server_name domain1.com www.domain1.com domain2.com www.domain2.com ;

Depending on if you want to catch all or some domain names, you have a lot of control in what you do. Also, the second version (commented out) will include the URI making it more seamless to the user.

NOTE: #1/#2 – The $1 is part of the regex and is the part caught between ()’s. In the if statement that is the part after the ‘www.’ so the domain.com and in line #1 it’s everything between the start ‘^’ and the end ‘$’ on the regex. This is a common form, but the second version #2 is better for that case since it doesn’t have Nginx process regex like we do in the if statement.

Rewrite All Domains To Proper Domain

One common request is to redirect all traffic so it uses the proper domain name. You can hit 123.45.67.891 or ec2-123-45-67-891.compute-1.amazonaws.com directly but if you just have one site up, most people prefer to correct that to a domain name.

server { listen 80 default; server_name _; rewrite ^ $scheme://www.bedom.com; }

This listens to all requests not caught by other server blocks and redirects them to http(s)://www.bedom.com which also helps with future SSL requests which would depend on the domain name. You also don’t usually want to include the URI as other domains may have invalid URI’s (ones that wouldn’t match on your site).

Drop Obviously Bad Requests (.php/.aspx)

Sometimes the requests can go to the wrong site. For example, an IP address is reused, a site is changed, or there is a typo in a configuration and you’re getting requests that are obviously not for you. Normally any request that isn’t for a static asset gets passed to your app. You’re running a Rails app so I’m 100% sure you won’t be using Active Server Pages and most likely won’t run PHP or .CGI files. Adding this to your server block will drop the requests before they hit the Rails queue.

Drop requests to non-rails requests

if ($request_filename ~* .(aspx|php|jsp|cgi)$) { return 410; }

Since location blocks are usually preferred to "if"'s, this is a little better

location ~ .(aspx|php|jsp|cgi)$ { return 410; }

This ensures bad requests don’t overload the Rails app. The 410 is a “don’t try this again” which should be better than a 404.

Different Types Of Rewrites and Gotchas

Earlier I mentioned that permanent redirects (status 301) can be dangerous. Since the browser remembers this redirect you can have bad experiences if you are not careful. An example that I have seen is:

rewrite ^.*$ /coming_soon permanent;

This one was for a site that was going to do a relaunch and wanted all requests to go to a “coming soon” page. The initial issue was that this caught all pages including coming_soon and ended up being an infinite redirect in the browsers cache. That was bad. They then used a location block to catch the coming_soon URL but then when they finally removed the rewrite and relaunched, they discovered many URLs that existed before were still redirecting to the coming_soon page. The use of permanent meant that they had to get customers to clear their caches to view the relaunched site. (Well, actually, I caught this shortly after they deployed it so it was primarily just the developers and testers who encountered this problem).

rewrite ^(.*)$ https://$host$1 permanent; #3 rewrite ^ https://$host$request_uri permanent; #4

This second example was when a company used ssl_requirement in their app and set pages like about and company to force non-ssl. After the Firesheep issue they added this line to force all requests to SSL, which is good, but they forgot to update ssl_requirement so when people went to the about page, they got bounced from http to https and back until the browser gave up.

NOTE: #3/#4 again both lines do the same thing but the second line is less work for Nginx.

Redirect: via Rewrite vs via Return

Source

More often than not there are times when your initial thought process changes mid way through your production ready application and you decide to change your application url. It could be your scheme (from a non-www to a www or vice-versa) or it could be your protocol (say, from http to https).

There are two ways of implementing this change in nginx.

Redirect from non-www to www

server {
        server_name example.com;
        # Option 1
        return 301 $scheme://$host$request_uri;

        # Option 2
        rewrite ^ http://$host$request_uri? permanent;
}

Redirect from http to https

server {
        server_name example.com;
        # Option 1
        return 301 https://$server_name$request_uri;

        # Option 2
        rewrite ^ https://$server_name$request_uri? permanent;
}

REWRITE

  • Only the part of the original url that matches the regex is rewritten.
  • Slower than a Return.
  • Returns HTTP 302 (Moved Temporarily) in all cases, irrespective of permanent.
  • Suitable for temporary url changes.

RETURN

  • The entire url is rewritten to the url specified.
  • Faster response than rewrite.
  • Returns HTTP 301 (Moved Permanently).
  • Suitable for permanent changes to the url.
  • No need to set permanent.
  • More details on nginx can be found here

Internal redirect to another domain

14 October 2013

Let say we have multiple sites a.com b.com and c.com and created some shared resourse (widget) under shared.com. For simplicity just imagine Disqus where you need to embed comments widget (shared resourse) to every site, but don’t want to deal with AJAX ”same origin policy” problems and Iframes ( actually sometimes Iframes are not so bad and could be used with care, but we are not talking about that ).

Or maybe you just want to go to example.com/awesome-page and see a page from another domain, let say Yahoo.com homepage, but preserving original url ( have no idea why would you do that, but hey.. ). So example.com/awesome-page --> (internally fetches) --> yahoo.com

How would you do that with Nginx?

Let’s go back to our a b c .com –> shared.com . To overcome AJAX same-origin policy problem on each client side we can create special path prefix /comment, then in Nginx config we do something like this:

server
{
  listen 80; 
  server_name a.com b.com c.com;
 
  location ~* ^/comment/(.*) {
    proxy_set_header HOST shared.com;
    # $1 - stores capture from the location on top
    # $is_args will return ? if there are query params
    # $args stores query params
    proxy_pass http://comment/$1$is_args$args;
  }
 }
 
server {
  listen 80;
  server shared.com;
 
  location / {
    # Proxy to some app handler
  }
}
 
upstream comment {
  server localhost; # or any other host essentially
}

Now we could request a.com/comment/api/v1/resource which internally will be transferred to its own service as shared.com/api/v1/resource (notice /comment prefix is gone).

That way you can create small Nginx API to fetch external resources (omg!). Feel free to improvise!

Subfolder by Locations

server {
     location / {
         ...
         error_page  404  = /index.php?q=$request_uri;
     }

     location /filestore/ {
         ...
     }

     location /tmp/ {
         ...
     }

     location /cgi-bin/ {
         ...
     }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment