Linux Tutorials

Install PHP 8.4 on RHEL 10 / Rocky Linux 10 / AlmaLinux 10

RHEL 10, Rocky Linux 10 and AlmaLinux 10 ship PHP 8.3 as the default PHP in AppStream. PHP 8.4 is there only as separate php8.4 packages, and RHEL 10 has no module streams at all. To run PHP 8.4 as the main php stack, use Remi, which brings its own php:remi-8.4 module stream to EL10. PHP 8.4 brings property hooks, asymmetric visibility, the new array_find() function, and deprecation of implicit nullable parameter types.

Original content from computingforgeeks.com - post 27869

This guide installs PHP 8.4 from Remi, tunes php.ini and PHP-FPM, wires PHP into either Nginx or Apache, and shows how to confirm the build is patched against PHP2Shell. The install, configuration and PHP2Shell steps below were run on Rocky Linux 10.2 and AlmaLinux 10.2 in October 2026 against Remi’s PHP 8.4.27 packages, with systemd managing php-fpm, Nginx and httpd. The RHEL, SELinux and firewall commands were not part of that run.

Prerequisites

Before you begin, make sure the following are in place:

  • A running instance of RHEL 10, Rocky Linux 10, or AlmaLinux 10 with root or sudo access.
  • System packages updated to the latest versions.
  • A working internet connection for downloading packages.

Start by updating your system:

sudo dnf update -y

If the update pulled in a new kernel, reboot before you continue so the rest of the steps run on the system you will keep.

Step 1: Enable EPEL and Remi Repositories

The EPEL (Extra Packages for Enterprise Linux) repository is a dependency for Remi, which provides the latest PHP releases packaged for RHEL-based distributions.

Install EPEL repository

On Rocky Linux and AlmaLinux, epel-release comes straight from the distribution’s own repositories:

sudo dnf install -y epel-release

On RHEL 10 (not Rocky or Alma), you may need to enable CodeReady Builder first:

sudo subscription-manager repos --enable codeready-builder-for-rhel-10-$(arch)-rpms
sudo dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-10.noarch.rpm

Only the subscription-manager line needs an active RHEL subscription, because CodeReady Builder is a Red Hat repository that supplies build dependencies some EPEL packages expect. The EPEL release RPM itself installs from the Fedora mirror. On Rocky Linux and AlmaLinux the matching repository is CRB, and EPEL or Remi extension packages can depend on it, so enable it with sudo crb enable if dnf reports a missing dependency. The packages in this guide installed on our Rocky and Alma boxes without CRB, and we did not run this guide on a subscribed RHEL host.

Install Remi repository

Remi publishes one release package per EL major version. The EL10 one adds the remi-modular and remi-safe repositories:

sudo dnf install -y https://rpms.remirepo.net/enterprise/remi-release-10.rpm

Verify both repositories are enabled:

sudo dnf repolist | grep -E 'epel|remi'

Three entries come back, one for EPEL and two for Remi:

epel             Extra Packages for Enterprise Linux 10 - x86_64
remi-modular     Remi's Modular repository for Enterprise Linux 10 - x86_64
remi-safe        Safe Remi's RPM repository for Enterprise Linux 10 - x86_64

For more on what each Remi repository carries, see the Remi repository setup for RHEL 10 and its rebuilds.

Step 2: Reset and Enable the PHP 8.4 Module

RHEL 10 dropped modular content from AppStream, so every php stream you see here comes from Remi’s remi-modular repository. Selecting a stream before the install is what makes plain package names such as php-fpm resolve to PHP 8.4 instead of the distribution’s 8.3. List the streams first:

sudo dnf module list php

Remi offers every branch from 7.4 up, and none is enabled yet:

Remi's Modular repository for Enterprise Linux 10 - x86_64
Name Stream   Profiles                   Summary
php  remi-7.4 common [d], devel, minimal PHP scripting language
php  remi-8.0 common [d], devel, minimal PHP scripting language
php  remi-8.1 common [d], devel, minimal PHP scripting language
php  remi-8.2 common [d], devel, minimal PHP scripting language
php  remi-8.3 common [d], devel, minimal PHP scripting language
php  remi-8.4 common [d], devel, minimal PHP scripting language
php  remi-8.5 common [d], devel, minimal PHP scripting language
php  remi-8.6 common [d], devel, minimal PHP scripting language

