AlmaLinux

Install PHP 8.4 on Rocky Linux 10 / AlmaLinux 10

PHP is a general-purpose scripting language that powers the majority of dynamic websites and web applications, including WordPress, Drupal, and Laravel. Rocky Linux 10 and AlmaLinux 10 ship PHP 8.3 as the default php packages in AppStream, and the same repository now carries a parallel set of php8.4 packages, so you no longer need a third-party repo to run PHP 8.4. The Remi repository is still the way to get PHP 8.5 or switch branches with a module stream.

Original content from computingforgeeks.com - post 74938

This guide covers all three paths: PHP 8.3 from AppStream, PHP 8.4 from AppStream, and PHP 8.4 from Remi. We also check the build against the PHP2Shell bugs, configure PHP-FPM with Nginx, install common extensions, tune php.ini for production, and handle firewall and SELinux settings. The install paths, the PHP2Shell check and the PHP-FPM plus Nginx setup below were run on Rocky Linux 10.2 and AlmaLinux 10.2 in October 2026, and every systemctl step was run on Rocky Linux 10.2 as well.

Prerequisites

  • A server running Rocky Linux 10 or AlmaLinux 10 with root or sudo access
  • At least 1 GB RAM and 10 GB free disk space
  • A working internet connection to download packages
  • Nginx to serve PHP web applications (Step 5 installs it if it is missing)

Step 1: Install PHP 8.3 from AppStream (Default)

Rocky Linux 10 and AlmaLinux 10 include PHP 8.3 in the default AppStream repository. This is the simplest option and receives security updates through the OS lifecycle, so it is the one to pick unless your application needs a newer branch.

Install PHP with the CLI and commonly used extensions:

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

Confirm the installed version matches PHP 8.3:

php --version

The output confirms PHP 8.3 is installed from the AppStream repository:

PHP 8.3.36 (cli) (built: Oct 19 2026 14:00:34) (NTS gcc x86_64)
Copyright (c) The PHP Group
Zend Engine v4.3.36, Copyright (c) Zend Technologies
    with Zend OPcache v8.3.36, Copyright (c), by Zend Technologies

If PHP 8.3 from AppStream meets your requirements, skip ahead to Step 3 to confirm the build is patched, then carry on with PHP-FPM. If you need PHP 8.4 features like property hooks, asymmetric visibility, or the new array_find() functions, continue with Step 2.

Step 2: Install PHP 8.4

PHP 8.4 brings significant improvements including property hooks, asymmetric visibility, lazy objects, HTML5 DOM parser, new array functions (array_find, array_any, array_all), and multibyte string functions (mb_trim, mb_ucfirst). You can get it two ways on Rocky Linux 10 or AlmaLinux 10. Option A uses the php8.4 packages that already sit in AppStream, so there is no extra repository to trust or maintain, and it is what we would run on a server that only needs 8.4. Option B uses Remi, which is worth the extra repo only when you want PHP 8.5 or plan to switch branches later.

Option A: Install the AppStream php8.4 packages

The php8.4 packages conflict with the default php 8.3 packages, because both ship the same binaries and config files. If you installed PHP 8.3 in Step 1, a plain dnf install php8.4-cli stops with package php8.4-cli-8.4.27-1.el10_2.x86_64 from appstream conflicts with php-cli < 8.4. The --allowerasing flag tells dnf to remove the 8.3 packages in the same transaction, and on a fresh server it does nothing extra:

sudo dnf install -y --allowerasing php8.4 php8.4-cli php8.4-fpm php8.4-mysqlnd php8.4-gd php8.4-mbstring php8.4-xml php8.4-pecl-zip php8.4-opcache php8.4-bcmath php8.4-intl php8.4-soap

There is no separate php8.4-curl package because cURL support is built into php8.4-common. The binary is still called php, the service is still php-fpm, and the config stays in /etc/php.ini and /etc/php-fpm.d/, so every later step in this guide applies unchanged. Check the version:

php --version

On Rocky Linux the build line names the Rocky Enterprise Software Foundation:

