Hacker News new | past | comments | ask | show | jobs | submit login
Show HN: This weird bug is probably present in your Nginx configuration. (keen.posterous.com)
100 points by k33n on May 16, 2011 | hide | past | favorite | 20 comments



The best configuration for a frontloader that I have found so far is:

    server
    {
            server_name  domainname.com;

            root /srv/sites/domainname.com/httpdocs;
            index index.php;

            access_log  /srv/sites/domainname.com/logs/access.log main;
            error_log   /srv/sites/domainname.com/logs/error.log info;

            try_files $uri $uri/ /index.php?$query_string;

            location ~ \.php$
            {
                    try_files $uri $uri/ /index.php?$query_string =403;

                    include /usr/local/nginx/conf/fastcgi_params;
                    fastcgi_pass                   phpfpm;
                    fastcgi_param SCRIPT_FILENAME  $document_root$fastcgi_script_name;
            }
    }
This will allow static files to be served appropriately, standalone .php files to be run, and anything requiring a frontloader to be passed on accordingly. This should work with most frontloader based frameworks, however certain ones will fail, e.g. symphony (no not symfony)...

EDIT: Added change suggested by nbpoole to fix security vulnerability.


That configuration contains a major security vulnerability, at least if your site allows file uploads (I tested it on my server to be sure). I've written about this issue before: https://nealpoole.com/blog/2011/04/setting-up-php-fastcgi-an...

Edit: You need to copy the try_files line into the PHP location block. I'd recommend using the following line (I just added =403 to the end of yours, so you return a 403 error rather than a 500 error when try_files fails):

   try_files $uri $uri/ /index.php?$query_string =403;


Wow. That's actually really bad. I just patched my php.ini to disallow file uploads because thankfully, we don't need them anyway. Others might not be so lucky though.

Also, I'm assuming that by "move try_files" you really mean "copy a variation of try_files". Sorry for being pedantic, and correct me if I'm wrong.


Yes, you're right. I actually did move the line in my testing, but for it to work properly, you would want to duplicate it. I'll update my comment. :)

I should also mention that merely allowing uploads via php.ini is not an issue: you need to be storing them somewhere web accessible. A good rule of thumb for this: if you could put a PHP script in the uploads directory and have it execute, you have a potential problem.


Thanks - testing and looking in to this now!

Edit: In our server setup we use a completely seperate server to handle file upload and serving - which does not run on php-fpm so thankfully this does not affect us - however still going to make appropriate changes and a note that this is a possible issue.


My block has an expires line in there for caching:

    if (-f $request_filename) {
       expires 30d;
       break;
    }
Where would it go in this new scenario?


try_files allow you to use a named location as the final argument as opposed to a file path. This means you can have

location /static_files { try_files $uri @fallback; }

location @fallback { expires 30d; }


Personally I handle that with Varnish in front of nginx so not really sure.


This is listed on their list of common pitfalls. Apparently there are a huge number of incorrect configurations posted online, with many of these errors in them.

http://wiki.nginx.org/Pitfalls


As the blog post points out, this does look to be a consequence of "IfIsEvil" (http://wiki.nginx.org/IfIsEvil). The behavior of calling the script, not rendering it, and displaying the actual file is very strange though, especially since the if statements appear mutually exclusive. Maybe someone with nginx experience can explain why that would happen.


Yeah, I love nginx and its syntax is great for the most part but it's easy to fall into the trap of thinking that you're writing in a turing-complete language. Something as simple as using multiple conditions in an if statement and redirecting to https if x-forwarded-proto is not set becomes a huge pain:

https://gist.github.com/974486


Firstly, everyone should read http://nginx.org/en/docs/http/request_processing.html before they go trying to "figure out what NginX is doing".

Secondly... because NginX configuration is declarative and ifs are imperative (the opposite paradigm). The imperative ifs are actually compiled into a little mini language and evaluated at runtime, but under the hood, ifs are hacky locations. This is the ultimate reason why If is Evil in NginX, and always will be.

In NginX's declarative setup, you declare each separate location and the behavior that should result within that location. In NginX, only one location wins for ultimate processing! Finally, one must consider the order of evaluation of locations. Specifically, it is something like server if, location = (exact match), location ~ (regex match), location, location if --only one location wins, ever... but it may pass through several locations, especially if you add in error_pages and so on and so forth.

So, coming full circle and attempting to answer the question of just why "calling the script, not rendering it, and displaying the actual file"; well, if you have not properly setup your locations such that PHP files always are routed to a PHP processing location, then you will in fact serve PHP files just like any other file.

NginX's configuration language is much like any language - if you don't really know what it is doing, it is quite easy to make it do something you didn't expect.


That's interesting and, if accurate, that bug is almost certainly affecting all my sites, PHP and otherwise. I'm writing this comment as a mental note to look into it after I get off the airplane.


Same here. Fixed on my local environment, just need to alter my production server asap.


Yeah, same here. I'm surprised I had not heard of this before today, but I know for a fact that it affects at least one of my sites.

Have a good flight. I'm stuck in a car for another few hours, myself.


If you ever stopped by #nginx on Freenode you'd be told multiple times per day. :)


Never thought I had a need to. Now I do. :)


Another method is to automatically serve certain file extensions as static:

  location ~* ^.+.(jpg|jpeg|gif|css|png|js|zip|ico|xml|pdf|html)$ {
  	access_log        off;
  	expires           30d;
  	break;
  }


While not an option for everyone, serving static content from a different domain is another way to avoid this bug.


Offtopic: am I the only one struck by "about 2 hours of WTF'ing"? For a truly elusive bug, one that you stuff and hang over your fireplace after hunting it down, 2 weeks seem more appropriate.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: