Install PHP 8.5 in parallel with the system PHP using Remi SCL
Keep the distribution PHP unchanged while adding Remi's php85 Software Collection, its FPM service, isolated configuration, and an explicit application handler.
Run a PHP 8.5 application beside a system runtime without replacing /usr/bin/php or the default php-fpm service.
- Enterprise Linux 8, 9, 10
- PHP 8.5
- Supported host and recovery access Use a supported distribution, keep an independent root-capable session, and record the current package repositories before changing the PHP runtime.
cat /etc/os-release && uname -m - Application compatibility Confirm the application, framework, extensions, and deployment tooling explicitly support the target PHP branch.
- Configuration backup Archive the active PHP, web-server, and application configuration before packages, pools, or handlers change.
OneLiners never runs these steps or stores secrets. Review placeholders, versions, current state, and change-control requirements before using a command.
Full guide
What you will build
- A Remi `php85` Software Collection installed beside, rather than over, the Enterprise Linux system PHP, with separate executables, configuration tree, FPM service and application socket.
- A dedicated non-login application identity and bounded FPM pool whose Unix socket is reachable only by the selected web-server group, while the packaged general-purpose pool is disabled.
- A canary-only handler and evidence set proving that `/usr/bin/php` remains unchanged, the `/opt/remi/php85` CLI is PHP 8.5, and only the intended virtual host reaches the new pool.
- System PHP and its service/socket remain byte-for-byte outside the SCL transaction; prefixed packages come from Remi and the parallel CLI/FPM validate independently.
- The application pool has an explicit identity, socket ownership, process ceiling, environment policy and PHP-only extension restriction; no unused default SCL pool listens.
- A FastCGI or HTTP canary proves the new runtime, while a control site continues to report its original runtime and rollback requires only restoring one handler and disabling the parallel service.
Architecture
How the parts fit together
Remi's `php85-*` packages place PHP 8.5 under `/opt/remi/php85`, configuration under `/etc/opt/remi/php85`, runtime state under `/var/opt/remi/php85` or the explicitly selected `/run` socket, and expose the separate `php85-php-fpm` unit. They do not replace `/usr/bin/php` or the distribution `php-fpm`. A dedicated pool runs application code as `{{appUser}}`, while nginx or Apache reaches `/run/php85-{{app}}.sock` through `{{webGroup}}`. Only one reviewed canary virtual host references that socket. The default SCL pool is disabled after two configuration tests, so installing the parallel branch does not create an unmanaged general-purpose handler.
- The web server selects a reviewed virtual host and sends only `.php` requests to the unique SCL socket.
- The php85 FPM master accepts the socket connection and starts bounded workers under the application identity.
- Workers read the SCL configuration and application files, return FastCGI data, and remain isolated from the system PHP service.
- Canary and control checks compare runtime markers before broader traffic is considered.
Assumptions
- The host is a supported RHEL, AlmaLinux or Rocky Linux 8–10 system on x86_64/aarch64, and EPEL/CRB/Remi use official signed repository packages.
- An existing system PHP may serve production traffic and must remain unchanged; a maintenance/canary window and independent recovery session are available.
- The application, Composer dependencies and native extensions support PHP 8.5, and the required prefixed Remi packages exist before installation.
- `{{appUser}}`, `{{appGroup}}`, `{{webGroup}}`, `{{app}}`, `{{docroot}}` and the exact virtual host are reviewed values; the web group receives socket access, not application ownership.
Key concepts
- Software Collection
- A prefixed runtime installed beside system packages so multiple major versions can coexist without sharing binary and configuration paths.
- system runtime
- The unprefixed `/usr/bin/php` and default FPM service used by existing consumers.
- Unix socket
- A local filesystem-named FastCGI endpoint whose owner, group and mode form an access-control boundary.
- pool
- A set of FPM workers sharing Unix identity, process policy, socket and PHP overrides.
- canary
- A deliberately limited application or traffic slice used to prove the new runtime before migration.
Before you copy
Values used in this guide
{{app}}Short lowercase application identifier used in pool and socket names.
Example: billing{{appUser}}Dedicated non-login Unix user that executes the application.
Example: billing{{appGroup}}Private primary group for application files and runtime data.
Example: billing{{webGroup}}Existing group of the selected nginx or Apache worker.
Example: nginx{{docroot}}Absolute public document root used by the canary handler and health fixture.
Example: /srv/billing/publicSecurity and production boundaries
- Parallel installation reduces migration blast radius but does not isolate kernel, filesystem or credentials. Pool identity, path permissions and handler scope still matter.
- Only the web-server group should connect to the socket; it must not own application source or writable runtime data broadly.
- Do not copy secrets into pool environment directives. `clear_env=yes` preserves the default fail-closed environment boundary.
- Never expose a phpinfo page. Use a minimal, access-controlled health marker and delete it after acceptance.
Stop before continuing if
- Stop on unsupported platform, repository/signature failure, missing prefixed extension, or any transaction that replaces unprefixed system PHP.
- Do not start the service while the pool test fails, the default pool remains unexpectedly active, socket access is broad, or the application identity is shared.
- Do not switch a virtual host until its prior handler is archived and both canary plus control runtime checks are ready.
instruction
Record the system runtime
Prove which commands and sockets belong to the distribution runtime before adding a parallel tree.
Why this step matters
A parallel migration is successful only if the original binary, service and sockets are known and demonstrably unchanged.
What to understand
Record the resolved CLI path/version, FPM unit and every existing PHP socket before installing anything.
Map each current virtual host to its handler; an unidentified socket can be a production dependency.
System changes
- No persistent change; inventories system PHP executables, service state and local sockets.
Syntax explained
command -v php- Shows which executable the current PATH resolves.
ss -lxnp- Lists listening Unix sockets with process details.
command -v php; php -v; systemctl status php-fpm --no-pager 2>/dev/null || true; sudo ss -lxnp | grep php || trueSystem PHP paths, service state, and sockets are recorded.
Checkpoint: Checkpoint: inspect
Continue whenThe system CLI path/version, FPM unit, sockets and consuming sites are recorded.
Stop whenAny runtime or socket owner/consumer is unknown, or no pre-change control request can be made.
If this step fails
php exists but `php-fpm` unit is absent.
Likely causeThe host may use a versioned unit, Apache module, container or custom build.
systemctl list-units --all '*php*fpm*'ps -ef | grep '[p]hp-fpm'rpm -qa 'php*' | sort
ResolutionIdentify the actual SAPI and handler before defining the isolation baseline.
Security notes
- Runtime inventory can expose paths and tenant names; retain it as restricted change evidence.
Alternatives
- Collect equivalent executable, package, service, socket and handler inventory through configuration management.
Stop conditions
- Any runtime or socket owner/consumer is unknown, or no pre-change control request can be made.
command
Enable EPEL, CRB, and Remi
Configure only the official dependency repositories required by Remi for the detected EL major version.
Why this step matters
The SCL is trustworthy only when its EPEL/CRB dependencies and Remi definitions are installed through current official, signed paths for the exact host major.
What to understand
Derive `EL_MAJOR` from `/etc/os-release`; registered RHEL uses CodeReady Builder and clones use Remi's documented `crb install` helper.
Review enabled repository IDs and GPG settings after installation; repository presence does not yet alter system PHP.
System changes
- Adds EPEL and Remi repository packages and enables CodeReady Builder/CRB.
Syntax explained
epel-release-latest-${EL_MAJOR}- Uses Fedora's official EPEL release package for the detected major.
remi-release-${EL_MAJOR}- Uses Remi's official Enterprise Linux repository package.
crb install- Follows Remi's current clone prerequisite path.
. /etc/os-release; EL_MAJOR="${VERSION_ID%%.*}"; sudo dnf install -y "https://dl.fedoraproject.org/pub/epel/epel-release-latest-${EL_MAJOR}.noarch.rpm"; if [ "$ID" = rhel ]; then sudo subscription-manager repos --enable "codeready-builder-for-rhel-${EL_MAJOR}-$(arch)-rpms"; else sudo crb install; fi; sudo dnf install -y "https://rpms.remirepo.net/enterprise/remi-release-${EL_MAJOR}.rpm"; sudo dnf repolist --enabled | grep -Ei 'epel|codeready|crb|remi'EPEL, CodeReady Builder or CRB, and Remi repository IDs are listed.
Checkpoint: Checkpoint: repositories
Continue whenBase, EPEL, CodeReady Builder/CRB and `remi-safe` repository IDs are visible with signature checking enabled.
Stop whenPlatform is unsupported, RHEL entitlement is absent, TLS/signatures fail, or repository provenance is mixed.
If this step fails
The release package installs but `php85` is not available.
Likely causeRemi-safe is disabled, metadata is stale, or the release package does not match the EL major.
rpm -q epel-release remi-releasesudo dnf repolist all | grep -E '^remi|epel|crb|codeready'sudo dnf clean expire-cache
ResolutionCorrect the official release package and enabled repository set; never bypass GPG verification.
Security notes
- Repository files and keys change the future software trust boundary; review and version them.
Alternatives
- Use an approved internal mirror that preserves upstream signatures and update cadence.
Stop conditions
- Platform is unsupported, RHEL entitlement is absent, TLS/signatures fail, or repository provenance is mixed.
command
Install the php85 parallel packages
Use packages prefixed php85 so files live below /opt/remi/php85 and do not replace the system CLI.
Why this step matters
Only prefixed `php85-*` packages preserve the system branch; an explicit extension list also makes native-code supply and patch obligations auditable.
What to understand
Preview with `--assumeno` and reject any removal or replacement of unprefixed PHP.
Install only application-required extensions; the baseline shown is common, not mandatory.
System changes
- Installs PHP 8.5 under `/opt/remi/php85` and its separate configuration/service files.
Syntax explained
php85- Installs the SCL meta-package and activation helpers.
php85-php-fpm- Installs the separate FastCGI service.
php85-php-opcache- Adds the matching bytecode cache.
php85-php-*- Selects extensions compiled for this SCL ABI.
sudo dnf install -y php85 php85-php-cli php85-php-fpm php85-php-opcache php85-php-mysqlnd php85-php-xml php85-php-mbstring php85-php-intl php85-php-zipPackages install below /opt/remi/php85.
Checkpoint: Checkpoint: install
Continue whenOnly prefixed packages and dependencies install, `/usr/bin/php` remains the recorded version, and all required SCL extensions resolve.
Stop whenDNF changes unprefixed PHP, removes unrelated packages, lacks a required extension, or uses an unexpected repository.
If this step fails
A required extension has no `php85-php-*` package.
Likely causeIt may not support PHP 8.5 or may be supplied under another reviewed name.
dnf search 'php85-php-*'dnf repoquery --whatprovides 'php85-php-*'
ResolutionKeep the old runtime or containerize/parallel-test until a trusted compatible build exists.
Security notes
- Do not install unpinned PECL binaries or arbitrary RPMs to fill an extension gap.
Alternatives
- Start with CLI/FPM only and add each proven extension during staging.
Stop conditions
- DNF changes unprefixed PHP, removes unrelated packages, lacks a required extension, or uses an unexpected repository.
instruction
Inspect the isolated layout and unit
Locate the CLI, php.ini, pool files, systemd unit, and default socket before editing a web-server handler.
Why this step matters
SCL path separation is easy to defeat accidentally; inspect exact binaries, INI scan directories, unit command and default pool/socket before editing.
What to understand
Use absolute SCL paths rather than relying on a shell activation environment.
The verbose FPM test reveals included pool files and duplicate names before the service starts.
System changes
- No persistent change; reads the SCL CLI, FPM configuration and unit definition.
Syntax explained
/opt/remi/php85/root/usr/bin/php- Invokes the parallel CLI explicitly.
php-fpm -tt- Tests and dumps effective FPM configuration.
systemctl cat- Shows the exact executable and unit overrides.
/opt/remi/php85/root/usr/bin/php -v; /opt/remi/php85/root/usr/sbin/php-fpm -tt; systemctl cat php85-php-fpmPHP 8.5.x and the php85-php-fpm unit are displayed.
Checkpoint: Checkpoint: inspect layout
Continue whenPHP 8.5 is under `/opt/remi/php85`, configuration is under `/etc/opt/remi/php85`, and the separate unit's paths are understood.
Stop whenAbsolute paths resolve to the system runtime, FPM test fails, or unit/config directories differ from the intended package layout.
If this step fails
The SCL CLI works but FPM loads another configuration.
Likely causeThe unit has an override or an environment/command-line option selects another prefix.
systemctl cat php85-php-fpmsystemctl show php85-php-fpm -p ExecStart -p Environment
ResolutionReconcile the packaged unit and configuration prefix before creating pools.
Security notes
- Inspect unit overrides for untrusted environment files or writable executable/config paths.
Alternatives
- Use the package manifest (`rpm -ql`) to map paths when service start is not yet allowed.
Stop conditions
- Absolute paths resolve to the system runtime, FPM test fails, or unit/config directories differ from the intended package layout.
config
Create a dedicated application pool
Create a managed non-login identity and a complete application-specific pool with a unique Unix socket and bounded process policy.
Why this step matters
A complete dedicated pool establishes the process, filesystem and socket boundaries before any request can reach PHP 8.5.
What to understand
Create the managed group/user idempotently and private application directories; do not reuse the web worker identity.
The displayed configuration defines one socket, 0660 access for `{{webGroup}}`, bounded ondemand workers, recycling, clean environment and `.php` execution only.
System changes
- Creates the application identity/directories and `/etc/opt/remi/php85/php-fpm.d/{{app}}.conf`.
Syntax explained
pm = ondemand- Starts workers when requests arrive and removes idle workers.
pm.max_children = 8- Places a hard concurrency ceiling that must be capacity-tested.
pm.max_requests = 500- Recycles workers to bound long-lived leak accumulation.
listen.mode = 0660- Limits socket access to owner and selected web group.
clear_env = yes- Does not import the service manager's arbitrary environment into workers.
/etc/opt/remi/php85/php-fpm.d/{{app}}.confValues stay on this page and are never sent or saved.
[{{app}}]
user = {{appUser}}
group = {{appGroup}}
listen = /run/php85-{{app}}.sock
listen.owner = {{appUser}}
listen.group = {{webGroup}}
listen.mode = 0660
pm = ondemand
pm.max_children = 8
pm.process_idle_timeout = 10s
pm.max_requests = 500
clear_env = yes
catch_workers_output = yes
security.limit_extensions = .phpValues stay on this page and are never sent or saved.
getent group {{appGroup}} >/dev/null || sudo groupadd --system {{appGroup}}; id -u {{appUser}} >/dev/null 2>&1 || sudo useradd --system --gid {{appGroup}} --home-dir /srv/{{app}} --shell /usr/sbin/nologin {{appUser}}; sudo install -d -o {{appUser}} -g {{appGroup}} -m 0750 /srv/{{app}}/{public,var,tmp}; sudo install -o root -g root -m 0640 /dev/null /etc/opt/remi/php85/php-fpm.d/{{app}}.conf; sudoedit /etc/opt/remi/php85/php-fpm.d/{{app}}.confThe new pool has a unique name, identity, socket, and bounded ondemand worker policy.
Checkpoint: Checkpoint: configure pool
Continue whenThe identity is non-login, paths have intended ownership, the pool file is root-owned 0640, and `php-fpm -tt` shows all intended directives.
Stop whenIdentity exists for another purpose, web group is wrong, paths contain unbacked data, socket name collides, or capacity has not been reviewed.
If this step fails
FPM rejects the new pool file.
Likely causePlaceholder text remains, identity/path is invalid, directive is unsupported, or another pool has the same name/socket.
sudo /opt/remi/php85/root/usr/sbin/php-fpm -ttgrep -R -nE '^\[billing\]|^listen = /run/php85-billing.sock' /etc/opt/remi/php85/php-fpm.d
ResolutionFix the exact reported directive or collision, replace every placeholder, and retest before service start.
Security notes
- Never run the pool as root or make the socket/world/application tree broadly writable.
Alternatives
- Use `dynamic` process management when measured steady concurrency benefits from warm workers.
Stop conditions
- Identity exists for another purpose, web group is wrong, paths contain unbacked data, socket name collides, or capacity has not been reviewed.
config
Disable the unused default pool
Disable the packaged pool only after the application pool validates, preventing an unexpected general-purpose socket while remaining idempotent.
Why this step matters
The packaged `www` pool is unnecessary for a site-specific SCL and can expose an additional broadly configured listener.
What to understand
Test once with both pools, rename only the packaged file if it still exists, then test again.
The conditional rename is rerunnable and retains the source for rollback; do not delete it.
System changes
- Renames `www.conf` to a non-`.conf` filename after successful validation.
Syntax explained
php-fpm -tt &&- Prevents disabling the fallback when the application pool is already invalid.
[ -f ... ]- Makes reruns safe when the packaged pool is already disabled.
sudo /opt/remi/php85/root/usr/sbin/php-fpm -tt && if [ -f /etc/opt/remi/php85/php-fpm.d/www.conf ]; then sudo mv /etc/opt/remi/php85/php-fpm.d/www.conf /etc/opt/remi/php85/php-fpm.d/www.conf.disabled; fi; sudo /opt/remi/php85/root/usr/sbin/php-fpm -ttBoth configuration tests succeed and only the intended pool remains enabled.
Checkpoint: Checkpoint: disable default pool
Continue whenBoth tests pass and only the named application pool is enabled in the effective configuration.
Stop whenAnother site uses the default SCL pool, the first or second test fails, or configuration management owns the filename.
If this step fails
A site fails after disabling `www.conf`.
Likely causeIt was an undocumented consumer of the default socket.
sudo ss -lxnp | grep php85grep -R -n 'php85' /etc/nginx /etc/httpd 2>/dev/null
ResolutionRestore `www.conf`, validate/restart the SCL service, and migrate that consumer explicitly.
Security notes
- Removing unused listeners reduces accidental cross-application execution surfaces.
Alternatives
- Keep the default pool disabled declaratively in configuration management rather than renaming on-host.
Stop conditions
- Another site uses the default SCL pool, the first or second test fails, or configuration management owns the filename.
command
Enable the parallel FPM unit
Start the php85-specific service and verify its socket owner and permissions.
Why this step matters
Starting only the SCL unit proves that parallel service lifecycle and socket metadata are independent from the system FPM.
What to understand
Validate immediately before start, inspect service state and exact Unix socket owner/group/mode.
Compare the system service/socket again after start; no unprefixed state should change.
System changes
- Enables and starts `php85-php-fpm`, creating pool workers and `/run/php85-{{app}}.sock`.
Syntax explained
enable --now- Starts now and enables the unit for boot.
ss -lxnp- Shows listening Unix sockets and owning process.
sudo systemctl enable --now php85-php-fpm; systemctl is-active php85-php-fpm; sudo ss -lxnp | grep php85php85-php-fpm is active and the application socket exists.
Checkpoint: Checkpoint: start
Continue whenThe SCL unit is active, the unique socket is 0660 with intended identities, and system PHP/FPM evidence is unchanged.
Stop whenService or syntax fails, socket collides/is broad, system service changes, or workers use the wrong user.
If this step fails
The unit loops or exits at start.
Likely causePool syntax, socket ownership/path, identity, extension or SELinux policy failed.
systemctl status php85-php-fpm --no-pagersudo journalctl -u php85-php-fpm -n 150 --no-pagersudo ausearch -m AVC -ts recent
ResolutionCorrect only the evidenced failure and retest; do not disable SELinux or broaden modes.
Security notes
- Record socket and worker identity as acceptance evidence, not only `active` service status.
Alternatives
- Start manually in foreground on staging for diagnostics before enabling the systemd unit.
Stop conditions
- Service or syntax fails, socket collides/is broad, system service changes, or workers use the wrong user.
config
Point only the intended virtual host to PHP 8.5
Configure Apache or nginx to use /run/php85-{{app}}.sock; leave all other sites on their existing handlers.
Why this step matters
Parallel installation provides value only when routing is explicit: one virtual host uses the new socket and every other site remains on its recorded handler.
What to understand
Use the server-specific FPM integration guide to create the handler, archive the old site file and validate nginx/Apache before reload.
Search all configuration for the socket and require exactly the intended reference; deny execution outside the public root.
System changes
- Changes one reviewed virtual-host handler outside this guide; the supplied command verifies scope and does not write configuration.
Syntax explained
grep -R --line-number- Audits every nginx/Apache reference to the exact socket.
cgi-fcgi -bind -connect- Can test the socket directly without changing public routing.
Values stay on this page and are never sent or saved.
sudo grep -R --line-number '/run/php85-{{app}}.sock' /etc/httpd /etc/nginx 2>/dev/null || trueExactly the intended virtual host references the php85 socket.
Checkpoint: Checkpoint: wire handler
Continue whenExactly one intended site references the php85 socket, parser/reload succeeds, and canary plus control runtime markers differ as planned.
Stop whenZero/multiple/unintended sites reference the socket, source-file execution is broad, prior handler is not archived, or control site changes.
If this step fails
Canary still reports the system PHP version.
Likely causeAnother location/handler wins, proxy cache returns an old response, or the site did not reload.
sudo nginx -T 2>&1 | grep -n php85-billing.socksudo apachectl -t -D DUMP_RUN_CFG 2>&1 | grep -n php85-billing.sockcurl -v https://billing.example.com/internal/runtime
ResolutionTrace the selected virtual host/location and effective handler, fix only the canary scope, then repeat canary/control tests.
Security notes
- Never configure a catch-all PHP handler that executes uploads or arbitrary `.php` paths outside the reviewed document root.
Alternatives
- Test direct FastCGI first and postpone the public handler until a separate canary change window.
Stop conditions
- Zero/multiple/unintended sites reference the socket, source-file execution is broad, prior handler is not archived, or control site changes.
Finish line
Verification checklist
php -v; /opt/remi/php85/root/usr/bin/php -vThe system CLI remains unchanged and the parallel CLI reports PHP 8.5.x.SCRIPT_NAME=/health.php SCRIPT_FILENAME={{docroot}}/health.php REQUEST_METHOD=GET cgi-fcgi -bind -connect /run/php85-{{app}}.sock 2>/dev/null | headThe dedicated socket returns a valid FastCGI response.Recovery guidance
Common problems and safe checks
Installing `php85-*` proposes changing or removing unprefixed `php*` packages.
Likely causeThe wrong package names/stream were selected or another repository has conflicting metadata.
sudo dnf install php85 php85-php-fpm --assumenodnf repoquery --installed --qf '%{name} %{evr} %{repoid}' 'php*' | sort
ResolutionCancel and require only prefixed SCL packages from the intended repository; repair repository policy before retrying.
php85-php-fpm is active but the application socket is absent.
Likely causeThe pool file is disabled/invalid, `/run` path cannot be created, or the unit loaded a different configuration.
sudo /opt/remi/php85/root/usr/sbin/php-fpm -ttsystemctl cat php85-php-fpmsudo journalctl -u php85-php-fpm --since '-15 min' --no-pager -n 150
ResolutionCorrect the exact pool/path error, retest configuration, restart only the SCL unit, and verify the socket metadata.
The canary returns 502 or permission denied.
Likely causeThe handler points at the wrong socket, the web worker lacks socket access, SELinux blocks connection, or FPM cannot read/execute the application tree.
sudo ss -lxnp | grep php85namei -l /run/php85-billing.sock /srv/billing/publicsudo ausearch -m AVC -ts recentsudo tail -n 100 /var/log/nginx/error.log /var/log/httpd/error_log 2>/dev/null
ResolutionFix the narrow socket, path or SELinux policy evidenced by checks; never use chmod 777 or disable SELinux.
Reference
Frequently asked questions
Why install `php85` and `php85-php-*` instead of enabling a module stream?
The prefixed packages are the parallel SCL layout. Enabling `php:remi-8.5` changes the unprefixed system runtime.
Can both FPM services use port 9000?
No. Use unique Unix sockets or distinct addresses. This guide uses one named socket per application.
Does `/opt/remi/php85/root/usr/bin/php` affect shell scripts using `/usr/bin/php`?
No, unless PATH or the script interpreter is changed explicitly. That separation is part of the acceptance proof.
Recovery
Rollback
Remove the application handler and parallel pool without changing the system PHP runtime.
- Restore the virtual-host configuration and validate/reload the web server.
- Disable php85-php-fpm, restore the original pool file if needed, and retain logs for diagnosis.
- Remove php85 packages only after no virtual host references their socket.
Evidence