PHP 8.4.27 (cli) (built: Oct 19 2026 14:38:59) (NTS gcc x86_64)
Copyright (c) The PHP Group
Built by Rocky Enterprise Software Foundation
Zend Engine v4.4.27, Copyright (c) Zend Technologies
    with Zend OPcache v8.4.27, Copyright (c), by Zend Technologies

AlmaLinux reports PHP 8.4.24 with Built by AlmaLinux instead. That lower number is expected, and Step 3 shows why it is still the patched build. If you took Option A, skip to Step 3.

Option B: Add EPEL and Remi repositories

Install EPEL first, as Remi depends on it:

sudo dnf install -y epel-release

Enable the CodeReady Builder (CRB) repository, which provides development dependencies needed by some PHP extensions:

sudo dnf config-manager --set-enabled crb

Add the Remi repository for EL 10:

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

After this, dnf repolist shows remi-modular and remi-safe next to epel and crb. Nothing comes from Remi yet, because its PHP builds sit behind a module stream that you enable next.

Option B (continued): Remove default PHP and install Remi PHP 8.4

If you already installed PHP 8.3 from AppStream, remove it first to avoid conflicts:

sudo dnf remove -y php php-cli php-common php-fpm

Reset the PHP module stream and enable Remi’s PHP 8.4:

sudo dnf module reset php -y
sudo dnf module enable php:remi-8.4 -y

Install PHP 8.4 with common extensions:

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

Verify PHP 8.4 is active:

php --version

The output should confirm PHP 8.4 from the Remi repository:

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

Whichever source you picked, the next step proves the installed build carries the October 2026 security fix before the server takes any traffic.

Step 3: Check PHP for PHP2Shell (CVE-2026-109618 and CVE-2026-109619)

PHP2Shell is the name for CVE-2026-109618 and CVE-2026-109619, two remote code execution bugs in PHP itself rated CVSS v4.0 9.2 in PHP’s 20 October pre-announcement and fixed on 21 October 2026 in 8.5.12, 8.4.27, 8.3.36 and 8.2.35 (details in the PHP security advisory). Every source in this guide already ships the fix, but only if your mirror has caught up, so check what dnf actually installed. Rocky’s rebased packages and Remi’s changelogs say “rebase to 8.3.36” or “Update to 8.4.27” without the CVE IDs, which makes the package version the check:

rpm -qa 'php*-common'

Compare the result with the first patched build for your source in the table below. Anything at that release or newer is fixed:

SourceFirst patched package
AppStream php (8.3), Rockyphp-common-8.3.36-1.el10_2
AppStream php (8.3), AlmaLinuxphp-common-8.3.33-1.el10_2.alma.1 (or 8.3.36-1.el10_2)
AppStream php8.4, Rockyphp8.4-common-8.4.27-1.el10_2
AppStream php8.4, AlmaLinuxphp8.4-common-8.4.24-1.el10_2.alma.1
Remi php:remi-8.4php-common-8.4.27-1.module_php.8.4.el10.remi
Remi php:remi-8.5php-common-8.5.12-1.module_php.8.5.el10.remi

For any other Remi stream, the upstream fixed release or newer is the patched build. The remi-7.4 to remi-8.1 streams still exist, but those branches are outside upstream PHP support, so Remi’s own backports are the only fixes they get.

AlmaLinux shipped backported builds before the rebase landed (8.3.33 and 8.4.24 with an .alma.1 suffix), so on AlmaLinux the suffix is what matters, not the upstream number. Our AlmaLinux 10.2 box returned exactly that build:

php8.4-common-8.4.24-1.el10_2.alma.1.x86_64

AlmaLinux does write the CVE IDs into its changelog, so on that build you can confirm the backport directly:

rpm -q --changelog php8.4-common | grep CVE-2026-10961

Both IDs print, each with its GitHub advisory reference:

  CVE-2026-109618 (GHSA-w8hw-w9fx-j79w)
  CVE-2026-109619 (GHSA-6wmf-4rv5-p3rp)

If your version is older than the table, upgrade every PHP package and restart PHP-FPM yourself once dnf finishes. The php-fpm package scriptlet does restart the service, but partway through the transaction, before the rest of the extensions are replaced, so only a restart after dnf finishes is one you can trust. Nginx does not load PHP, so it needs no restart. On a brand new install, skip the restart here, because Step 7 starts the service:

