# Nginx + PHP FastCGI on Windows: no php-fpm, no fantasy

> Run php-cgi as a FastCGI listener, point Nginx at it, set SCRIPT_FILENAME and fix_pathinfo, and know why IIS FastCGI is often the better Windows choice.

**Tested on**

- Build: 26100.33650
- Edition: Standard / Datacenter
- Architecture: x64
- Evidence: E1
- Verified: 2026-10-29

## The short answer

Nginx does **not** spawn PHP workers. On Windows you start `php-cgi.exe` as a FastCGI listener and point Nginx at it:

```bat
C:\PHP\8.4\php-cgi.exe -b 127.0.0.1:9000 -c C:\PHP\8.4\php.ini
```

Then:

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

There is no official Windows php-fpm. If a guide says “install php-fpm on Windows”, it is using a third-party build or confusing FastCGI with FPM.

## Full server block

```nginx
server {
    listen 8084;
    server_name php.local;
    root C:/sites/php;
    index index.php index.html;

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

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param HTTPS $https if_not_empty;
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_read_timeout 60s;
    }
}
```

`SCRIPT_FILENAME` is the one parameter people forget. Without it, PHP-CGI returns “No input file specified” or a blank page.

## php.ini for this mode

```ini
cgi.fix_pathinfo=0
fastcgi.impersonate=1
date.timezone=UTC
expose_php=Off
display_errors=Off
log_errors=On
```

`cgi.fix_pathinfo=0` plus `try_files`/`=404` prevents a nonexistent `.php` path from being passed to PHP-CGI. It is not a substitute for upload validation.

## Process supervision

PHP-CGI is a process you must keep alive:

- run it under WinSW/NSSM or a scheduled task with restart;
- set `PHP_FCGI_MAX_REQUESTS` (e.g. 500) so workers recycle;
- log stderr/stdout somewhere outside the web root;
- monitor the listener (`Test-NetConnection 127.0.0.1 9000`);
- do not start a second listener on 9000.

For a Windows-only server, **IIS FastCGI** usually supervises PHP better than a hand-wrapped `php-cgi`; use Nginx+PHP when you already standardize on Nginx or need its config model.

## Verify

```powershell
& C:\PHP\8.4\php-cgi.exe -v
Test-NetConnection 127.0.0.1 -Port 9000
Set-Content C:\sites\php\info.php '<?php phpinfo();' -Encoding ASCII
curl.exe -I -H "Host: php.local" http://127.0.0.1:8084/info.php
```

Remove `info.php` after the test.

> **Caution: FastCGI is not a per-request CGI**
>
> A single `php-cgi -b` listener handles multiple requests. Starting one process per request is the old CGI model and will collapse under load. Use a supervisor and recycle deliberately.

## Lab boundary

E1 for Nginx+PHP. The lab installed PHP 8.4.27 and verified IIS FastCGI, but did not run php-cgi behind Nginx. The Nginx config is the next verification step, not a reproduced claim.

## Sources

- [nginx — FastCGI module](https://nginx.org/en/docs/http/ngx_http_fastcgi_module.html)
- [PHP manual — FastCGI Process Manager](https://www.php.net/manual/en/install.fpm.php)
- [PHP — Windows downloads](https://windows.php.net/downloads/releases/)

---

Source: https://windows.homes/en/server/web/nginx-php/
