# Multiple PHP versions on AlmaLinux and Rocky Linux, one FPM pool per site

> Run PHP 8.4 and 8.2 side by side with Apache and Remi's packages: each site gets its own PHP version, its own Linux user and its own FPM pool.

Source: https://www.corepanel.net/tutorials/multiple-php-versions-almalinux-rocky
Author: CorePanel Team
Published: 2026-10-05
Track: Server stack · intermediate · ~25 min
Tested on: AlmaLinux 9.8 (2026-10-05)
Tested on: AlmaLinux 10.2 (2026-10-05)
Tested on: Rocky Linux 9.8 (2026-10-05)
Tested on: Rocky Linux 10.2 (2026-10-05)
Before you start: https://www.corepanel.net/tutorials/initial-server-setup-almalinux-rocky
CorePanel edition needed: Personal (free)
Tags: php, php-fpm, apache, remi, almalinux, rocky-linux
From CorePanel Tutorials — https://www.corepanel.net/tutorials

---

A hosting server rarely gets to pick one PHP version. A client's old shop needs 8.2,
while new WordPress sites should run 8.4. The distribution ships one PHP at a time, so this
tutorial uses Remi's repository, which installs each version under its own prefix so
they never collide.

You will end up with two sites, each running as its own Linux user in its own
**PHP-FPM pool**:

| Site | PHP | Runs as |
|---|---|---|
| `site1.example.com` | 8.4 | `site1` |
| `site2.example.com` | 8.2 | `site2` |

A pool per site is what keeps one site's code from reading another site's files. It also
means one site that runs away with its PHP processes does not starve the others.

## Add the EPEL and Remi repositories

Remi's packages depend on EPEL, and EPEL depends on CodeReady Builder (`crb`), which is
off by default.

```bash
dnf -y install dnf-plugins-core
dnf config-manager --set-enabled crb
dnf -y install epel-release
dnf -y install "https://rpms.remirepo.net/enterprise/remi-release-$(rpm -E %rhel).rpm"
```

`$(rpm -E %rhel)` expands to your major version (9 or 10), so the same line works on both.

## Install Apache and both PHP versions

Remi names each version's packages `phpXY-php-*`. Install FPM and a few common
extensions for each version you want:

```bash
dnf -y install httpd
dnf -y install php84-php-fpm php84-php-opcache php84-php-mysqlnd php84-php-process
dnf -y install php82-php-fpm php82-php-opcache php82-php-mysqlnd php82-php-process
```

Each version lives in its own tree. Its configuration is under `/etc/opt/remi/php84/`,
its sockets under `/var/opt/remi/php84/run/`, and it runs as its own service,
`php84-php-fpm`. Nothing here replaces the system `php`, and you can add `php83` or
`php85` the same way later.

## Create a user and a document root per site

Each site gets its own Linux user. Its files belong to that user and to the `apache`
group, so Apache can serve static files, and nobody else can read them:

```bash
for site in site1 site2; do
  useradd --system --shell /sbin/nologin "$site"
  mkdir -p "/var/www/$site/public"
  chown -R "$site:apache" "/var/www/$site"
  chmod 750 "/var/www/$site"
done
```

Directories under `/var/www` already carry the SELinux label Apache is allowed to read,
so nothing needs relabelling.

Put a test page in each site that reports which PHP version runs it, and as which user:

```bash
for site in site1 site2; do
  cat > "/var/www/$site/public/index.php" <<'EOF'
<?php
echo PHP_VERSION, ' ', posix_getpwuid(posix_geteuid())['name'], "\n";
EOF
  chown "$site:apache" "/var/www/$site/public/index.php"
done
```

## Give each site its own FPM pool

A pool is a group of PHP processes with its own user, its own socket and its own limits.
Create one for `site1` under PHP 8.4:

```bash
cat > /etc/opt/remi/php84/php-fpm.d/site1.conf <<'EOF'
[site1]
user = site1
group = site1
listen = /var/opt/remi/php84/run/php-fpm/site1.sock
listen.owner = apache
listen.group = apache
listen.mode = 0660

pm = ondemand
pm.max_children = 5
pm.process_idle_timeout = 10s

php_admin_value[open_basedir] = /var/www/site1:/tmp
EOF
```

and one for `site2` under PHP 8.2:

