PHP, Java, Node and Python on one Windows server: the coexistence model
Design one Windows host with IIS/Nginx on 443, isolated runtimes on internal ports, one supervisor per app, and a port/identity map you can audit.
- Build
- 26100.33650
- Edition
- Standard / Datacenter
- Architecture
- x64
- Support
- Documentation (E1)
- Evidence
- E1Official documentation
- Verified
- 2026-10-29
- Page type
- Concept
The short answer
Section titled “The short answer”One public edge, many private runtimes:
443 IIS/Nginx (TLS, routing, logs) ├─ app-a.local -> 127.0.0.1:3000 Node ├─ app-b.local -> 127.0.0.1:8080 Tomcat ├─ app-c.local -> 127.0.0.1:8000 Python/Waitress └─ php.local -> FastCGI PHPOnly the edge listens on 80/443. Everything else binds 127.0.0.1 (or a private IP) and is reached through the proxy or FastCGI.
The map you must write down
Section titled “The map you must write down”| App | Runtime | Internal endpoint | Supervisor | Identity | Logs |
|---|---|---|---|---|---|
| app-a | Node 20 | 127.0.0.1:3000 | WinSW | svc-app-a | C:\logs\app-a |
| app-b | Tomcat/JDK 17 | 127.0.0.1:8080 | Tomcat service | svc-app-b | C:\tomcat...\logs |
| app-c | Python 3.11 | 127.0.0.1:8000 | NSSM | svc-app-c | C:\logs\app-c |
| php | PHP 8.4 | FastCGI pipe/port | IIS FastCGI | app pool | C:\inetpub\logs |
If two rows share a port, one app will fail to start or the proxy will route to the wrong process.
Isolation rules
Section titled “Isolation rules”- One app, one service identity. Do not run all apps as LocalSystem.
- One app, one log directory. Shared logs make incidents unreadable.
- One app, one dependency tree. Node
node_modules, Python venv, JavaWEB-INF/lib; do not share global packages. - One version per app. Pin absolute paths; no global PATH switch.
- One supervisor per app. Do not let IIS HPH and a Windows service both start the same process.
Edge choice
Section titled “Edge choice”- IIS + ARR if you already run IIS, need Windows auth, or want IIS logging.
- Nginx if you want simple
serverblocks and a small config surface. - Do not run both on 443. Pick one edge and make the other internal-only.
TLS terminates at the edge. Backends speak HTTP on localhost; do not expose their ports through the firewall. For PHP, FastCGI is not an HTTP port; it is a pipe/port between IIS/Nginx and php-cgi.
Startup order
Section titled “Startup order”- Edge (IIS/Nginx) can start first; it returns 502 until backends are up.
- Backend services start automatically.
- Health checks distinguish “edge up, app down” from “everything down”.
A 502 is useful: it says the edge is alive and the upstream is not. A connection refused on 443 says the edge is down.
Verify the map
Section titled “Verify the map”Get-NetTCPConnection -State Listen | Where-Object LocalPort -in 80,443,3000,8000,8080 | Select-Object LocalAddress,LocalPort,OwningProcessGet-CimInstance Win32_Process -Filter "Name='node.exe' OR Name='java.exe' OR Name='python.exe'" | Select-Object ProcessId,Name,ExecutablePath,CommandLineCross-check PID → app → port → hostname. If a port is owned by the wrong PID, stop guessing and fix the service definition.
Lab boundary
Section titled “Lab boundary”E1. The lab verified IIS/PHP and Nginx separately; it did not run Node/Java/Python behind one edge. The map is the design to validate on Server 2025.