Nginx and public_html, Part 2: PHP Scripts

Following up on my previous post on user web directories and nginx, here is an improved solution that executes PHP scripts in user home directories as well.

This solution assumes that you are running a PHP FCGI server. I am using the Ubuntu php5-cgi package. I run my server on port 9000; if you do otherwise, be sure to change the values below to reflect that.

First, you will need a configuration file to set up the fastcgi parameters in Nginx. I copied the contents of this file from the relevant part of this website (much of my technique is based on this website) and placed it into /etc/nginx/fastcgi_params.

fastcgi_param  QUERY_STRING       $query_string;
fastcgi_param  REQUEST_METHOD     $request_method;
fastcgi_param  CONTENT_TYPE       $content_type;
fastcgi_param  CONTENT_LENGTH     $content_length;

fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param  REQUEST_URI        $request_uri;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  DOCUMENT_ROOT      $document_root;
fastcgi_param  SERVER_PROTOCOL    $server_protocol;

fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;

fastcgi_param  REMOTE_ADDR        $remote_addr;
fastcgi_param  REMOTE_PORT        $remote_port;
fastcgi_param  SERVER_ADDR        $server_addr;
fastcgi_param  SERVER_PORT        $server_port;
fastcgi_param  SERVER_NAME        $server_name;

# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param  REDIRECT_STATUS    200;

Next, we need to instruct Nginx to process PHP files by invoking the fastcgi server. Recall that the public_html solution I devised relies on using rewrite rules to cause Nginx to enter different “states” of processing. The state starting with prefix “f~/” is where Nginx actually locates the file in the user’s home directory:

    location f~/ {
        alias /home/$homedir/public_html/;
        if (-d /home/$homedir/public_html$filedir) {
            rewrite ^f~/(.*) ~/$1;
        }
    }

To deal with PHP scripts, we install a similar processing rule to capture rewritten URLs that have the f~/ prefix and end in .php.

    location ~ ^f~/.*\.php$ {
        fastcgi_pass   localhost:9000;

        include /etc/nginx/fastcgi_params;
        fastcgi_param  SCRIPT_FILENAME /home/$homedir/public_html$filedir;
    }

We also need to deal with cases where a directory contains a file index.php. This is done by manipulating the processing states that deal with directories. I do this by introducing a new state, which is denoted below as “p~/.”

The full configuration file appears below.

    # For requests starting with a tilde, break them into three components:
    # 1. The username, everything after the tilde up to the first slash
    # 2. The file location, everything after the username up to the last slash
    # 3. The trailing slash(es)
    # Then, rewrite to go to the f~/ branch.
    location /~ {
        if ($request_uri ~ ^/~([^/]*)(/.*[^/]|)(/*)$) {
            set $homedir $1;
            set $filedir $2;
            set $trailingslashes $3;
            rewrite ^/~([^/]*)(/|$)(.*)$ f~/$3;
        }
    }                                                         

    # Here, the user-directory components have been parsed. Use an alias to set
    # the file directory prefix. But if the file at the requested URI is a
    # directory, we jump to the ~/ branch for additional processing.
    location f~/ {
        alias /home/$homedir/public_html/;
        if (-d /home/$homedir/public_html$filedir) {
            rewrite ^f~/(.*) ~/$1;
        }
    }                                                         

    # Similar to the above, but for PHP scripts.
    location ~ ^f~/.*\.php$ {
        fastcgi_pass   localhost:9000;

        include /etc/nginx/fastcgi_params;
        fastcgi_param  SCRIPT_FILENAME /home/$homedir/public_html$filedir;
    }

    # Here, the request is for a directory in a user's home directory. We check
    # that the request URI contained trailing slashes. If it did not, then we
    # add the slashes and send a redirect. This circumvents Nginx's faulty
    # internal slash-adding mechanism.
    location ~/ {
        autoindex on;
        alias /home/$homedir/public_html/;
        if ($trailingslashes = "") {
            rewrite .* /~$homedir$filedir/ redirect;
        }

        # If there is a PHP index page, then go to the state for that.
        if (-f /home/$homedir/public_html$filedir/index.php) {
            rewrite .* p~/;
        }
    }

    # This state is reached when the request is for a directory and there is a
    # PHP index page for that directory. In this case, we set up FCGI and run
    # the script.
    location = p~/ {
        fastcgi_pass   localhost:9000;

        include /etc/nginx/fastcgi_params;
        fastcgi_param  SCRIPT_FILENAME
                       /home/$homedir/public_html$filedir/index.php;
    }

As always, questions, comments, and improvements are always appreciated.

Comments 7