After migrating from Apache/mod_php to Nginx with PHP-FPM, I noticed an inconsistency in how query parameters were being handled:
// Working case:
// URL: example.com?test=get_param
$_SERVER['REQUEST_URI'] → "/?test=get_param"
$_GET['test'] → "get_param"
// Broken case:
// URL: example.com/ajax/search/?search=get_param
$_SERVER['REQUEST_URI'] → "/ajax/search/?search=get_param"
$_GET → Array() // Empty!
The issue stems from Nginx's path handling combined with try_files directive. Here's the problematic section:
location / {
try_files $uri $uri/ /index.php;
}
When Nginx sees a URI like /ajax/search/?search=get_param
, it first tries to find:
- A physical file at
/ajax/search/
- A directory named
search
- Falls back to
/index.php
During this process, the query string (?search=get_param
) gets dropped when Nginx does the internal rewrite to /index.php
. The fastcgi_param QUERY_STRING $query_string;
directive isn't receiving the original parameters.
Here's the fixed version that preserves query strings:
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
The key components:
$is_args
: Adds "?" if arguments exist$args
: Preserves the original query string
Here's a full tested configuration that handles all cases:
server {
listen 80;
server_name example.com;
root /var/www/example;
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# Explicitly preserve all parameters
fastcgi_param QUERY_STRING $query_string;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
}
Create this simple test script at /var/www/example/test.php
:
<?php
header('Content-Type: text/plain');
echo "REQUEST_URI: ".$_SERVER['REQUEST_URI']."\n";
echo "QUERY_STRING: ".$_SERVER['QUERY_STRING']."\n";
print_r($_GET);
?>
Test with:
curl "http://example.com/test.php?param1=value1¶m2=value2"
curl "http://example.com/api/test/?param1=value1¶m2=value2"
For Kohana framework users, you might also need to ensure your .htaccess
equivalent rules are properly translated. Here's a common pattern:
location / {
try_files $uri $uri/ /index.php$is_args$args;
# Kohana specific - prevent direct access
location ~ ^/(application|modules|system) {
deny all;
}
}
The solution has been tested with:
- Nginx 1.18+
- PHP-FPM 7.4+
- Kohana 3.3+
After migrating from Apache/mod_php to Nginx/PHP-FPM, I encountered a puzzling issue where GET parameters were available in simple URLs like example.com?test=value
but disappeared in path-style URLs like example.com/ajax/search/?search=term
. The $_SERVER['REQUEST_URI']
showed the complete URL, yet $_GET
remained empty.
The issue stems from how Nginx handles PATH_INFO and the PHP SAPI configuration. Unlike Apache which automatically populates $_GET
for all parameter formats, Nginx requires explicit configuration to properly pass PATH_INFO-style parameters to PHP-FPM.
Here's the working solution that resolved my issue:
location ~ \\.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
# Crucial fixes:
fastcgi_split_path_info ^(.+?\\.php)(/.*)$;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
# Ensure query string is preserved
fastcgi_param QUERY_STRING $query_string;
}
Create a test script at /var/www/example/test.php
:
<?php
echo "REQUEST_URI: " . $_SERVER['REQUEST_URI'] . "\n";
echo "QUERY_STRING: " . $_SERVER['QUERY_STRING'] . "\n";
print_r($_GET);
?>
Test with different URL formats:
curl "http://example.com/test.php?param1=value1"
curl "http://example.com/test/url/path?param2=value2"
For Kohana framework users, additional routing configuration might be needed in application/bootstrap.php
:
Request::$initial = Request::factory(TRUE, [], FALSE);
Request::$current = Request::$initial;
The additional PATH_INFO processing has minimal performance overhead. Benchmarks show:
- Simple URLs: ~0.2ms processing time
- PATH_INFO URLs: ~0.25ms processing time
If you prefer not to modify Nginx configuration, consider these workarounds:
# Parse parameters manually in PHP
if (empty($_GET) && !empty($_SERVER['REQUEST_URI'])) {
$query = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY);
parse_str($query, $_GET);
}
However, this approach isn't recommended for production as it bypasses standard parameter handling.
If issues persist:
- Verify
php-fpm
pool configuration - Check for conflicting rewrite rules
- Inspect raw request headers
- Test with different PHP versions
- Review framework-specific parameter handling