Hint: [d]efault, [e]nabled, [x]disabled, [i]nstalled

Reset the current PHP module stream. On a fresh system it prints Nothing to do., which is fine:

sudo dnf module reset php -y

Now enable the Remi PHP 8.4 module stream:

sudo dnf module enable php:remi-8.4 -y

Confirm the active stream:

sudo dnf module list php

The remi-8.4 line now carries the [e] flag:

php  remi-8.4 [e] common [d], devel, minimal PHP scripting language

With the stream enabled, every php-* package name in the next step resolves to the 8.4 build from Remi.

Step 3: Install PHP 8.4 and Extensions

Install PHP 8.4 along with the most commonly needed extensions:

sudo dnf install -y php php-fpm php-cli php-mysqlnd php-pgsql php-curl php-gd php-mbstring php-xml php-zip php-intl php-opcache php-redis

Each package in that line covers one job:

PackagePurpose
php-fpmFastCGI Process Manager, required for Nginx and recommended for Apache
php-cliCommand-line interface for running PHP scripts
php-mysqlndNative MySQL/MariaDB driver
php-pgsqlPostgreSQL database driver
php-curlClient URL library for HTTP requests
php-gdImage processing library
php-mbstringMultibyte string handling
php-xmlXML parsing and manipulation
php-zipZIP archive handling
php-intlInternationalization functions
php-opcacheOpcode caching for performance
php-redisRedis key-value store driver

On the 8.4 stream, php-curl is provided by php-common and php-zip by php-pecl-zip, so dnf resolves those names to other packages without complaint. The php package also pulls in httpd-filesystem, which is why /var/www/html exists even on a box that only runs Nginx.

Step 4: Verify the PHP Installation

Check the installed PHP version:

php -v

The CLI reports the Remi build and the bundled OPcache:

PHP 8.4.27 (cli) (built: Oct 19 2026 14:38:59) (NTS gcc x86_64)
Copyright (c) The PHP Group
Built by Remi's RPM repository <https://rpms.remirepo.net/> #StandWithUkraine #NoAI
Zend Engine v4.4.27, Copyright (c) Zend Technologies
    with Zend OPcache v8.4.27, Copyright (c), by Zend Technologies

List all loaded PHP modules to confirm your extensions are active:

php -m

With the install line above, the list includes curl, gd, intl, mbstring, mysqlnd, pgsql, redis, zip and Zend OPcache. If one is missing, the troubleshooting section at the end covers why.

Check PHP 8.4 for PHP2Shell (CVE-2026-109618, CVE-2026-109619)

PHP2Shell is a pair of critical remote code execution bugs in PHP itself, CVE-2026-109618 and CVE-2026-109619, rated CVSS v4.0 9.2 in PHP’s 20 October pre-announcement (Red Hat gives both a CVSS 3.1 score of 8.1 and rates CVE-2026-109618 Important). PHP fixed them upstream on 21 October 2026 in 8.5.12, 8.4.27, 8.3.36 and 8.2.35, per the PHP security advisory. Remi’s php:remi-8.4 stream ships 8.4.27, so a fresh install from this guide is patched and you can skip the upgrade steps that follow the package check. A server built before that date keeps running the old build until you upgrade it.

Remi was not first to the fix. PHP’s 21 October advisory told Remi users to switch to their distribution’s packages, because Remi had no update that day, and Remi published 8.4.27 by 22 October. If you ever hit a gap like that again, the fixed distribution builds are the fallback (listed below), at the cost of leaving the Remi stream. The PHP2Shell check and patch overview covers the other distributions.

Check the installed package rather than trusting the branch name. The second name covers servers that use the AppStream php8.4 packages instead of Remi:

rpm -q php-common php8.4-common

A Remi build at 8.4.27 or later is fixed. Remi’s changelog for this release only says “Update to 8.4.27” and names no CVE, so the version number is the check here:

php-common-8.4.27-1.module_php.8.4.el10.remi.x86_64
package php8.4-common is not installed

