Fixing Nginx Rewrite Rule That Causes PHP Files to Download Instead of Executing


2 views

When working with Nginx rewrites for PHP applications, you might encounter a situation where your rewritten URL causes the PHP file to download instead of being executed. This typically occurs with a configuration like:

location / {
    index index.php index.html;
    rewrite ^/test$ /test.php break;
}

location ~ \\.php$ {
    fastcgi_pass 127.0.0.1:9000;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME /home/sites/default$fastcgi_script_name;
    fastcgi_index index.php;
}

The break flag in the rewrite rule stops all further processing of rewrite directives, which means the request never reaches the PHP handler location block. Additionally, the fastcgi parameters might not be properly set for rewritten URLs.

Solution 1: Using try_files instead of rewrite

A more modern approach is to replace the rewrite with try_files:

location / {
    try_files $uri $uri/ /test.php$is_args$args;
}

Solution 2: Proper rewrite with last flag

If you must use rewrite, ensure it reaches the PHP handler:

location / {
    rewrite ^/test$ /test.php last;
}

location ~ \\.php$ {
    try_files $uri =404;
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
}

Solution 3: Full path specification

For more complex setups, explicitly specify the document root:

location ~ \\.php$ {
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_param SCRIPT_FILENAME /full/path/to/your/site$fastcgi_script_name;
    include fastcgi_params;
}

Check these common issues:

  • Verify PHP-FPM is running and listening on the correct port
  • Ensure file permissions allow Nginx to read PHP files
  • Check error logs: tail -f /var/log/nginx/error.log
  • Test with a simple PHP file containing just <?php phpinfo(); ?>

A production-ready configuration might look like:

server {
    listen 80;
    server_name example.com;
    root /var/www/html;
    index index.php;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \\.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}

When your Nginx rewrite rule causes PHP files to download rather than execute, this typically indicates a breakdown in the FastCGI processing chain. The symptom suggests that while the rewrite itself works (the correct file is being located), the PHP interpreter isn't properly handling the request.

Here's the original setup that triggers the download behavior:

location / {
    index index.php index.html;
    rewrite ^/test$ /test.php break;
}

location ~ \.php$ {
    fastcgi_pass    127.0.0.1:9000;
    include         fastcgi_params;
    fastcgi_param SCRIPT_FILENAME /home/sites/default$fastcgi_script_name;
    fastcgi_index   index.php;
}

The break flag in the rewrite rule stops all further processing of rewrite directives, which can interfere with the PHP location block's ability to handle the request. Additionally, your FastCGI parameters might not be properly propagated.

Solution 1: Using try_files Instead of rewrite

This is the more modern and recommended approach:

location / {
    try_files $uri $uri/ /index.php?$args;
}

location ~ \.php$ {
    try_files $uri =404;
    fastcgi_pass    127.0.0.1:9000;
    include         fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_index   index.php;
}

Solution 2: Proper Rewrite with Last Flag

If you must use rewrite, modify it like this:

location / {
    index index.php index.html;
    rewrite ^/test$ /test.php last;
}

location ~ \.php$ {
    fastcgi_pass    127.0.0.1:9000;
    include         fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_index   index.php;
}

Notice the change in SCRIPT_FILENAME from your original configuration. Using $document_root ensures the correct path resolution:

fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

After making changes, always test your configuration:

nginx -t
systemctl reload nginx

For more complex setups, ensure your PHP-FPM pool matches your Nginx configuration:

[www]
listen = 127.0.0.1:9000
listen.allowed_clients = 127.0.0.1
user = www-data
group = www-data
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3

Remember to restart PHP-FPM after configuration changes:

systemctl restart php-fpm