# 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.

**Tested on**

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

## The short answer

One public edge, many private runtimes:

```text
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          PHP
```

Only 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

| 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

- **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, Java `WEB-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

- **IIS + ARR** if you already run IIS, need Windows auth, or want IIS logging.
- **Nginx** if you want simple `server` blocks 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

1. Edge (IIS/Nginx) can start first; it returns 502 until backends are up.
2. Backend services start automatically.
3. 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

```powershell
Get-NetTCPConnection -State Listen |
  Where-Object LocalPort -in 80,443,3000,8000,8080 |
  Select-Object LocalAddress,LocalPort,OwningProcess
Get-CimInstance Win32_Process -Filter "Name='node.exe' OR Name='java.exe' OR Name='python.exe'" |
  Select-Object ProcessId,Name,ExecutablePath,CommandLine
```

Cross-check PID → app → port → hostname. If a port is owned by the wrong PID, stop guessing and fix the service definition.

> **Caution: Do not use ports as your app inventory**
>
> A port map that exists only in your head is an outage waiting to happen. Keep the table in the repo, with the service name and owner.

## 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.

## Sources

- [Microsoft Learn — ARR reverse proxy](https://learn.microsoft.com/en-us/iis/extensions/url-rewrite-module/reverse-proxy-with-url-rewrite-v2-and-application-request-routing)
- [nginx — Reverse proxy](https://nginx.org/en/docs/http/ngx_http_proxy_module.html)
- [Microsoft Learn — IIS application pools](https://learn.microsoft.com/en-us/iis/configuration/system.applicationhost/applicationpools/)

---

Source: https://windows.homes/en/server/runtimes/multi-runtime/