```bash
cat > /etc/opt/remi/php82/php-fpm.d/site2.conf <<'EOF'
[site2]
user = site2
group = site2
listen = /var/opt/remi/php82/run/php-fpm/site2.sock
listen.owner = apache
listen.group = apache
listen.mode = 0660

pm = ondemand
pm.max_children = 5
pm.process_idle_timeout = 10s

php_admin_value[open_basedir] = /var/www/site2:/tmp
EOF
```

What the settings do:

- **`user` / `group`** — the PHP code of this site runs as this user, and only this
  user. File permissions do the isolating from here.
- **`listen.*`** — only Apache may connect to the socket.
- **`pm = ondemand`** — no processes at all until a request arrives, and idle ones exit
  after 10 seconds. On a server with many small sites this is the setting that saves the
  most memory. `pm.max_children` caps how many requests run at once, per site.
- **`open_basedir`** — a second fence inside PHP itself: even a file the user could
  read is off limits outside these paths.

Each version also ships a default `www` pool. It is not used here, so disable it, then
start both services:

```bash
mv /etc/opt/remi/php84/php-fpm.d/www.conf /etc/opt/remi/php84/php-fpm.d/www.conf.disabled
mv /etc/opt/remi/php82/php-fpm.d/www.conf /etc/opt/remi/php82/php-fpm.d/www.conf.disabled
systemctl enable --now php84-php-fpm php82-php-fpm
```

## Point each Apache virtual host at its pool

The `SetHandler` line is where a site picks its PHP version: it hands `.php` files to
that site's socket.

```bash
cat > /etc/httpd/conf.d/site1.conf <<'EOF'
<VirtualHost *:80>
    ServerName site1.example.com
    DocumentRoot /var/www/site1/public
    <Directory /var/www/site1/public>
        Require all granted
        AllowOverride All
    </Directory>
    <FilesMatch "\.php$">
        SetHandler "proxy:unix:/var/opt/remi/php84/run/php-fpm/site1.sock|fcgi://localhost"
    </FilesMatch>
    DirectoryIndex index.php index.html
</VirtualHost>
EOF

cat > /etc/httpd/conf.d/site2.conf <<'EOF'
<VirtualHost *:80>
    ServerName site2.example.com
    DocumentRoot /var/www/site2/public
    <Directory /var/www/site2/public>
        Require all granted
        AllowOverride All
    </Directory>
    <FilesMatch "\.php$">
        SetHandler "proxy:unix:/var/opt/remi/php82/run/php-fpm/site2.sock|fcgi://localhost"
    </FilesMatch>
    DirectoryIndex index.php index.html
</VirtualHost>
EOF

apachectl configtest
systemctl enable --now httpd
```

To move a site to another PHP version later, change the version in its `SetHandler`
path, move its pool file into that version's `php-fpm.d`, and reload both services.

## Check that each site runs its own PHP, as its own user

Until DNS points the names at this server, ask Apache directly with a `Host` header:

```bash
curl -fsS -H 'Host: site1.example.com' http://127.0.0.1/ | grep -E '^8\.4\.[0-9]+ site1$'
curl -fsS -H 'Host: site2.example.com' http://127.0.0.1/ | grep -E '^8\.2\.[0-9]+ site2$'
```

Each line prints the version and the user, for example `8.4.26 site1`.

Now check the isolation. Have `site2` try to read `site1`'s code:

```bash
cat > /var/www/site2/public/peek.php <<'EOF'
<?php
echo @file_get_contents('/var/www/site1/public/index.php') === false ? "blocked\n" : "LEAKED\n";
EOF
chown site2:apache /var/www/site2/public/peek.php
curl -fsS -H 'Host: site2.example.com' http://127.0.0.1/peek.php | grep -x blocked
rm /var/www/site2/public/peek.php
```

`blocked` is the answer you want. Two separate fences stop it: `open_basedir` refuses the
path, and even without it, `site2` is neither the owner of `/var/www/site1` nor in its
group.

## The CorePanel way

CorePanel builds this structure for every hosting account and keeps it up to date.
Each account is its own Linux user with its own on-demand FPM pool, and PHP 8.4, 8.3, 8.2
and 7.4 come installed side by side. The version is chosen per account when you create
it, and changed later under **Edit account → PHP version**: CorePanel rewrites the pool
on the new version and re-points every site of the account in one reload. A single site
can also run a version of its own, from the **PHP** column of the account's domains, and
your customers can pick it from their own panel. `open_basedir`,
process limits and a private temporary directory per account are set the same way.

See [How PHP runs](https://www.corepanel.net/docs/php/how-php-runs) and
[Isolation and limits](https://www.corepanel.net/docs/php/isolation-and-limits).