Distribution builds are numbered differently, and these are the fixed AppStream packages on EL10:

  • Rocky Linux: php 8.3.36-1.el10_2 and php8.4 8.4.27-1.el10_2
  • AlmaLinux: php 8.3.36-1.el10_2 or the earlier backport 8.3.33-1.el10_2.alma.1, and php8.4 8.4.24-1.el10_2.alma.1 or later
  • RHEL: see Red Hat’s CVE-2026-109618 page (not tested here)

The Alma backports keep an older version number with an alma.1 suffix, so compare the full release string, not the version. AlmaLinux explains its builds in the AlmaLinux PHP2Shell post.

On an existing server with an older build, upgrade every PHP package from the stream you already have enabled:

sudo dnf upgrade -y "php*"

Then restart PHP-FPM, because old worker processes keep the vulnerable code in memory until they are replaced. In our test the php-fpm package’s own file trigger restarted php-fpm during the upgrade, but restart it anyway and confirm:

sudo systemctl restart php-fpm

If Apache runs PHP through mod_php (Option B in Step 9), restart httpd as well, because nothing in the upgrade restarts it. On our mod_php box the web page still reported 8.4.26 apache2handler after the upgrade finished, and only switched to 8.4.27 once httpd was restarted:

sudo systemctl restart httpd

To prove the running service is the new one, compare the end of the upgrade transaction with the time each service last started. If the upgrade was your last dnf transaction, history info last shows its times to the second (otherwise find the ID with sudo dnf history list php-common | head -3 and pass it instead of last):

sudo dnf history info last | sed -n '1,2p;4p'

On our Rocky box the upgrade finished at 16:15:13:

Transaction ID : 11
Begin time     : Thu Oct 22 16:15:12 2026
End time       : Thu Oct 22 16:15:13 2026 (1 seconds)

Both start times must be later than that end time. Drop httpd from the command if you run Nginx:

systemctl show -p ActiveEnterTimestamp php-fpm httpd

After the restarts, php-fpm and httpd both started after the upgrade:

ActiveEnterTimestamp=Thu 2026-10-22 16:15:27 UTC

ActiveEnterTimestamp=Thu 2026-10-22 16:15:28 UTC

The most direct proof is a request through the web server, because Remi bumps PHP_VERSION on every release. Drop a small probe into the web root and fetch it:

echo '<?php echo PHP_VERSION, " ", PHP_SAPI, " jit=", ini_get("opcache.jit"), PHP_EOL;' | sudo tee /var/www/html/p2s.php
curl -s http://127.0.0.1/p2s.php

Through PHP-FPM the probe answers 8.4.27 fpm-fcgi jit=disable, and through mod_php it answers:

8.4.27 apache2handler jit=disable

Anything below 8.4.27 means a service still runs the old build. Remove the probe once you have your answer:

sudo rm -f /var/www/html/p2s.php

If dnf upgrade reports nothing to do while you still have an old AppStream build, your mirror may lag behind; run sudo dnf clean metadata and try again later. For the full RHEL family patch matrix see the PHP2Shell patch guide for RHEL, Rocky and Alma, and if a box cannot be upgraded today, the PHP2Shell mitigations reduce exposure until it can.

Step 5: Configure php.ini

The main PHP configuration file is located at /etc/php.ini. Open it with your preferred editor:

sudo vim /etc/php.ini

Below are the settings I recommend adjusting for most production workloads. Change the values to match your server resources and application requirements.

Memory and execution limits

The stock file ships memory_limit = 128M, a 30 second execution limit and max_input_vars commented out at 1000. Change the existing lines, and remove the leading semicolon from max_input_vars:

memory_limit = 256M
max_execution_time = 300
max_input_time = 300
max_input_vars = 5000

Save the file. PHP-FPM reads these values when it starts in Step 7, and the CLI picks them up immediately.

File upload settings

Raise both upload values together, since post_max_size caps the whole request body and must be at least as large as upload_max_filesize:

upload_max_filesize = 64M
post_max_size = 64M

Nginx has its own body limit of 1 MB by default, so add client_max_body_size 64m; to the server block in Step 8 if you serve uploads through it.

