Guide · Last updated: November 2026
An old project needs PHP 7.4 or 8.2, the new one needs 8.5, and switching back and forth is getting old. Windows can run them all at the same time. Most tools just aren't set up that way.
Below: the short answer, what XAMPP, Laragon and Herd actually do, how to add a second PHP version to XAMPP, and how it all works underneath if you want to build it yourself.
You can build either setup by hand. The rest of this guide shows how. Or let a tool that runs versions side by side do it for you: that's what I built ForgeKit for.
Two different things both get called "multiple PHP versions":
How the common Windows tools handle it:
php folder. See does XAMPP support PHP 8.5.bin\php and switch the active one from the menu. One version runs at a time. Commercial use needs a licence (Laragon's licence explained).Switching is enough if you work on one project at a time. If you maintain an old client site while building something new, side by side saves you a restart and a broken site every time you change context.
XAMPP's Apache can do this without a second XAMPP. Keep its built-in PHP for most sites, and send one site to another PHP version running as a separate FastCGI process. It's the "one server, several PHP versions" approach described further down.
C:\php\8.5. Create its php.ini from php.ini-development.C:\php\8.5\php-cgi.exe -b 127.0.0.1:9085xampp\apache\conf\httpd.conf, make sure mod_proxy and mod_proxy_fcgi are loaded (uncomment their LoadModule lines).<FilesMatch "\.php$"> SetHandler "proxy:fcgi://127.0.0.1:9085" </FilesMatch>The catches:
php-cgi.exe has to be running before you open the site, and it doesn't start with XAMPP. You start it yourself or with a script, every time.php.ini, port and start command.artisan use whichever php.exe is first in your PATH, not the site's version. Call the right one by its full path.It works, and for one extra version it's a reasonable afternoon. Past two or three versions, keeping track of the processes and ports becomes its own job, which is the part ForgeKit automates.
On Windows, PHP, Apache and Nginx ultimately run as ordinary processes:
php.exe
php-cgi.exe
httpd.exe
nginx.exeThey are executable files like any other Windows application.
The important difference is that server processes can listen on network ports.
When you open:
http://localhost:8080your browser is connecting to whichever process is listening on port 8080 of your local machine.
Another server can listen on 8081. Another can listen on 8082.
There is therefore no fundamental reason you cannot run several PHP environments simultaneously. The main rule is that two processes cannot bind to the same address and port at the same time. Apache itself lists an existing server already occupying the configured port as a common reason another Apache instance cannot start.
The basic idea can be as simple as:
Apache instance A → port 8081 → PHP 5.6
Apache instance B → port 8082 → PHP 7.4
Nginx instance C → port 8083 → PHP 8.4All three can continue running at once.
No container is required. They are ordinary native Windows processes with separate configurations.
PHP includes a small development server of its own.
Open two terminals and run two different PHP executables:
C:\php\5.6\php.exe -S 127.0.0.1:8056 -t C:\sites\legacy
C:\php\8.4\php.exe -S 127.0.0.1:8084 -t C:\sites\modernYou now have:
http://127.0.0.1:8056 → PHP 5.6
http://127.0.0.1:8084 → PHP 8.4Both versions are running simultaneously because they are separate processes listening on separate ports.
PHP's built-in server is intended for development and testing rather than as a full web server. On Windows it also runs as a single-threaded process, so it is useful as a demonstration or lightweight project server, but Apache or Nginx gives you a more complete environment.
The most straightforward full setup is to create one web-server environment for each PHP version.
For example:
Environment: legacy
Apache: 127.0.0.1:8081
PHP: 5.6
Environment: client-work
Apache: 127.0.0.1:8082
PHP: 7.4
Environment: modern
Nginx: 127.0.0.1:8083
PHP: 8.4Each environment gets its own:
php.ini Apache supports specifying a different server root and configuration file when starting httpd.exe, while Nginx supports a separate configuration file and prefix directory through its -c and -p arguments. That makes multiple independent instances possible without duplicating every binary.
It is not literally unlimited. You are limited by available ports, memory and what remains sensible to manage. But for local development, you can comfortably run several independent environments.
An Apache configuration for one environment might include:
ServerRoot "C:/dev/apache-legacy"
Listen 127.0.0.1:8081
PidFile "logs/httpd.pid"
ErrorLog "logs/error.log"
<VirtualHost 127.0.0.1:8081>
ServerName old-project.test
DocumentRoot "C:/sites/old-project/public"
<Directory "C:/sites/old-project/public">
AllowOverride All
Require all granted
</Directory>
</VirtualHost>A second Apache instance can use another configuration:
ServerRoot "C:/dev/apache-modern"
Listen 127.0.0.1:8082
PidFile "logs/httpd.pid"
ErrorLog "logs/error.log"
<VirtualHost 127.0.0.1:8082>
ServerName modern-project.test
DocumentRoot "C:/sites/modern-project/public"
<Directory "C:/sites/modern-project/public">
AllowOverride All
Require all granted
</Directory>
</VirtualHost>Start them with separate configuration files:
C:\apache\bin\httpd.exe -f C:\dev\apache-legacy\conf\httpd.conf
C:\apache\bin\httpd.exe -f C:\dev\apache-modern\conf\httpd.conf Apache officially supports selecting an alternate configuration with -f and a different server root with -d.
If PHP is loaded directly into Apache as a module, each Apache instance can load the PHP module belonging to that environment. Windows PHP provides Thread Safe builds for Apache mod_php, while Non-Thread-Safe builds are intended for FastCGI setups.
A separate server does not mean one server per website.
One Apache or Nginx instance can serve many projects through virtual hosts, called VirtualHost blocks in Apache and server blocks in Nginx.
For example, the PHP 7.4 environment could contain:
client-one.test
client-two.test
old-laravel.test
wordpress-seven.testAll four domains can point to the same Apache instance and therefore use the same PHP 7.4 environment.
Another group of sites can point to the PHP 8.4 instance:
new-laravel.test
experiment.test
api-project.testThe browser sends the requested hostname in the HTTP request. Apache or Nginx examines that hostname and selects the matching virtual host and document root. Apache supports multiple name-based virtual hosts on one IP address and port.
Your Windows hosts file provides the local name resolution:
127.0.0.1 old-laravel.test
127.0.0.1 new-laravel.test
127.0.0.1 experiment.testThe hosts file tells Windows that those domains belong to your own machine.
The Apache or Nginx configuration then decides which folder and PHP environment should handle each domain. Apache's documentation explicitly notes that virtual-host configuration does not create DNS entries automatically; local hostnames still need DNS or hosts-file entries.
There is one inconvenience with running several complete web-server instances.
You probably want URLs like:
http://legacy.test
http://modern.testrather than:
http://legacy.test:8081
http://modern.test:8082 Only one process can listen on 127.0.0.1:80, so the individual backend servers cannot all use port 80.
The usual solution is a small front router or reverse proxy:
Browser
|
v
Router on port 80
|
+-- legacy.test → Apache PHP 5.6 on port 8081
|
+-- client.test → Apache PHP 7.4 on port 8082
|
+-- modern.test → Nginx PHP 8.4 on port 8083The router owns ports 80 and 443. It looks at the requested hostname and forwards the request to the correct backend server.
For example:
server {
listen 80;
server_name legacy.test;
location / {
proxy_set_header Host $host;
proxy_pass http://127.0.0.1:8081;
}
}
server {
listen 80;
server_name modern.test;
location / {
proxy_set_header Host $host;
proxy_pass http://127.0.0.1:8083;
}
}Reverse proxies present themselves as the single public-facing server while forwarding requests to hidden backend servers. Both Apache and Nginx support this model.
This is broadly the model ForgeKit uses: backend environments run on their own internal ports, while a local router lets every site use a clean domain.
You do not have to run several Apache or Nginx servers.
Another clean architecture is:
One Nginx server
Multiple PHP FastCGI processesNginx handles HTTP for every site on port 80.
Each PHP version runs independently on a different internal FastCGI port:
PHP 5.6 → 127.0.0.1:9056
PHP 7.4 → 127.0.0.1:9074
PHP 8.4 → 127.0.0.1:9084The browser never connects to these PHP ports directly. They are only used for communication between Nginx and PHP.
On Windows, the executable is normally:
php-cgi.exe not PHP-FPM. PHP's documentation notes that PHP-FPM is unavailable on Windows, while Nginx's FastCGI module forwards requests to a FastCGI backend with fastcgi_pass.
A basic local setup can start the PHP processes like this:
C:\php\5.6\php-cgi.exe -b 127.0.0.1:9056 -c C:\php\5.6\php.ini
C:\php\7.4\php-cgi.exe -b 127.0.0.1:9074 -c C:\php\7.4\php.ini
C:\php\8.4\php-cgi.exe -b 127.0.0.1:9084 -c C:\php\8.4\php.iniThen each Nginx server block chooses its required PHP backend.
server {
listen 80;
server_name legacy.test;
root C:/sites/legacy/public;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass 127.0.0.1:9056;
}
}server {
listen 80;
server_name modern.test;
root C:/sites/modern/public;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass 127.0.0.1:9084;
}
}Both sites use the same Nginx process and the same browser port.
The important difference is this one line:
fastcgi_pass 127.0.0.1:9056;versus:
fastcgi_pass 127.0.0.1:9084; That line determines which PHP process executes the request. Nginx's official documentation describes fastcgi_pass as the connection to the FastCGI server and shows SCRIPT_FILENAME being passed so PHP knows which file to execute.
This is not limited to Nginx.
Apache can also send PHP requests from different virtual hosts to different FastCGI backends through mod_proxy_fcgi.
Conceptually:
<VirtualHost *:80>
ServerName legacy.test
DocumentRoot "C:/sites/legacy/public"
<FilesMatch "\.php$">
SetHandler "proxy:fcgi://127.0.0.1:9056"
</FilesMatch>
</VirtualHost>
<VirtualHost *:80>
ServerName modern.test
DocumentRoot "C:/sites/modern/public"
<FilesMatch "\.php$">
SetHandler "proxy:fcgi://127.0.0.1:9084"
</FilesMatch>
</VirtualHost> Apache's mod_proxy_fcgi supports selecting a FastCGI host and port from server or virtual-host configuration.
Nginx simply makes the separation particularly easy to understand.
Neither is inherently better.
Apache 5.6 · Apache 7.4 · Nginx 8.4
Advantages
Disadvantages
One Nginx · PHP-CGI 5.6 · PHP-CGI 7.4 · PHP-CGI 8.4
Advantages
Disadvantages
For local development, the performance difference is unlikely to be the deciding factor. Apache and Nginx can both handle local workloads comfortably. Choose the architecture that makes your projects easiest to understand and maintain.
ForgeKit turns the underlying pieces into three concepts:
The actual installed software:
Independent configured servers built from those runtimes:
Projects connected to a particular web-server instance:
legacy-client.test → Legacy Apache → PHP 5.6
shop.test → Client Apache → PHP 7.4
new-app.test → Modern Nginx → PHP 8.4ForgeKit assigns the internal ports, writes the virtual-host files, updates the Windows hosts file, creates the logs and configuration folders, and starts and stops the processes.
The underlying idea remains simple:
Different executable files, running as different processes, listening on different ports.
You can absolutely assemble that architecture yourself.
ForgeKit exists because doing it repeatedly, remembering every port, generating every configuration file and managing every process eventually becomes its own project.
Whichever setup you choose, each PHP version should normally have its own:
php.iniDo not globally add every PHP directory to the Windows PATH.
If C:\php\5.6, C:\php\7.4 and C:\php\8.4 all compete through global environment variables, you recreate the global-switching problem you were trying to remove.
Use full executable paths or a project-aware command shim instead:
C:\php\7.4\php.exe artisan migrate
C:\php\8.4\php.exe artisan testAlso bind local FastCGI and backend server ports to:
127.0.0.1rather than:
0.0.0.0unless you intentionally want them accessible from other machines on your network.
The final architecture may contain a few configuration files, but the underlying idea is not complicated.
Windows can run multiple copies and versions of a program simultaneously.
Each web server listens on a port.
Each virtual host decides which site to serve.
Each site can be connected to the PHP process or server environment it needs.
That is all ForgeKit is taking advantage of.
You do not need Docker containers to achieve process isolation for local PHP development. Native Windows processes and ports already provide the basic building blocks.
ForgeKit simply turns those building blocks into something you can set up in a few clicks instead of spending an afternoon naming ports and arguing with Windows paths.
ForgeKit lets you create independent Apache and Nginx environments, attach sites to the PHP version they need, and run them all simultaneously on Windows.
Not out of the box: each XAMPP install has one PHP version. You can add a second one by running another PHP version's php-cgi.exe as a FastCGI process and pointing one site's virtual host at it with mod_proxy_fcgi. That process has to be started separately each time.
Switching (XAMPP, Laragon) means one version is active and every site uses it until you change it. Side by side means several versions run at once and each site uses the one it needs, so an old PHP 7.4 project and a new PHP 8.5 project both keep working.
Yes. PHP, Apache, and Nginx are ordinary Windows processes, and separate processes can each listen on their own port at the same time. Two Apache instances, one on PHP 5.6 and one on PHP 8.4, can run simultaneously as long as they use different ports.
No. Process and port isolation on Windows doesn't require containers. Native binaries listening on separate ports, combined with a router or reverse proxy for clean domains, achieve the same practical isolation for local development.
Separate web-server instances give each PHP version its own fully independent Apache or Nginx process, at the cost of more processes and needing a router for clean domains. One Nginx server with several PHP-CGI backends keeps a single HTTP server on port 80 and routes each site to the right PHP process via fastcgi_pass, but all sites then share that one Nginx process's lifecycle.
No. PHP-FPM is not available on Windows. The Windows equivalent for FastCGI setups is php-cgi.exe, which Nginx or Apache's mod_proxy_fcgi can talk to in the same way.
ForgeKit models this as Runtimes (installed PHP, Apache, Nginx, MySQL, and MariaDB versions), Instances (independent configured servers built from those runtimes), and Sites (projects assigned to a specific instance). It assigns the internal ports, writes the virtual-host files, updates the Windows hosts file, and starts and stops the processes for you.
ForgeKit handles the ports, virtual hosts, hosts file entries, and process management, so different PHP versions can run side by side without fighting each other.