sudo dnf upgrade -y "php*"
sudo systemctl restart php-fpm

Then confirm the service really started after the upgrade. On AlmaLinux backports the version string does not change, so the start time is the proof that the patched code is the code serving requests:

systemctl show -p ActiveEnterTimestamp php-fpm

Our Rocky test box went from php8.4-common 8.4.24 to 8.4.27. The scriptlet had restarted the service at 15:54:05, mid-transaction, and the timestamp after our own restart matched the clock at that restart:

ActiveEnterTimestamp=Thu 2026-10-22 15:55:06 UTC

When dnf says there is nothing to upgrade but the version is still old, the mirror dnf picked may be behind. On 22 October one Rocky mirror still served AppStream metadata from the morning of 21 October, with 8.3.33 as the newest php build, while the primary server already had 8.3.36. Check how fresh your mirror’s metadata is:

dnf repoinfo appstream | grep Repo-updated

The lagging mirror reported this timestamp, which predates the fixed packages:

Repo-updated       : Wed Oct 21 07:49:38 2026

On Rocky Linux you can point AppStream at the primary server for this one transaction, which pulled 8.3.36 straight away in our test. The URL is for x86_64, so replace x86_64 with aarch64 on an ARM server:

sudo dnf upgrade -y --setopt=appstream.mirrorlist= --setopt=appstream.baseurl=https://dl.rockylinux.org/pub/rocky/10/AppStream/x86_64/os/ "php*"

Restart php-fpm afterwards as above, then run rpm -qa 'php*-common' again. On AlmaLinux, run sudo dnf clean metadata and retry the upgrade later. The package version in the table is your proof, not the php --version output alone.

Step 4: Review Installed PHP Extensions

List all loaded PHP modules to confirm extensions were installed correctly:

php -m

You should see modules like curl, gd, mbstring, mysqlnd, opcache, xml, and zip in the list. If an extension is missing, install its package by name. LDAP support, for example:

sudo dnf install -y php-ldap

That name works for AppStream 8.3 and for Remi. On the AppStream php8.4 packages from Option A, every extension carries the prefix, so the same package is php8.4-ldap. The table below lists commonly needed PHP extensions and their purpose:

ExtensionPurpose
php-mysqlndMySQL/MariaDB native driver for database connections
php-gdImage processing (thumbnails, watermarks, captchas)
php-mbstringMultibyte string handling for UTF-8 content
php-xmlXML parsing and manipulation
php-curlHTTP client for API calls and remote requests
php-zipZIP archive compression and extraction
php-opcacheBytecode cache for PHP performance
php-bcmathArbitrary precision mathematics
php-intlInternationalization (formatting, transliteration)
php-soapSOAP protocol support for web services

For applications like WordPress on Nginx, the extensions above cover all requirements. Framework-specific apps may need additional extensions, so check your application’s documentation.

Step 5: Configure PHP-FPM with Nginx

PHP-FPM (FastCGI Process Manager) is the recommended way to run PHP behind Nginx. It manages worker processes, handles request queuing, and provides process-level isolation.

Configure PHP-FPM pool

Open the default PHP-FPM pool configuration:

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

Leave the pool user as apache. That sounds wrong on an Nginx server, but the packaged session directory /var/lib/php/session is root:apache with mode 770, so a pool running as nginx can’t write sessions and every login form breaks with session_start(): open(/var/lib/php/session/sess_..., O_RDWR) failed: Permission denied (13). Nginx already reaches the socket through listen.acl_users = apache,nginx, which is why listen.owner and listen.group are ignored on this pool (php-fpm -t warns ACL set, listen.owner = 'nginx' is ignored). Only the process manager needs tuning, so the relevant settings look like this:

; Packaged defaults: keep them, sessions are owned by group apache
user = apache
group = apache

; Unix socket, Nginx gets access through the ACL
listen = /run/php-fpm/www.sock
listen.acl_users = apache,nginx

; Process manager settings
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 35
pm.max_requests = 500

The pm.max_children value depends on your available RAM. Each PHP-FPM worker uses roughly 30-50 MB. For a server with 2 GB RAM, keep it at 20-30. For 4 GB or more, 50 is reasonable. Test the pool file before you start the service:

sudo php-fpm -t

A clean pool prints one notice and no ACL warnings:

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

If you already have a pool running as nginx from an older version of this guide, set user and group back to apache and restart php-fpm. Sessions start persisting with the next request, which we confirmed by reloading a counter page through Nginx three times.

Configure Nginx to pass PHP requests to PHP-FPM

Install Nginx first if the server does not have it yet:

sudo dnf install -y nginx

The php package also pulls in httpd as a weak dependency. It installs disabled, so it never competes with Nginx for port 80. Leave it that way, or run sudo systemctl disable --now httpd if you enabled it earlier.

Create or edit an Nginx server block. Open the configuration file:

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

Add the PHP-FPM location block inside your server section:

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 for syntax errors:

sudo nginx -t

A successful test shows:

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

The new server block takes effect when Nginx restarts in Step 7, after php.ini is tuned.

Step 6: Tune php.ini for Production

The default php.ini ships with conservative values. For production web applications, adjust these key directives. Open the file:

sudo vi /etc/php.ini

Update these settings for a typical production web server:

; Memory and execution limits
memory_limit = 256M
max_execution_time = 300
max_input_time = 300
max_input_vars = 5000

; File upload limits
upload_max_filesize = 64M
post_max_size = 64M

; Error handling - log errors but don't display them to visitors
display_errors = Off
log_errors = On

; Session security
session.cookie_httponly = 1
session.cookie_secure = 1
session.use_strict_mode = 1

; Timezone - set to your server's timezone
date.timezone = UTC

Two of these settings behave differently behind PHP-FPM. There is no error_log line here on purpose: the pool file sets php_admin_value[error_log] = /var/log/php-fpm/www-error.log, so errors from web requests land in www-error.log (a test warning on our box went there). And session.cookie_secure = 1 means browsers only send the session cookie over HTTPS, so logins on a plain HTTP test site will not stick until TLS is in place. The table below explains each directive and its recommended production value:

DirectiveRecommended Value
memory_limit256M (increase to 512M for heavy CMS like Magento)
max_execution_time300 (seconds, stops runaway scripts)
upload_max_filesize64M (match your application’s upload needs)
post_max_size64M (must be equal to or larger than upload_max_filesize)
display_errorsOff (never expose errors to visitors in production)
opcache.enable1 (enabled by default, big performance gain)

Configure OPcache for performance

OPcache stores precompiled PHP bytecode in shared memory, eliminating the need to parse scripts on every request. Open the OPcache configuration:

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

Set these values for production use:

opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.revalidate_freq=60
opcache.validate_timestamps=1

Setting opcache.revalidate_freq=60 means PHP checks for file changes every 60 seconds. During active development, set this to 0 so changes take effect immediately.

Step 7: Start and Enable PHP-FPM

Enable PHP-FPM to start at boot and start the service:

sudo systemctl enable --now php-fpm

The nginx package is not enabled at boot on a fresh install, so enable it and restart it to pick up the PHP-FPM configuration:

sudo systemctl enable nginx
sudo systemctl restart nginx

Verify both services are running:

sudo systemctl status php-fpm nginx

Both services should show active (running). If PHP-FPM fails to start, check the log:

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

A pool error names the file and the line, so fix it in /etc/php-fpm.d/www.conf and run sudo php-fpm -t before you start the service again.

Step 8: Configure Firewall for Web Traffic

Open HTTP (port 80/TCP) and HTTPS (port 443/TCP) in the firewall to allow web traffic:

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

Confirm the rules are active:

sudo firewall-cmd --list-services

The output should include http and https in the list of allowed services.

Step 9: Configure SELinux for PHP-FPM

Rocky Linux 10 and AlmaLinux 10 run SELinux in enforcing mode by default. PHP-FPM needs specific SELinux booleans to function properly, especially if your application connects to a database or sends emails. These SELinux commands were not part of the October 2026 re-test, so after enabling them, check sudo ausearch -m avc -ts recent for denials while you use the application.

Allow PHP-FPM to connect to database servers (required for MySQL or MariaDB connections):

sudo setsebool -P httpd_can_network_connect_db 1