OPcache settings

OPcache dramatically improves PHP performance by caching precompiled bytecode in shared memory. Add or update these directives in /etc/php.d/10-opcache.ini:

sudo vim /etc/php.d/10-opcache.ini

Set the following values:

opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.revalidate_freq=2
opcache.validate_timestamps=1
opcache.save_comments=1

Leave the JIT off, which is PHP’s default (opcache.jit=disable). On EL10 with SELinux enforcing, the JIT needs memory that is writable and executable, which the httpd_t domain that php-fpm runs in is denied by default. In our enforcing Rocky Linux 10.2 VM, running PHP 8.5 from the same Remi build family, turning the JIT on made php-fpm segfault after a few requests on an execmem denial, and Nginx answered 502 from then on.

If you have a CPU-bound application and want the JIT anyway, set opcache.jit=tracing in this file, then allow executable memory and restart php-fpm. The boolean loosens SELinux for every httpd_t process (Apache, Nginx and php-fpm), so only use it when the JIT is worth that trade:

sudo setsebool -P httpd_execmem 1
sudo systemctl restart php-fpm

With the boolean on, the same VM served 40 of 40 requests with the JIT active and no new crashes. Neither command was part of our Rocky and Alma run for this guide, and mod_php cannot use the JIT at all (Option B in Step 9 explains why).

Step 6: Configure PHP-FPM

PHP-FPM handles PHP requests as a separate process pool and is the recommended way to run PHP with both Nginx and Apache. The main pool configuration file is:

sudo vim /etc/php-fpm.d/www.conf

Leave the pool user as apache, even if you serve PHP through Nginx. The stock pool already lets Nginx reach the socket through listen.acl_users = apache,nginx, while the session, OPcache and WSDL directories under /var/lib/php are owned by group apache with mode 770. Switching the pool to user = nginx breaks PHP sessions, and every later PHP package update resets those directories to apache again. The only line to change is pm.max_requests, which ships commented out. After the edit, the active lines that matter read:

user = apache
group = apache
listen = /run/php-fpm/www.sock
listen.acl_users = apache,nginx
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 35
pm.max_requests = 500

Recycling each worker after 500 requests limits the damage of slow memory leaks in extensions. Size pm.max_children from the memory a worker really uses on your application, not from these defaults. Test the configuration before starting the service, since a typo here keeps PHP-FPM from starting at all.

sudo php-fpm -t

A valid pool prints a single notice line:

[22-Oct-2026 15:42:57] NOTICE: configuration file /etc/php-fpm.conf test is successful

A quick note on the pm directive:

  • dynamic: spawns child processes on demand between min and max limits (good default for most servers).
  • static: keeps a fixed number of child processes running at all times (useful for dedicated PHP servers with predictable load).
  • ondemand: only spawns workers when requests arrive, then kills idle ones (good for low-traffic sites to save memory).

Step 7: Enable and Start PHP-FPM

Enable PHP-FPM so it comes back after a reboot, and start it now:

sudo systemctl enable --now php-fpm

Verify PHP-FPM is running:

sudo systemctl status php-fpm

Look for active (running) and the Ready to handle connections status:

● php-fpm.service - The PHP FastCGI Process Manager
     Loaded: loaded (/usr/lib/systemd/system/php-fpm.service; enabled; preset: disabled)
     Active: active (running) since Thu 2026-10-22 15:42:57 UTC; 30ms ago
   Main PID: 609 (php-fpm)
     Status: "Ready to handle connections"

Also confirm the socket file exists:

ls -la /run/php-fpm/www.sock

The socket is owned by root, and the + after the mode is the ACL that grants the apache and nginx users access:

srw-rw----+ 1 root root 0 Oct 22 15:42 /run/php-fpm/www.sock

If the socket is missing, PHP-FPM did not start, so read the journal as shown in the troubleshooting section before touching the web server.

Step 8: Integrate PHP 8.4 with Nginx

Nginx and Apache both bind port 80, so run one of them, not both. If httpd is already running on this server and you are switching to Nginx, stop and disable it first with sudo systemctl disable --now httpd. Then install Nginx if you have not already:

