3/31/2011 This has been updated to reflect a better configuration to be used with Nginx 0.8/0.9.
Over the last couple of years I’ve been constantly researching for a way to get the PHP environment variables to show up correctly. My latest pains were with PATH_INFO and PHP_SELF, which are now finally solved.
My current configuration are PHP-FPM (5.2.10) and NginX (0.8.29) on a CentOS 5.4 x64 VPS. (As of 2011, I’m now using PHP 5.3.6, Nginx 0.9.6 and FreeBSD 8.2)
Traditionally you would use a PHP configuration such as this:
server {
server_name your-domain.com www.your-domain.com;
location / {
root html/default;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /usr/local/nginx/html/default$fastcgi_script_name;
fastcgi_pass 127.0.0.1:9000;
}
}
Within fastcgi_params would be something like this:
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;
While the above method may seem to work at first, you’ll quickly notice problems when it comes to using $_SERVER[‘PATH_INFO’] and $_PATH[‘PATH_TRANSLATED’], and often enough $_SERVER[‘PHP_SELF’] ends up being set incorrectly when you try to adjust for the two environment variables.
Here is a setup I’ve come to prefer, especially when it comes to having multiple virtual hosts that use PHP. Notable tips are commented below the line.
Simple Nginx configuration file with a single virtual host
worker_processes 1;
pid logs/nginx.pid;
events { worker_connections 1024; }
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
index index.html index.htm index.php;
# Identical to Apache's DirectoryIndex, setting it in
# the http block set it as a default for all server blocks within
server {
listen 80;
# since port 80 is set by default, you do not actually need
# to set this, unless of course you are binding to a specific
# address such as listen server-ip-address:80 or alternate port;
server_name example.com www.example.com;
root html/example.com/;
# You will want to set your root here, since otherwise
# $document_root within the php block will not work
# if you set it in the location block you would also have
# to set the php block within that location as well
location / {
# This would replace the typical mod_rewrite rules for wordpress
# it can also be try_files $uri $uri/ @rewrites; where it goes to a
# location @rewrites { ... } where you can place rewrite rules if a file
# or folder is not found.
try_files $uri $uri/ /index.php;
}
location = /favicon.ico { access_log off; log_not_found off; }
# If you haven't created a favicon for your site, you can keep
# your access and error logs clean by turning off the logs
# when a browser requests the fav icon (its also a good way
# to keep your logs from filling with useless information)
location ~ /\. { access_log off; log_not_found off; deny all; }
# You want to make sure that Nginx does not serve any .hidden files
include php.conf;
# I prefer to keep my php settings in one file, so I can simply
# paste this single line for each of my virtual hosts
}
}
Now the php.conf file (which I’ve created in the /conf folder with nginx.conf)
fastcgi_intercept_errors on;
# this will allow Nginx to intercept 4xx/5xx error codes
# Nginx will only intercept if there are error page rules defined
# -- This is better placed in the http {} block as a default
# -- so that in the case of wordpress, you can turn it off specifically
# -- in that virtual host's server block
location ~ \.php {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
# A handy function that became available in 0.7.31 that breaks down
# The path information based on the provided regex expression
# This is handy for requests such as file.php/some/paths/here/
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
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 SCRIPT_FILENAME $document_root$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;
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;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
}
There you have it, PHP should now have correct environment variables. For example http://www.example.com/php.php/a/path/string/?var=foo would render the following results:
$_SERVER["QUERY_STRING"] -> var=foo
$_SERVER["SCRIPT_NAME"] -> /php.php
$_SERVER["SCRIPT_FILENAME"] -> /usr/local/nginx/html/default/php.php
$_SERVER["REQUEST_URI"] -> /php.php/a/path/string/?var=foo
$_SERVER["DOCUMENT_URI"] -> /php.php/a/path/string/
$_SERVER["DOCUMENT_ROOT"] -> /usr/local/nginx/html/default
$_SERVER["PATH_INFO"] -> /a/path/string/
$_SERVER["PATH_TRANSLATED"] -> /usr/local/nginx/html/default/a/path/string
$_SERVER["PHP_SELF"] -> /php.php/a/path/string/
So there you have it. A simple php block that will correctly assign the path environment variables, without having to use multiple blocks and patterns. And quite easy to simply assign to a new virtual host by simply pasting the include php; line.
First of all, thanks for sharing this! I’ve successfully replicated your setup, and for /php.php/a/path/string/?var=foo I get the same results as you. I do have a question however:
What should the expected result be if php.php is in a subfolder, i.e. /folder/php.php/a/path/string/?var=foo?
Currently, PATH_TRANSLATED for /php.php/ and /folder/php.php/ is exactly the same, I wonder if this is indeed correct or if /folder/ should go in there too.
When using paths after the php file name, PATH_INFO should show up as the path trailing the script, I would assume this should be the same for PATH_TRANSLATED as well when using a path trailing the file name. I do however advise that people concentrate on parsing the REQUEST_URI as opposed to relying on PATH_INFO and PATH_TRANSLATED. Most systems now days use mod_rewrite or similar and use the request_uri instead of the older path_info.
Also ran into a PHP security issue after following this guide (mainly because my php.conf did not include the folder restrictions anymore I had in place before).
http://cnedelcu.blogspot.com/2010/05/nginx-php-via-fastcgi-important.html
Yes, I’d prefer mod_rewrite over file.php/something too but unfortunately there are still a lot of scripts around that do this, both in Open Source and Proprietary scripts. 🙁
I replicated your config but my scripts were returning a 404 in the error log. I had to have a SCRIPT_FILENAME manually configured for each vhost which solved the problem.
Well that’s not really *solving* it but rather taking a long work around. If you have the root defined in the server { } block and not location { } block then script filename would be automatic in the fastcgi params.