Clean-URL rewrite rule with nested "location" and alias directive

Francis Daly francis at daoine.org
Thu Nov 21 20:45:02 UTC 2013


On Wed, Nov 20, 2013 at 10:31:07PM -0500, Ben Johnson wrote:
> On 11/20/2013 4:10 AM, Francis Daly wrote:

Hi there,

> > If you do have free choice in the matter, some things work more easily
> > within nginx if you can use "root" and not "alias" -- so if you want
> > files to be accessible below the url /stage/, having them directly in
> > a directory called /stage/ is convenient.

> a.) I prefer to maintain identical directory structures within my
> staging and production environments. This allows my build scripts and
> relative paths to remain identical (provided that I set a "root path"
> variable or similar in each shell script), and it helps me to remember
> to where I must "cd" in order to execute a build script.

That's perfectly sensible.

And the web server configurations for production and staging would
also be very similar, if they were served at similar points in the
hierarchy of the web domain or domains.

But because you serve one at "/" and one at "/stable/", you end up
having different configurations. And because of nginx's implementation
of "alias", and your use of a filesystem hierarchy which means you need
"alias", you end up having very different configurations.

> b.) I really prefer to keep PHP and PHP-template files out of the
> document root (on the filesystem).

That seems mostly unrelated, unless I'm missing something. Put your php
files in "php/" which is parallel to "web/" if you like, nginx won't
care. In fact, if you don't use try_files, nginx won't even look. All
it needs to know is what filename to tell the fastcgi server to read.

> I don't want user-agents to be able
> to request .tpl files directly, for example, by requesting
> /templates/home.tpl.

I confess that I thought that was standard -- the php file itself should
have written in it "read your associated files from this directory
location", which should not be web-accessible (unless you specifically
want it to be).

> course, I build my applications such that every request is routed
> through a single point that has complete authority over how the request
> is handled.

If you really mean "every", then the only nginx config you need is

  location ^~ /myapp/ {
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME /var/myapp.php;
    fastcgi_pass unix:php.sock;
  }

Anything else is the web server getting in the way (and implementing
things that it can probably do more efficiently than your application
can).

So the time spent arranging the web server configuration you want can
be counted as time saved not implementing the extra features in your
application.

> In the second case, this seems risky; if I were to put the "stage"
> directory within the production site's document root,

I may have been unclear, or I may be misunderstanding you now.

What I intended to suggest was that: currently all of
your staging web content is visible on the filesystem in
/var/www/example.com/private/stage/web/;

if you either move or copy (or possibly even symlink) it to be visible
in /var/www/example.com/private/stage/web/stage/, then you will be able
to configure nginx using "root" instead of "alias", and your "staging"
configuration can look much more like your "production" one.

      location ^~ /stage/ {
        root /var/www/example.com/private/stage/web/;
        # The files are read from /var/www/example.com/private/stage/web/stage/
        index index.php index.html index.htm;
        try_files $uri $uri/ /stage/index.php?q=$uri;

        location ~ \.php$ {
          # use a different "root" here if you want; but make sure the php
          # files can be read from within "stage/" below that root.
          try_files $uri /stage/index.php?q=$uri;
          fastcgi_pass unix:/var/run/php5-fpm.sock;
          fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
          include /etc/nginx/fastcgi_params;
        }
      }


> I meant to say that there are certain cases in which I want index.php to
> dispatch the request to dedicated CSS and JS controllers.

If "dispatch to the controller" means "something within php, and therefore
nginx doesn't have to know or care about it", then it probably should
Just Work.

> Maybe it would be helpful for me to demonstrate how I achieve this when
> the "site" is not accessed in a subdirectory (with respect to the URL)
> and the files actually exist in the virtual host's document root.

> location / {
> 	try_files $uri $uri/ @virtual;
> }

That'll become "/stage/" and "@stagevirtual", I guess. But it'll probably
want a suitable "root" added -- your "location /" can safely inherit
the server one, while "/stage/" can't.

> location @virtual {

> 	fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

@stagevirtual will need to know its version of $document_root. So set
a suitable "root" there too.

> 	if ($uri ~ '/download/.*/$') {
> 		rewrite ^/(.*)/$ /$1 permanent;

"/stage/" will already be there in both parts of the rewrite, so no
change needed.

> 	if ($uri !~ '(/|\.[a-zA-Z0-9]{1,12}|/download/.*)$') {
> 		return 301 $uri/$is_args$args;

"/stage/" will already be there in $uri, so no change needed.

> 	if ($uri ~ '(/|/download/.*|/css2/.*|/js2/.*)$') {
> 		rewrite  ^(.*)$  /index.php?q=$1  last;

That should become "/stage/index.php".

(Note that in this specific rewrite, $1 == $uri, so you could avoid the
match-and-capture.)

> This is all I'm trying to do. I just want to replicate the above with
> the "less-friendly" directory structure that we've been discussing.

I'm not seeing many changes being needed. Provided you avoid
"alias". Which requires a filesystem change. Which hopefully won't break
the non-nginx part of your workflow.

(Actually, within your @stagevirtual, the only place root/alias seems to
matter is in the SCRIPT_FILENAME thing. So if you can build your own from
the variables you have to hand, you can probably get away with whatever
file structure you like.)

> If there is a better, more concise, or more secure way to accomplish the
> above, then I am eager to learn.

In your staging web directory: ln -s . stage. In your staging php
directory: ln -s . stage. Then set "root" in your "location ^~/stage/"
and in your "location @stagevirtual".

So long as everything that tries to recurse into the directories can
recognise the symlink loop, it should be fine.

Then in production, either "rm stage", or don't create the link in the
first place.

> Hmm, I think I see what you're getting at here. But is there no means by
> which to achieve the desired configuration with my particular directory
> structure? I'm a bit surprised at how difficult it is to decouple the
> request location from the filesystem directory from which it is served,
> while maintaining this clean-URL setup.

If you want nginx, and you want a directory structure that requires
nginx's "alias", then you get to deal with nginx's "alias", which has
some imperfections.

Change one of your wants, and the problem disappears.

All the best,

	f
-- 
Francis Daly        francis at daoine.org



More information about the nginx mailing list