AlmaLinux

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

You need PHP on a RHEL 10 family server, and since 21 October 2026 it has to be a build patched against PHP2Shell. That is the pair of critical PHP bugs that put every WordPress, Laravel, Drupal or Magento host on an upgrade list. RHEL 10, Rocky Linux 10, and AlmaLinux 10 ship PHP 8.3 in the default AppStream repository, and the Remi repository provides access to newer versions like PHP 8.4 and PHP 8.5. Both carry the fix, though a lagging Rocky mirror can hide it.

Original content from computingforgeeks.com - post 1097

This guide covers installing PHP on AlmaLinux 10, Rocky Linux 10, and RHEL 10, from the default AppStream package to the latest PHP 8.5 via the Remi repository. We also cover PHP-FPM configuration with both Nginx and Apache, key php.ini tuning, and switching between PHP versions. Check the PHP supported versions page for current lifecycle dates before choosing a version for production. I reran the AppStream and Remi installs on fresh AlmaLinux 10 boxes in October 2026, with PHP-FPM serving pages through Nginx and Apache; firewall, SELinux and RHEL 10 itself were not re-tested.

Prerequisites

  • A server running AlmaLinux 10, Rocky Linux 10, or RHEL 10
  • Root or sudo access
  • Internet connectivity for package downloads
  • A web server (Nginx or Apache) if you plan to serve PHP pages; we cover both setups

Step 1: Install PHP from AppStream (Default Version)

RHEL 10 and its derivatives dropped modularity, so PHP 8.3 is available directly from the AppStream repository without enabling any module streams. This is the simplest path if PHP 8.3 meets your requirements.

Install PHP with a set of commonly needed extensions:

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

Confirm the installed PHP version:

php -v

The output confirms PHP 8.3 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

That build is the patched one (more on that in the PHP2Shell check below). If PHP 8.3 is all you need, jump to the PHP2Shell check that follows Step 2 first, since that is where a lagging mirror shows up, and then carry on with Step 3 for extensions. If you need PHP 8.4 or 8.5, continue to Step 2.

Step 2: Install PHP 8.5 or 8.4 from Remi Repository

The Remi repository maintains up-to-date PHP packages for Enterprise Linux. PHP 8.5 is the latest stable release with active support until December 2027, and PHP 8.4 remains in active support until December 2026.

Enable EPEL and Remi repositories

Install EPEL first, then the Remi release package for EL10:

sudo dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-10.noarch.rpm
sudo dnf install https://rpms.remirepo.net/enterprise/remi-release-10.rpm

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

sudo crb enable

The crb helper comes with the epel-release package, so it only exists after the EPEL install above. It printed CRB repo is enabled and named: crb on our AlmaLinux box.

Install PHP 8.5 from Remi

Reset any existing PHP module configuration and enable the Remi PHP 8.5 stream:

sudo dnf module reset php
sudo dnf module enable php:remi-8.5

Now install PHP with common extensions:

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

Verify that PHP 8.5 is now active:

php -v

You should see PHP 8.5 confirmed in the output:

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

To install PHP 8.4 instead, replace remi-8.5 with remi-8.4 in the module enable command. Do not mix this with the php8.4 packages that Rocky and AlmaLinux also publish in AppStream: on EL10 the php8.4-* packages conflict with the plain php-* 8.3 packages, so dnf refuses to install one set on top of the other.

Check Your Build Against PHP2Shell (CVE-2026-109618 and CVE-2026-109619)

PHP2Shell is two critical bugs in PHP itself, CVE-2026-109618 and CVE-2026-109619 (both rated CVSS v4.0 9.2 by PHP; Red Hat rates CVE-2026-109618 Important), that allow remote code execution through crafted HTTP requests. PHP fixed them on 21 October 2026 in 8.5.12, 8.4.27, 8.3.36 and 8.2.35, and both repositories in this guide already ship the fix. The PHP security announcement is clear that updating is the only complete fix.

On EL10 the package version is the whole check. The AppStream changelog only says “rebase to 8.3.36” and Remi writes “Update to 8.5.12”, with no CVE IDs, so grepping the changelog for the CVE finds nothing even on a patched box. Query the installed package instead:

rpm -q php-common php8.4-common

The second name covers the separate php8.4 packages, and whichever set you did not install shows up as not installed. Our AlmaLinux 10 box with AppStream PHP returned the fixed build:

php-common-8.3.36-1.el10_2.x86_64
package php8.4-common is not installed