sudo dnf install -y nginx
sudo systemctl enable --now nginx

Create or edit your Nginx server block to pass PHP requests to PHP-FPM:

sudo vim /etc/nginx/conf.d/default.conf

Paste this server block and replace php.example.com with your own domain:

server {
    listen 80;
    server_name php.example.com;
    root /var/www/html;
    index index.php index.html;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/run/php-fpm/www.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location ~ /\.ht {
        deny all;
    }
}

Test the Nginx configuration and reload:

sudo nginx -t && sudo systemctl reload nginx

The syntax check passes before the reload runs:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Requests for php.example.com now reach PHP-FPM through the socket. If the reload fails instead, nginx -t names the file and line to fix before anything changes for live traffic.

Step 9: Integrate PHP 8.4 with Apache

Apache supports PHP through two methods: mod_php and PHP-FPM. PHP-FPM is the recommended approach because it handles processes independently and is the default on EL10. If you followed Step 8, stop Nginx before going further, because httpd cannot bind port 80 while Nginx holds it:

sudo systemctl disable --now nginx

Skip that line on a server that never had Nginx installed, since systemctl only reports that the unit does not exist.

Option A: Using PHP-FPM with Apache (Recommended)

Install Apache and start it:

sudo dnf install -y httpd
sudo systemctl enable --now httpd

Apache on EL10 runs the event MPM, and the php package’s own /etc/httpd/conf.d/php.conf already hands .php files to the FPM socket whenever mod_php is not loaded. On our test box PHP pages were served by fpm-fcgi with no extra file at all. To keep the handler visible in its own file anyway, create one:

sudo vim /etc/httpd/conf.d/php-fpm.conf

Add the handler and the default index.

<FilesMatch \.php$>
    SetHandler "proxy:unix:/run/php-fpm/www.sock|fcgi://localhost"
</FilesMatch>

DirectoryIndex index.php

Restart both services:

sudo systemctl restart php-fpm httpd

The pool user stays apache from Step 6, so nothing else changes on the PHP side.

Option B: Using mod_php with Apache

Installing httpd and php is not enough for mod_php on EL10. The module file /etc/httpd/conf.modules.d/20-php.conf only loads libphp.so under the prefork MPM, and Apache ships with event, so httpd -M | grep php prints nothing until you switch. Install and enable Apache if Option A did not already, and remove php-fpm.conf if you created it there, so it does not override the mod_php handler:

sudo dnf install -y httpd
sudo systemctl enable httpd
sudo rm -f /etc/httpd/conf.d/php-fpm.conf
sudo vim /etc/httpd/conf.modules.d/00-mpm.conf

Uncomment the prefork line and comment out the event line, so the two read:

LoadModule mpm_prefork_module modules/mod_mpm_prefork.so
#LoadModule mpm_event_module modules/mod_mpm_event.so

The JIT has to stay off inside Apache. The stock httpd.service unit sets MemoryDenyWriteExecute=yes, so when we had opcache.jit=on in the OPcache file, every PHP request crashed the Apache child and returned an empty page. Pin it off for mod_php only, so a later JIT change for PHP-FPM can never take Apache down. The IfModule wrapper matters: without it, httpd refuses to start with Invalid command 'php_admin_value' as soon as you move back to the event MPM and mod_php stops loading.

sudo vim /etc/httpd/conf.d/php-nojit.conf

Add the wrapped directive:

<IfModule mod_php.c>
    php_admin_value opcache.jit disable
</IfModule>

Check the syntax and restart Apache:

sudo httpd -t
sudo systemctl restart httpd

Then verify that Apache loaded the prefork MPM and the PHP module.

httpd -M 2>/dev/null | grep -E 'mpm|php'

Both modules appear in the list:

 mpm_prefork_module (shared)
 php_module (shared)

Note that mod_php runs PHP inside the Apache process itself, which uses more memory per worker than PHP-FPM, loses the JIT, and needs an httpd restart for every PHP security update. Prefork also switches off HTTP/2: mod_http2 stays loaded but logs AH10034: The mpm module (prefork.c) is not supported by mod_http2 and serves HTTP/1.1 only. For production servers handling significant traffic, PHP-FPM is the better choice.