Allow PHP-FPM to make outbound network connections (needed for API calls and fetching remote content). Sending mail through a local MTA uses its own boolean, httpd_can_sendmail:

sudo setsebool -P httpd_can_network_connect 1
sudo setsebool -P httpd_can_sendmail 1

If your PHP application needs to write to a directory (uploads, cache), label only that directory as writable instead of loosening the whole web policy. semanage comes from policycoreutils-python-utils:

sudo dnf install -y policycoreutils-python-utils
sudo semanage fcontext -a -t httpd_sys_rw_content_t "/var/www/html/uploads(/.*)?"
sudo restorecon -Rv /var/www/html/uploads

Verify the booleans are set:

getsebool httpd_can_network_connect_db httpd_can_network_connect httpd_can_sendmail

Each of the three booleans should report on in the output.

Step 10: Test PHP Processing

Create a test PHP file to confirm Nginx passes requests to PHP-FPM correctly:

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

Open http://10.0.1.50/info.php in a browser, using your server’s address. You should see the PHP information page showing the installed version, loaded extensions, and configuration values. For HTTPS on this same Nginx and PHP-FPM stack, the DokuWiki guide walks through certbot with the Nginx plugin on Rocky Linux 10.

Remove the test file after verification, because it exposes sensitive server details:

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

A forgotten info.php tells anyone the exact PHP build and module list, which is the first thing scanners look for after a disclosure like PHP2Shell.

Step 11: Switch Between PHP Versions

Module streams only exist in the Remi repository on EL10. AppStream has no php module, so the old dnf module enable php:8.3 fails with missing groups or modules: php:8.3. To go from Remi back to the AppStream PHP 8.3 packages, reset the stream and let distro-sync downgrade to what AppStream offers. dnf prints WARNING: modularity is deprecated, and functionality will be removed in a future release of DNF5. on these module commands; it is a notice, and the commands still work on Rocky Linux 10:

sudo dnf module reset php -y
sudo dnf distro-sync -y

To move between Remi branches, switch the stream in one command. This example moves to PHP 8.5, and dnf module list php shows every stream from remi-7.4 up:

sudo dnf module switch-to php:remi-8.5 -y

If you took Option A, there is no module involved. Going back from the AppStream php8.4 packages to PHP 8.3 is the same --allowerasing swap in reverse:

sudo dnf install -y --allowerasing php php-cli php-fpm php-mysqlnd php-gd php-mbstring php-xml php-curl php-zip php-opcache php-bcmath php-intl php-soap

After switching versions, restart PHP-FPM, verify the version, and repeat the Step 3 check against the new branch:

sudo systemctl restart php-fpm
php --version

If you run web applications backed by a LEMP stack, test your application thoroughly after any PHP version change.

Conclusion

PHP is running on Rocky Linux 10 / AlmaLinux 10, either 8.3 or 8.4 from AppStream or 8.4 from the Remi repository, and Step 3 told you whether that build carries the PHP2Shell fix. PHP-FPM is configured with Nginx, common extensions are installed, and the firewall and SELinux policies are set for production use.

For a production deployment, add SSL/TLS certificates using Let’s Encrypt, set up log rotation for /var/log/php-fpm/, monitor PHP-FPM worker usage with tools like pm.status_path, and keep PHP updated with regular dnf update runs.

Keep reading

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 on RHEL, Rocky Linux and AlmaLinux 10 / 9 / 8 AlmaLinux Patch PHP2Shell on RHEL, Rocky Linux and AlmaLinux 10 / 9 / 8 Customize KDE Plasma Desktop with Themes|Modules|Extensions AlmaLinux Customize KDE Plasma Desktop with Themes|Modules|Extensions CapRover Docker Install on Debian 13: Deploy Your First App Containers CapRover Docker Install on Debian 13: Deploy Your First App Configure Varnish Cache 7 on Ubuntu 22.04|20.04|18.04 Apache Configure Varnish Cache 7 on Ubuntu 22.04|20.04|18.04 Install Nagios Server on Rocky Linux 10 / AlmaLinux 10 AlmaLinux Install Nagios Server on Rocky Linux 10 / AlmaLinux 10

Leave a Comment

Press ESC to close