Compare your result against the first fixed package for the source you installed from:

  • AppStream php on Rocky and Oracle Linux: 8.3.36-1.el10_2
  • AppStream php on AlmaLinux: 8.3.33-1.el10_2.alma.1, a backport listed in the AlmaLinux PHP2Shell notice, or the later 8.3.36-1.el10_2
  • AppStream php8.4 on Rocky and Oracle Linux: 8.4.27-1.el10_2
  • AppStream php8.4 on AlmaLinux: 8.4.24-1.el10_2.alma.1, a backport that keeps the old version number
  • Remi php:remi-8.5 and php:remi-8.4: 8.5.12 and 8.4.27

Remi was a day behind. PHP’s 21 October advisory told users of rpms.remirepo.net to switch to distribution packages, because “The repo will not have updates today”. Remi published 8.5.12 and 8.4.27 on 22 October, so a Remi box that still shows an older build only needs the upgrade below. If you cannot wait on a third-party repo next time, the AppStream builds listed above are the fixed fallback.

On RHEL 10 itself we did not test, and Red Hat often backports instead of rebasing, so compare against the fixed package named in the errata linked from Red Hat’s CVE-2026-109618 page rather than any number above.

Anything older needs an upgrade, followed by a php-fpm restart so the workers load the new code. Apache does not need a restart, because on EL10 it hands PHP to php-fpm instead of loading it in-process:

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

A backport such as the Alma build keeps the old version number, so php -v cannot tell you whether the running workers are the new ones. Check when the service last started instead:

systemctl show -p ActiveEnterTimestamp php-fpm

We rolled an AlmaLinux 10 box back to the unpatched 8.3.33-1.el10_2 build to try this, and after the upgrade 13 packages moved to 8.3.36. The restart time sits after the upgrade, so php-fpm is running the patched code:

ActiveEnterTimestamp=Thu 2026-10-22 15:24:00 UTC

If that timestamp is older than your upgrade, the restart did not happen and the old workers are still serving requests.

Rocky Linux mirror still serving 8.3.33

On 22 October the Rocky mirror our test box picked still served AppStream metadata from before the fix. The symptom is easy to miss: rpm -q php-common shows 8.3.33, yet dnf upgrade "php*" reports nothing to do, even after dnf clean metadata. Look at the age of the metadata dnf is using:

dnf repolist -v appstream | grep -E "Repo-updated|Repo-baseurl"

That mirror had not synced since the morning of 21 October:

Repo-updated       : Wed Oct 21 07:49:38 2026
Repo-baseurl       : https://mirror.example.com/mirrors/rockylinux/10.2/AppStream/x86_64/os/ (59 more)

You can wait for the mirror to catch up, but for a public PHP site we would not. This one-off upgrade pulls AppStream straight from the Rocky master server for a single transaction and leaves your repo files untouched. The URL is for x86_64; on an ARM server replace x86_64 with aarch64:

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

That moved our Rocky Linux 10 box from 8.3.33 to the fixed build, which rpm -q php-common confirmed straight afterwards:

php-common-8.3.36-1.el10_2.x86_64

Follow it with the same php-fpm restart and start-time check shown earlier. Every EL10 branch this guide installs has a fix, so you do not need the interim workarounds from the advisory here; patching is the fix.

Step 3: Install PHP Extensions

PHP extensions add support for databases, image processing, caching, and other functionality your application needs. The base install from Step 1 or Step 2 covers most common use cases, but here is how to find and install additional extensions.

List all available PHP extensions:

dnf search php- | grep -i "^php-"

The table below covers commonly needed PHP extensions and what they provide:

PackagePurpose
php-mysqlndMySQL/MariaDB native driver
php-pgsqlPostgreSQL driver
php-redisRedis client extension
php-gdImage processing (JPEG, PNG, GIF)
php-mbstringMulti-byte string handling (UTF-8)
php-xmlXML parsing and DOM support
php-curlHTTP client (cURL bindings)
php-zipZIP archive handling
php-intlInternationalization (ICU library)
php-opcacheBytecode caching for performance
php-soapSOAP web services
php-ldapLDAP directory access
php-bcmathArbitrary precision math
php-imagickImageMagick bindings

Install any additional extensions you need. Redis and ImageMagick support do not come from AppStream: php-redis and php-imagick live in EPEL, and on our AppStream-only AlmaLinux box dnf answered Error: Unable to find a match: php-imagick until EPEL and CRB were enabled. If you stayed on AppStream PHP 8.3, run only the EPEL install and sudo crb enable from Step 2; the Remi lines are only for readers moving to PHP 8.4 or 8.5. With those repositories in place, add both:

sudo dnf install php-redis php-imagick

After installing extensions, verify they are loaded:

php -m

This lists all loaded PHP modules. You can also check a specific extension:

php -m | grep -i redis

A line reading redis means the extension is loaded. No output means the CLI did not load it, and php --ini shows which ini files PHP actually read.

Step 4: Configure PHP-FPM

PHP-FPM (FastCGI Process Manager) is the standard way to run PHP with both Nginx and modern Apache setups. It runs as a separate service that handles PHP requests from the web server.

Enable and start PHP-FPM:

sudo systemctl enable --now php-fpm

Verify the service is running:

systemctl status php-fpm

Look for Active: active (running) and enabled on the Loaded line. If the unit failed instead, sudo php-fpm -t tests the configuration and names the file and line it cannot parse.

The main PHP-FPM pool configuration file is at /etc/php-fpm.d/www.conf. Open it to tune pool settings:

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

Key settings to adjust for production workloads:

; Use a Unix socket for better performance (default on EL10)
listen = /run/php-fpm/www.sock

; Process manager - dynamic is good for most workloads
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 35
pm.max_requests = 500

Leave the socket permissions alone. The EL10 pool file already sets listen.acl_users = apache,nginx, which lets both web servers use the socket, and while that ACL is set php-fpm ignores listen.owner and listen.group: php-fpm -t prints WARNING: [pool www] ACL set, listen.owner = 'nginx' is ignored if you add them. Leave user = apache and group = apache as shipped too, even behind Nginx. On EL10 the session, wsdlcache and opcache directories under /var/lib/php belong to group apache with mode 0770, and /var/log/php-fpm is owned by apache, so a pool switched to nginx cannot write sessions or its error log.

After making changes, restart PHP-FPM:

sudo systemctl restart php-fpm

With the shipped owner and the ACL, the same pool serves Nginx and Apache alike, and session files land in /var/lib/php/session owned by apache.

Step 5: Configure PHP with Nginx

Nginx does not process PHP natively; it passes PHP requests to PHP-FPM over a Unix socket. If Nginx is already running, for example from our WordPress with Nginx on Rocky Linux 10 / AlmaLinux 10 setup, add the PHP handler to your server block.

Install Nginx if you have not already:

sudo dnf install nginx
sudo systemctl enable --now nginx

Create a server block configuration for your site:

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

Add the following configuration that passes .php requests to PHP-FPM, replacing example.com with your own domain:

server {
    listen 80;
    server_name 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

Open the firewall for HTTP and HTTPS traffic. See our Firewalld guide for Rocky Linux 10 for more details on zone-based rules:

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

We served a test page through this exact server block with the pool left as apache. It called session_start(), and two requests sharing a cookie returned the same session ID with the counter going from 1 to 2, so sessions are written and read back through Nginx:

sid=78e3ov8gf5aq043b5qls2fv3pf n=1 sapi=fpm-fcgi v=8.3.36
sid=78e3ov8gf5aq043b5qls2fv3pf n=2 sapi=fpm-fcgi v=8.3.36

Remove any probe file like that once you have seen it work.

Step 6: Configure PHP with Apache

Apache can handle PHP through PHP-FPM using the mod_proxy_fcgi module, which is included by default in Apache on RHEL 10. This is the recommended approach over the older mod_php.

Steps 5 and 6 are alternatives, not a sequence. Nginx and Apache both listen on port 80, so systemctl enable --now httpd fails while Nginx holds the port. If you followed Step 5 and want Apache instead, stop and disable Nginx first:

sudo systemctl disable --now nginx

Then install Apache and start it:

sudo dnf install httpd
sudo systemctl enable --now httpd

PHP-FPM ships a default Apache configuration file that sets up the proxy. Verify it exists:

cat /etc/httpd/conf.d/php.conf

The file should contain a handler that forwards .php files to PHP-FPM. If you need to customize it, open the file:

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

The handler in the shipped file uses the Unix socket:

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

The pool already runs as apache, so nothing changes there. Restart both PHP-FPM and Apache to pick up the handler:

sudo systemctl restart php-fpm httpd

Open the firewall for web traffic if you have not done so already:

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

On both AlmaLinux 10 and Rocky Linux 10 a test page served by Apache came back from PHP-FPM with no edits to php.conf, and on AlmaLinux the session probe from Step 5 kept its session across requests here too, so the shipped handler is all most sites need.

Step 7: Configure php.ini Settings

The main PHP configuration file controls memory limits, upload sizes, error handling, and timezone settings. These defaults are conservative and usually need adjustment for production web applications.

Open the PHP configuration file:

sudo vim /etc/php.ini

Update the following directives based on your application requirements:

; Memory limit - increase for CMS/frameworks like WordPress, Laravel
memory_limit = 256M

; Maximum upload file size
upload_max_filesize = 64M

; Maximum POST data size - must be >= upload_max_filesize
post_max_size = 64M

; Maximum execution time in seconds
max_execution_time = 300

; Maximum input variables (increase for complex forms/WooCommerce)
max_input_vars = 5000

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

; Error display - disable in production, enable in development
display_errors = Off
log_errors = On

; OPcache settings for production performance
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

If you are running a LAMP stack on RHEL 10 with WordPress or a similar CMS, the settings above are a solid starting point. For Laravel or Symfony applications, you may also want to increase max_execution_time for queue workers.

Restart PHP-FPM after making changes:

sudo systemctl restart php-fpm

The CLI reads the same /etc/php.ini, so php -i | grep memory_limit confirms the new value, but a phpinfo page served through the web server (Step 9) is the definitive check for what PHP-FPM loaded.

Step 8: Switch PHP Versions

If you installed PHP from the Remi repository, switching between versions is straightforward using DNF module streams. This is useful when testing application compatibility or upgrading to a newer PHP release.

Check which PHP module stream is currently enabled:

dnf module list php

The output shows available streams with [e] marking the enabled one:

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 [e] 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

To switch from PHP 8.5 to PHP 8.4, let dnf change the stream and swap the PHP packages in one transaction. Unlike a bare dnf distro-sync, this touches only the module’s packages and leaves the rest of the system alone:

sudo dnf module switch-to php:remi-8.4

On our AlmaLinux box it downgraded 14 packages from 8.5.12 to 8.4.27 and pulled in php-opcache as a weak dependency, ending with both builds patched. dnf also prints a warning that modularity is deprecated in DNF5, which does not stop the switch. We tried dnf distro-sync "php*" as the narrower alternative and it failed with cannot install both php-common-8.4.27 and php-common-8.5.12, so stick with switch-to.

Verify the version change:

php -v

Restart PHP-FPM after the switch:

sudo systemctl restart php-fpm

Always test your application after switching PHP versions. Check the PHP error log for deprecation notices or incompatibilities:

sudo tail -f /var/log/php-fpm/www-error.log

That is the pool’s log. The shipped pool sets php_admin_value[error_log] = /var/log/php-fpm/www-error.log, which overrides any error_log in php.ini for web requests, and phpinfo through Apache showed that path even with php.ini pointing elsewhere. The error.log file next to it is the php-fpm master log, readable by root only. Run rpm -q php-common after any switch as well, so you know the stream you landed on carries the PHP2Shell fix.

Step 9: Verify PHP Installation with phpinfo()

Create a PHP info page to confirm that PHP is working correctly with your web server. This page shows the full PHP configuration, loaded extensions, and server environment.

Create the test file in your web root:

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

Open your browser and navigate to http://10.0.1.50/info.php (your server’s IP). You should see the PHP information page showing your installed version, loaded modules, and configuration directives.

After verifying, remove the info page immediately. It exposes server details that attackers can use:

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

For a quick command-line verification without creating a web-accessible file, use:

php -i | head -20

You can also check that PHP-FPM is processing requests through the web server by checking the PHP-FPM status. Our older LEMP stack guide for Rocky Linux 8 and AlmaLinux 8 shows how to expose the PHP-FPM pool status page for real-time process metrics.

Conclusion

PHP is now installed and configured on your AlmaLinux 10, Rocky Linux 10, or RHEL 10 server. You have the default PHP 8.3 from AppStream or a newer version from the Remi repository, PHP-FPM running as a service, and integration with either Nginx or Apache.

For production deployments, enable HTTPS with Let’s Encrypt, set display_errors = Off, configure log rotation for PHP-FPM logs, and monitor PHP-FPM pool utilization to tune pm.max_children based on your server’s available memory. If you are running a Java application server like Tomcat alongside PHP, make sure to allocate memory budgets for each service separately.

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 Best RHCSA Books for 2026: RHEL 10 (EX200) and RHCE Study Guides RHEl Best RHCSA Books for 2026: RHEL 10 (EX200) and RHCE Study Guides Install Apache NetBeans IDE on Rocky Linux 10 / AlmaLinux 10 AlmaLinux Install Apache NetBeans IDE on Rocky Linux 10 / AlmaLinux 10

Leave a Comment

Press ESC to close