Open HTTP in the Firewall

Rocky Linux and AlmaLinux run firewalld by default, so a browser on another machine cannot reach Nginx or Apache until HTTP is allowed. We did not run these firewall commands in our test setup, so check the result on your own server with sudo firewall-cmd --list-services:

sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload

The --permanent rules only take effect after the reload, so run all three lines.

Step 10: Test with phpinfo()

Create a test PHP file in your web root to verify everything is working:

echo '<?php phpinfo(); ?>' | sudo tee /var/www/html/info.php

Open your browser and navigate to the server’s address (10.0.1.50 in this example):

http://10.0.1.50/info.php

You should see the PHP information page showing version 8.4.x with all your installed extensions listed. Check the following sections:

  • PHP Version: confirms 8.4.x
  • Server API: shows FPM/FastCGI (if using PHP-FPM) or Apache 2.0 Handler (if using mod_php)
  • Loaded Configuration File: should point to /etc/php.ini
  • OPcache: should show enabled, with JIT disabled unless you turned it on in Step 5

Important: Remove the test file after verification. Leaving phpinfo() exposed is a security risk as it reveals detailed server configuration.

sudo rm -f /var/www/html/info.php

With the file gone, the server no longer advertises its PHP build and module list to anyone who guesses the URL.

Managing Multiple PHP Versions with DNF Modules

One of the advantages of using DNF module streams is the ability to switch between PHP versions without manually removing packages. On EL10 this only applies to Remi’s streams, since AppStream has none.

List available PHP module streams

The same listing from Step 2 shows which stream is enabled:

sudo dnf module list php

Every stream listed comes from the remi-modular repository.

Switch to a different PHP version

To switch from PHP 8.4 to, say, PHP 8.3:

sudo dnf module reset php -y
sudo dnf module enable php:remi-8.3 -y
sudo dnf distro-sync -y

After switching, verify the active version:

php -v

The first line now names the 8.3 branch, which on our box was the already patched 8.3.36:

PHP 8.3.36 (cli) (built: Oct 19 2026 14:00:34) (NTS gcc x86_64)

Then restart PHP-FPM and your web server:

sudo systemctl restart php-fpm
sudo systemctl restart nginx

With Nginx or Apache plus PHP-FPM, the PHP-FPM restart is the one that matters, because the old workers keep the previous version loaded until they are replaced. With mod_php, PHP lives inside Apache, so httpd is the restart that counts:

sudo systemctl restart httpd

Run only the lines for the services you actually have.

Switch back to PHP 8.4

Going back is the same three module commands with the 8.4 stream, followed by a PHP-FPM restart:

sudo dnf module reset php -y
sudo dnf module enable php:remi-8.4 -y
sudo dnf distro-sync -y
sudo systemctl restart php-fpm

On a mod_php server, restart Apache as well, or it keeps serving the 8.3 build:

sudo systemctl restart httpd

For PHP 8.5 on the same systems, the PHP 8.5 install guide uses the remi-8.5 stream the same way.

Troubleshooting

PHP-FPM fails to start

Check the PHP-FPM logs for errors:

sudo journalctl -u php-fpm -e --no-pager

Common causes:

  • Socket permission errors: verify listen.acl_users in www.conf includes your web server user.
  • Port already in use: another PHP-FPM instance or process may be binding to the same socket/port. Check with ss -tlnp | grep php.
  • Syntax errors in configuration: validate with php-fpm -t.

502 Bad Gateway in Nginx

This usually means Nginx cannot communicate with PHP-FPM. Check these:

# Confirm PHP-FPM is running
sudo systemctl status php-fpm

# Check the socket file exists and has correct permissions
ls -la /run/php-fpm/www.sock

