Patched

Two critical PHP remote code execution flaws (CVE-2026-109618, CVE-2026-109619) are fixed in PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35. ForgeKit ships them out of the box.

← All guides

Guide · Last updated: November 2026

How to Run Multiple PHP Versions at the Same Time on Windows

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.

The short answer

  • Yes, you can run several PHP versions at once on Windows. Each version is its own set of programs, and Windows runs them side by side like any other programs.
  • There are two ways to do it: a separate web server for each PHP version, or one web server that hands each site to a different PHP process.
  • Most local tools switch rather than run side by side. XAMPP has one PHP, and Laragon switches its one active version from the menu. That's fine until two projects need different versions on the same day.
  • The versions that matter right now: PHP 8.5 is current, 8.6 is due on 19 November 2026, and 8.2 stops getting security fixes on 31 December 2026. 7.4 and 8.0 are long past end of life but still run plenty of client sites.

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.

Switching versions vs running them side by side

Two different things both get called "multiple PHP versions":

  • Switching: several versions are installed, but one is active. Change it, restart, and every site now runs on the new version.
  • Side by side: several versions run at the same time, and each site uses the one it needs. Nothing is switched globally.

How the common Windows tools handle it:

  • XAMPP: one PHP version per install. Since 23 October 2026 there are builds with PHP 8.3, 8.4 and 8.5, but changing versions still means a second XAMPP or replacing the php folder. See does XAMPP support PHP 8.5.
  • Laragon: you add versions to 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).
  • Laravel Herd: you can pin each site to a PHP version. Built around Laravel. Databases, Redis and mail need Herd Pro.
  • ForgeKit (made by me): several PHP versions run at once, each site attached to the one it needs, under Apache or Nginx. Free, including for commercial work.

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.

Adding a second PHP version to XAMPP

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.

  1. Download the other PHP version from php.net (for example PHP 8.5, x64 Non Thread Safe) and unzip it to its own folder, such as C:\php\8.5. Create its php.ini from php.ini-development.
  2. Start it as a FastCGI process: C:\php\8.5\php-cgi.exe -b 127.0.0.1:9085
  3. In xampp\apache\conf\httpd.conf, make sure mod_proxy and mod_proxy_fcgi are loaded (uncomment their LoadModule lines).
  4. In that site's virtual host, hand PHP files to the new process: <FilesMatch "\.php$"> SetHandler "proxy:fcgi://127.0.0.1:9085" </FilesMatch>
  5. Restart Apache. That site now runs on PHP 8.5, and every other site stays on XAMPP's own PHP (8.2 if you're on XAMPP 8.2.12).

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.
  • Each extra version needs its own folder, php.ini, port and start command.
  • Command-line tools such as Composer and 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.

PHP, Apache and Nginx are just programs

On Windows, PHP, Apache and Nginx ultimately run as ordinary processes:

php.exe
php-cgi.exe
httpd.exe
nginx.exe

They 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:8080

your 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.4

All three can continue running at once.

No container is required. They are ordinary native Windows processes with separate configurations.

The quickest proof: PHP's built-in server

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\modern

You now have:

http://127.0.0.1:8056 → PHP 5.6
http://127.0.0.1:8084 → PHP 8.4

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

Approach one: separate web-server instances

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

Each environment gets its own:

  • configuration directory
  • listening port
  • PHP version
  • php.ini
  • logs
  • virtual-host configuration
  • PID or process state
  • optional database environment

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.

A minimal Apache instance

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.

One server can serve many sites

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

All 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.test

The 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.test

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

But only one server can own port 80

There is one inconvenience with running several complete web-server instances.

You probably want URLs like:

http://legacy.test
http://modern.test

rather 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 8083

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

Approach two: one Nginx server with several PHP versions

You do not have to run several Apache or Nginx servers.

Another clean architecture is:

One Nginx server
Multiple PHP FastCGI processes

Nginx 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:9084

The 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.ini

Then each Nginx server block chooses its required PHP backend.

PHP 5.6 site

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;
    }
}

PHP 8.4 site

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.

Apache can do this too

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.

Which architecture is better?

Neither is inherently better.

Multiple web-server instances

Apache 5.6 · Apache 7.4 · Nginx 8.4

Advantages

  • Complete separation of configuration
  • Different Apache or Nginx versions if needed
  • Separate modules and extensions
  • Easy mental model
  • Useful for old or unusual projects
  • One broken environment is less likely to affect another

Disadvantages

  • More processes
  • More configuration directories
  • Each backend needs its own internal port
  • A router is needed for clean, portless domains

One web server, several FastCGI backends

One Nginx · PHP-CGI 5.6 · PHP-CGI 7.4 · PHP-CGI 8.4

Advantages

  • One HTTP server owns ports 80 and 443
  • Clean domains without another router
  • Fewer web-server processes
  • Each site can select a PHP version directly

Disadvantages

  • All sites share the same Nginx lifecycle and general configuration
  • FastCGI process management becomes your responsibility
  • The internal PHP ports need to be tracked
  • Unusual legacy projects may require separate server configurations anyway

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.

How ForgeKit organises this

ForgeKit turns the underlying pieces into three concepts:

Runtimes

The actual installed software:

  • PHP 5.6
  • PHP 7.4
  • PHP 8.4
  • Apache 2.4
  • Nginx
  • MySQL
  • MariaDB

Instances

Independent configured servers built from those runtimes:

  • Legacy Apache
  • Client PHP 7.4
  • Modern Nginx
  • Old MySQL 5.6
  • Current MariaDB

Sites

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

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

  • Your projects stay where they are. You point a site at its existing folder.
  • XAMPP or Laragon can stay installed. Stop their web server while ForgeKit runs, because only one program can own ports 80 and 443.
  • Free, including for commercial work. No licence, no account.

Things to keep separate

Whichever setup you choose, each PHP version should normally have its own:

  • PHP directory
  • php.ini
  • extension directory
  • temporary/session paths where appropriate
  • FastCGI port
  • logs
  • environment variables
  • Composer context

Do 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 test

Also bind local FastCGI and backend server ports to:

127.0.0.1

rather than:

0.0.0.0

unless you intentionally want them accessible from other machines on your network.

Multiple PHP versions are not complicated in principle

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.

Want the setup without managing it manually?

ForgeKit lets you create independent Apache and Nginx environments, attach sites to the PHP version they need, and run them all simultaneously on Windows.

The interactive preview, right here

Add a site, assign it a PHP or Node version, and open a simulated project terminal, without installing anything.

Open the full windows desktop demo in browser

Frequently asked questions

Can XAMPP run two PHP versions at the same time?

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.

What's the difference between switching PHP versions and running them side by side?

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.

Can I really run two PHP versions at the same time on Windows?

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.

Do I need Docker to isolate different PHP versions on Windows?

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.

What's the difference between running separate web servers and one Nginx server with multiple PHP-CGI backends?

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.

Does PHP-FPM work on Windows?

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.

How does ForgeKit handle running multiple PHP versions?

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.

Run several PHP versions on Windows, without the manual setup

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.