keepcalculator

06 Numbers and Powers

Fix nginx downloading PHP files instead of running them

Card No. 06·4690 · Updated · Standard formula, shown below

If nginx is downloading your PHP files instead of executing them, the fix is to add a location block that passes “.php” requests to PHP-FPM using the fastcgi_pass directive, and to ensure the file path is properly set with fastcgi_param SCRIPT_FILENAME. Without this configuration, nginx doesn’t know how to handle PHP files and simply serves them as static files, which your browser then downloads. Below is the exact configuration and the reasoning behind each line, plus common pitfalls and troubleshooting steps.

Why nginx downloads PHP files

Nginx is a web server that excels at serving static files (HTML, CSS, images) and proxying requests to other servers. Unlike Apache, which often has PHP built into the server process (via mod_php), nginx has no native PHP interpreter. When a request for a “.php” file arrives, nginx must hand it off to a separate PHP processing service—usually PHP-FPM—via the FastCGI protocol. If no such handoff is configured, nginx treats the “.php” file as a regular file and sends its raw contents to the browser. The browser, seeing a file it can’t render, offers it as a download.

The core fix: a location block for PHP

To fix the issue, you need to add a location block in your server configuration that matches “.php” requests and forwards them to PHP-FPM. Here’s a minimal, working example for a typical nginx site:

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

    location / {
        try_files $uri $uri/ =404;
    }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}

The critical lines are:

  • location ~ \.php$ — This matches any request ending in “.php”. The tilde (~) means case-sensitive regex; use ~* for case-insensitive.
  • include fastcgi_params; — Loads standard FastCGI parameters, including SCRIPT_FILENAME in some distributions, but it’s safer to set it explicitly.
  • fastcgi_pass — Tells nginx where to send the request. This can be a Unix socket (as shown) or a TCP address like 127.0.0.1:9000.
  • fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; — This is the most common missing piece. Without it, PHP-FPM doesn’t know the full path to the file, and you’ll get a blank page or “File not found” error.

FastCGI parameters explained

The fastcgi_params file (usually located at /etc/nginx/fastcgi_params) contains standard parameters like REQUEST_METHOD, QUERY_STRING, and CONTENT_TYPE. These are necessary for PHP to understand the request. However, SCRIPT_FILENAME is often not included, or is set to a generic value. That’s why you must set it explicitly. The variable $document_root refers to the root directive (e.g., /var/www/html), and $fastcgi_script_name is the URI path (e.g., /index.php). Combining them gives the absolute path on disk.

If you’re using a PHP-FPM pool that chroots or has a different root, you may need to adjust this. But for standard setups, the above works.

Common mistakes and troubleshooting

Even with the location block, you might still see downloads. Here are the usual culprits:

  • Wrong socket path: The fastcgi_pass must match the socket or port where PHP-FPM is listening. Check with php-fpm8.1 -t or look at /etc/php/8.1/fpm/pool.d/www.conf for the listen directive. If it’s a Unix socket, ensure the path exists and nginx has permission to access it.
  • Missing fastcgi_param SCRIPT_FILENAME: Even if fastcgi_params includes it, some distributions omit it. Always set it explicitly in your location block.
  • File permissions: PHP-FPM runs as a specific user (e.g., www-data). If that user can’t read the PHP file, you’ll get a 403 or 500 error, not a download. But if the file is readable, it will be executed.
  • Multiple location blocks: If you have a location / block with a try_files that catches everything, make sure the regex location for “.php” is not overridden. Regex locations are checked after prefix locations, but the first match wins. If you have a prefix location like location /files/ that matches before the regex, it might serve the file as static. Use ^~ to prevent regex checking if needed.
  • Nginx not reloaded: After changing the config, run nginx -t to test, then systemctl reload nginx (or nginx -s reload). If you forget to reload, the old config remains.

Alternative: using a TCP port for PHP-FPM

Instead of a Unix socket, some setups use a TCP port. This is common when PHP-FPM runs on a different server or in a container. The fastcgi_pass would then be 127.0.0.1:9000 (or the appropriate IP/port). The rest of the configuration stays the same. TCP is slightly slower but more flexible for distributed setups. Ensure your firewall allows the connection.

Security considerations

When configuring PHP-FPM, avoid passing user input to the SCRIPT_FILENAME parameter. The standard $document_root$fastcgi_script_name is safe because nginx normalizes the URI. However, be careful if you use fastcgi_param SCRIPT_FILENAME $request_filename with a try_files that could point to a non-existent file. Also, never set fastcgi_pass to a socket or port that is publicly accessible without authentication. PHP-FPM should listen only on localhost or a private network.

Frequently Asked Questions

Why is my PHP file downloading instead of executing?

Because nginx is not configured to pass “.php” requests to PHP-FPM. The server treats the file as a static asset and sends its raw contents. Adding the location block with fastcgi_pass fixes this.

I added the location block, but I get a blank page. What’s wrong?

Blank pages usually mean PHP-FPM is running but can’t find the file. Check that SCRIPT_FILENAME is set correctly. Also verify the file exists at the path specified by $document_root and that PHP-FPM has read permissions.

Should I use a Unix socket or TCP for fastcgi_pass?

Unix sockets are faster and more secure for local communication. TCP is useful when PHP-FPM is on a different machine or container. Both work; choose based on your architecture.

How do I find the correct PHP-FPM socket path?

Look in the PHP-FPM pool configuration file, typically /etc/php/<version>/fpm/pool.d/www.conf, for the listen directive. It might be something like listen = /run/php/php8.1-fpm.sock. You can also run ss -lx | grep php to list Unix sockets.

My site uses a framework like Laravel or WordPress. Do I need a different configuration?

No, the core PHP location block is the same. However, frameworks often have a front controller (e.g., index.php) and use pretty URLs. You’ll need a try_files directive to route all requests to the front controller. For example: try_files $uri $uri/ /index.php?$query_string;. The PHP location block remains unchanged.

Why does it work on Apache but not nginx?

Apache has mod_php which embeds the PHP interpreter into the server, so it executes PHP files automatically. Nginx is a reverse proxy and needs an external PHP processor. This is a design difference, not a bug. The nginx approach is generally more efficient but requires this explicit configuration.

Related

Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.

More cards in this drawer

The whole Numbers and Powers drawer →