# Verify Nginx is pointing to the correct socket path
grep fastcgi_pass /etc/nginx/conf.d/*.conf

PHP errors from the pool itself land in /var/log/php-fpm/www-error.log, which is the next file to read when the socket looks fine.

Apache error: “mprotect() failed [13] Permission denied”

This line in /var/log/httpd/error_log, followed by child pid ... exit signal Segmentation fault (11), means mod_php tried to start the JIT under the MemoryDenyWriteExecute=yes protection in httpd.service. Browsers get an empty response. Pin the JIT off for Apache with the wrapped php-nojit.conf from Option B. PHP-FPM’s unit does not set that protection, but under SELinux enforcing it still needs the httpd_execmem boolean from Step 5 before the JIT works.

PHP extensions not loading

If a specific extension does not show up in php -m output:

# Check if the extension package is installed
rpm -qa | grep php-

# Verify the .ini file exists in the scan directory
ls /etc/php.d/

# Check for errors when loading the extension
php -d display_errors=1 -r "phpinfo();" 2>&1 | grep -i error

A missing .ini file usually means the package was never installed, so dnf install the extension and restart PHP-FPM.

Module stream conflicts

If you see errors about conflicting module streams when installing PHP, or the AppStream php8.4 packages clash with installed php packages, start clean:

# Reset all PHP module streams
sudo dnf module reset php -y

# Remove any existing PHP packages
sudo dnf remove -y 'php-*' 'php8.4*'

# Re-enable the desired stream and install
sudo dnf module enable php:remi-8.4 -y
sudo dnf install -y php php-fpm php-cli

The AppStream php8.4 packages and the php packages conflict on EL10, so the remove line clears both sets before the clean install. In our test Remi’s packages also replaced an AppStream php8.4 install on their own, but a box with leftovers from both is quickest to fix this way. Add back the rest of the extensions from Step 3 once the base packages install cleanly.

SELinux blocking PHP-FPM

On RHEL-based systems, SELinux may block PHP-FPM from accessing certain files or connecting to databases. Check for denials:

sudo ausearch -m avc -ts recent

Common SELinux booleans you may need to enable:

# Allow PHP-FPM to connect to databases over the network
sudo setsebool -P httpd_can_network_connect_db 1

# Allow PHP-FPM to make outbound network connections
sudo setsebool -P httpd_can_network_connect 1

# Allow PHP-FPM to send emails
sudo setsebool -P httpd_can_sendmail 1

Turn on only the booleans your application needs. The SELinux troubleshooting guide covers reading the denials in detail.

Conclusion

You now have PHP 8.4 running on RHEL 10, Rocky Linux 10, or AlmaLinux 10. We covered installing PHP from the Remi repository using DNF module streams, configuring php.ini and PHP-FPM for production use, integrating with both Nginx and Apache, and switching between PHP versions. The Remi repository makes it straightforward to stay current with the latest PHP releases while DNF modules provide a clean way to manage version changes across your infrastructure.

Keep reading

PHP2Shell (CVE-2026-109618, CVE-2026-109619): Check and Patch PHP Php PHP2Shell (CVE-2026-109618, CVE-2026-109619): Check and Patch PHP PHP2Shell on End-of-Life PHP 8.1, 8.0 and 7.4 (WHMCS, ionCube) Php PHP2Shell on End-of-Life PHP 8.1, 8.0 and 7.4 (WHMCS, ionCube) Patch PHP2Shell (CVE-2026-109618) on Ubuntu 26.04 / 24.04 / 22.04 Php Patch PHP2Shell (CVE-2026-109618) on Ubuntu 26.04 / 24.04 / 22.04 PHP memory_hard_limit_percent Explained: New Hard Memory Limit Php PHP memory_hard_limit_percent Explained: New Hard Memory Limit Patch PHP2Shell on RHEL, Rocky Linux and AlmaLinux 10 / 9 / 8 AlmaLinux Patch PHP2Shell on RHEL, Rocky Linux and AlmaLinux 10 / 9 / 8 Install CodeIgniter 4 PHP Framework on Ubuntu 22.04|20.04|18.04 Php Install CodeIgniter 4 PHP Framework on Ubuntu 22.04|20.04|18.04

5 thoughts on “Install PHP 8.4 on RHEL 10 / Rocky Linux 10 / AlmaLinux 10”

  1. sudo dnf distro-sync -y does not look like a safe command.
    You can use sudo dnf module switch-to php:remi-8.4 to safely switch between versions.

    Reply

Leave a Comment

Press ESC to close