How To

Install Apache, MariaDB, PHP (LAMP) on Rocky Linux 9

For general web development and hosting, you need a Programming language, a database, and a web server. The LAMP stack is a collection of open-source Linux tools that include Apache, MySQL/MariaDB, and PHP. These tools work closely together to enable a server to host and run modern and dynamic web applications.

Original content from computingforgeeks.com - post 121208

The components of the LAMP stack are well elaborated below:

  • Apache: This is an open-source cross-platform web server and one of the two most widely used web servers, alongside Nginx. This software is developed and maintained by the Apache Software Foundation.
  • MariaDB: This is a relational database management system developed by the original developers of MySQL and guaranteed to stay open source.
  • PHP: This is an abbreviation of Hypertext Pre-processor. This is an open-source general scripting language that serves as a backend programming language for Web applications. Moreso, this language can also be used with HTML when creating web applications. The main difference between PHP and other languages such as JavaScript is that the codes are executed on the server, and HTML scripts are generated and sent to the client.

How does LAMP Stack work?

The Apache web server receives requests for web pages for the browser. If a PHP file is requested, then it passes the request to PHP which then loads and executes the code contained in the file. PHP also links with the database(MySQL/MariaDB) to obtain any referenced data in the code.

The PHP code and data fetched from the database are then used to generate HTML scripts that are used to display the web pages. The LAMP stack is effective when handling both static and dynamic web pages where data changes from time to time when loaded.

PHP then passes these results back to the Apache Web server which then sends them back to the browser.

In this guide, we will install and use Apache, MariaDB, and PHP (LAMP) on Rocky Linux 9. Every command below ran top to bottom on a fresh system, ending with a PHP form that wrote its row into MariaDB through PHP-FPM (re-tested October 2026 on Rocky Linux 9.8); firewalld and SELinux were not part of that run.

1. Install Apache web server

The first component to be installed here is the Apache Web server. This software is provided as httpd in the Rocky Linux repositories. To install it, execute the command:

sudo dnf install httpd

dnf lists the transaction and waits for confirmation. On our Rocky Linux 9.8 system it looked like this, and the package count varies with what is already installed:

Transaction Summary
================================================================================
Install  13 Packages

Total download size: 2.3 M
Installed size: 6.9 M
Is this ok [y/N]: y

Once installed, start and enable the service:

sudo systemctl enable --now httpd

Check the status of the service:

systemctl status httpd

The unit is enabled and active, and Apache reports that it listens on port 80:

● httpd.service - The Apache HTTP Server
     Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; preset: disabled)
     Active: active (running) since Thu 2026-10-22 19:44:42 UTC; 29ms ago
       Docs: man:httpd.service(8)
   Main PID: 197 (httpd)
     Status: "Started, listening on: port 80"
      Tasks: 177 (limit: 6425)
     Memory: 14.5M (peak: 14.5M)
        CPU: 66ms

You can also verify if Apache is running by accessing the default page on a browser. If firewalld is running (sudo systemctl is-active firewalld prints active), allow HTTP traffic through it first. A system without firewalld answers command not found, and you can skip this step:

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

Access the web page using the URL http://IP_Address or http://domain_name

Apache MariaDB PHP LAMP on Rocky Linux 9

The Rocky Linux test page in the browser confirms that Apache answers on port 80. With the web server in place, the database comes next.

2. Install MariaDB Database Server

We need a database server for web hosting. There are two options here, MySQL or the MariaDB database. In this guide, we will opt for the MariaDB database due to its high-performance storage engines, faster and safer replication, and compatibility with MySQL.

Rocky Linux 9 provides MariaDB 10 in the default repository. This can be installed using the command:

sudo dnf install mariadb-server mariadb

Once installed, start and enable the MariaDB service:

sudo systemctl enable --now mariadb

Verify if the service is running:

systemctl status mariadb

The unit description names the MariaDB 10.5 series, and the status line shows it is ready for queries:

● mariadb.service - MariaDB 10.5 database server
     Loaded: loaded (/usr/lib/systemd/system/mariadb.service; enabled; preset: disabled)
     Active: active (running) since Thu 2026-10-22 19:45:18 UTC; 15ms ago
       Docs: man:mariadbd(8)
             https://mariadb.com/kb/en/library/systemd/
    Process: 584 ExecStartPre=/usr/libexec/mariadb-check-socket (code=exited, status=0/SUCCESS)
    Process: 606 ExecStartPre=/usr/libexec/mariadb-prepare-db-dir mariadb.service (code=exited, status=0/SUCCESS)
    Process: 702 ExecStartPost=/usr/libexec/mariadb-check-upgrade (code=exited, status=0/SUCCESS)
   Main PID: 688 (mariadbd)
     Status: "Taking your SQL requests now..."
      Tasks: 13 (limit: 6425)
     Memory: 67.0M (peak: 99.1M)
        CPU: 452ms

By default, the MariaDB is not secure and this might cause vulnerabilities. To secure it, proceed as shown:

sudo mysql_secure_installation

Answer the prompts as shown. The script asks for the current root password first, and on a new install that is empty:

....
Enter current password for root (enter for none): Just press Enter
......
Switch to unix_socket authentication [Y/n] Y
.....
Change the root password? [Y/n] Y
New password:  New-root-password
Re-enter new password: Re-enter New-root-password
....
Remove anonymous users? [Y/n] Y
....
Disallow root login remotely? [Y/n] Y
.....
Remove test database and access to it? [Y/n] Y
......
Reload privilege tables now? [Y/n] Y
...
Thanks for using MariaDB!

Verify if you can log in using the created password:

mysql -u root -p

Enter the new root password. The banner reports the server version, and SELECT VERSION(); confirms it from inside the session:

Enter password: 
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 15
Server version: 10.5.29-MariaDB MariaDB Server

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> SELECT VERSION();
+-----------------+
| VERSION()       |
+-----------------+
| 10.5.29-MariaDB |
+-----------------+
1 row in set (0.000 sec)

MariaDB [(none)]> QUIT
Bye

Password login works for root, which matters later because the PHP form in step 6 connects with that password rather than through unix_socket authentication.

3. Install PHP and Extensions

The default PHP version available in the Rocky Linux 9 repositories is PHP 8.0. This version comes with bug fixes and enhancements, including the use of structured metadata syntax, newly named arguments that are order-independent, and improved performance for Just-In-Time compilation.

Install PHP 8.0 and its dependencies using the command:

sudo dnf install php-{common,gmp,fpm,curl,intl,pdo,mbstring,gd,xml,cli,zip,mysqli}

The package does not start PHP-FPM, and on our test system systemctl is-enabled php-fpm returned disabled after the install. Apache on Rocky Linux 9 runs the event MPM and hands every .php request to PHP-FPM, so a server where FPM is not enabled stops serving PHP after the next reboot. Enable and start it now:

sudo systemctl enable --now php-fpm

Check the PHP version:

php --version

Red Hat backports fixes without changing the version number, so every build of this package prints 8.0.30 and only the build date moves. The date is not proof of a patch either; the changelog check further down is:

PHP 8.0.30 (cli) (built: Oct 21 2026 20:14:53) ( NTS gcc x86_64 )
Copyright (c) The PHP Group
Zend Engine v4.0.30, Copyright (c) Zend Technologies

It is also possible to install other PHP versions. This can be done by adding the Remi repository, whose release package pulls in epel-release as a dependency, so EPEL gets enabled in the same transaction:

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

Once added, list the PHP versions available:

sudo dnf module list php

AppStream offers the 8.1, 8.2 and 8.3 streams, and Remi adds every branch from 7.4 to 8.6:

Rocky Linux 9 - AppStream
Name Stream   Profiles                   Summary               
php  8.1      common [d], devel, minimal PHP scripting language
php  8.2      common [d], devel, minimal PHP scripting language
php  8.3      common [d], devel, minimal PHP scripting language

Remi's Modular repository for Enterprise Linux 9 - 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

Enable and install the preferred version, say PHP 8.1 as shown. Take it from Remi, not from the AppStream php:8.1 stream: on our Rocky Linux 9.8 box the newest AppStream 8.1 build was 8.1.32, which does not carry the PHP2Shell fix described below, while Remi’s 8.1.34-6 does.

sudo dnf module enable php:remi-8.1 -y

Now install the PHP and its dependencies. If the default PHP 8.0 packages from above are already installed, this same command upgrades them to the Remi 8.1 builds, which it did on our test system. Because EPEL and Remi are new repositories, dnf also asks once for each of their GPG keys (the EPEL key 0x3228467C and Remi’s 0x478F8947); answer y to both:

sudo dnf install php-{common,gmp,fpm,curl,intl,pdo,mbstring,gd,xml,cli,zip,mysqli}

Verify the installation.

php --version

The CLI now reports the Remi build of the 8.1 branch:

PHP 8.1.34 (cli) (built: Oct 21 2026 09:32:21) (NTS gcc x86_64)
Copyright (c) The PHP Group
Zend Engine v4.1.34, Copyright (c) Zend Technologies

The version string alone does not say whether this build is safe to expose, so check the security patch level before Apache starts serving PHP pages.

Verify the PHP2Shell patch (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 the 20 October PHP pre-announcement and fixed upstream on 21 October 2026 in 8.5.12, 8.4.27, 8.3.36 and 8.2.35 (see the PHP security advisory). Both builds this guide installs are patched now: the default Rocky PHP 8.0 package and the Remi 8.1 package carry backports. Neither branch got an upstream release, so both still print 8.0.30 or 8.1.34, and php --version cannot tell you anything. The package changelog can, because both packagers wrote the two CVE IDs into it:

rpm -q php-common
rpm -q --changelog php-common | grep -E "CVE-2026-10961[89]"

On the Remi 8.1 install the package is release 8.1.34-6, and the grep prints one line naming both CVEs. The default AppStream package php-common-8.0.30-8.el9_8.1 printed the same CVE line on our system. If you see that line, a fresh install can stop here and move on to step 4:

php-common-8.1.34-6.module_php.8.1.el9.remi.x86_64
  CVE-2026-109618, CVE-2026-109619

Remi was not patched on day one. The 21 October PHP advisory told Remi users to switch to distribution packages because Remi had no update that day, and Remi published the fixed builds by 22 October. If you would rather run the Rocky packages, the default 8.0.30-8.el9_8.1 build and the AppStream php:8.2 (8.2.35) and php:8.3 (8.3.36) streams were all fixed on our Rocky Linux 9.8 box, while php:8.1 still served the unpatched 8.1.32. Moving from Remi 8.1 to the AppStream php:8.3 stream also moves your application up two PHP branches, so check its requirements first. On our Rocky Linux 9.8 box the switch took three commands; without -y, distro-sync shows the transaction and waits, so read it before you answer:

sudo dnf module reset php -y
sudo dnf module enable php:8.3 -y
sudo dnf distro-sync "php*"
sudo systemctl restart php-fpm

For the package set in this guide it upgraded 10 packages to 8.3.36 and downgraded php-pecl-zip to the AppStream 1.22.3 build, with nothing removed. A server that carries Remi-only extensions (php-sodium or php-pecl-* packages such as redis or imagick) has no AppStream build for them, and dnf then refuses the sync until you add --allowerasing, which removes those extensions. Red Hat wrote “rebase to 8.3.36” into the changelog without the CVE IDs, so on the AppStream php:8.2 and php:8.3 streams the grep above prints nothing and the version is the check: 8.3.36 or 8.2.35 and newer are fixed. Ours read php-common-8.3.36-1.module+el9.8.0+40396+4e680bb7.x86_64, and the probe page below reported 8.3.36 fpm-fcgi.

On an existing server where the grep prints nothing, upgrade every PHP package and restart PHP-FPM yourself. Apache on Rocky Linux 9 loads no PHP module (step 4 shows how to confirm it), so it needs no restart, but long-running PHP CLI workers such as queue workers or supervisor jobs do. If dnf finds nothing to upgrade, run sudo dnf clean metadata and retry, because a lagging mirror can still serve old repodata:

sudo dnf upgrade -y "php*"
sudo systemctl restart php-fpm
systemctl show -p ActiveEnterTimestamp php-fpm

We put our test system back on the unpatched Remi 8.1.34-5 and ran that sequence: dnf upgraded 10 packages to 8.1.34-6 and the CVE line appeared. The FPM start time has to be later than the upgrade, which sudo dnf history list shows at the top (19:54 in our run):

ActiveEnterTimestamp=Thu 2026-10-22 19:54:27 UTC

A probe page through Apache shows which build actually answers web requests. It reads 8.1.34 before and after the backport, so it proves that FPM serves the page, and the timestamp above proves the workers are new:

echo '<?php echo PHP_VERSION, " ", PHP_SAPI, "\n";' | sudo tee /var/www/html/v.php >/dev/null
curl -s http://localhost/v.php
sudo rm /var/www/html/v.php

The handler reads fpm-fcgi; on the default AppStream build the version reads 8.0.30 instead:

8.1.34 fpm-fcgi

Other distributions and repositories are covered in our PHP2Shell check and patch guide, which lists the fixed package for each.

4. Configure PHP for Apache

To configure PHP, open the file below:

sudo vi /etc/php.ini

In the file, make the desired settings. For example:

max_execution_time = 300
upload_max_filesize = 100M
post_max_size = 128M
date.timezone = Africa/Nairobi

After making the desired changes, you do not need to edit httpd.conf to make Apache run PHP. The php packages drop /etc/httpd/conf.d/php.conf, which hands every .php file to the PHP-FPM socket at /run/php-fpm/www.sock, and our test page rendered through FPM with no AddHandler line at all. Apache only reads that file when it restarts, though: before the restart below, the same page came back as raw PHP source with HTTP 200. Confirm that Apache runs the event MPM with the FastCGI proxy and no PHP module:

httpd -M | grep -E "mpm|proxy_fcgi|php"

Two modules match and there is no php_module, so PHP runs only inside PHP-FPM. Apache may also print an AH00558 warning about the server name, which does not affect this check:

 mpm_event_module (shared)
 proxy_fcgi_module (shared)

You can also check the PHP version by creating a test PHP file using the command:

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

Add the below lines to the file:

<?php
phpinfo();
?>

Save the file and restart Apache and PHP-FPM:

sudo systemctl restart php-fpm
sudo systemctl restart httpd

Access the page that displays the PHP parameters using the URL http://IP_Address/info.php. The screenshot below comes from the original July 2022 run on Rocky Linux 9.0 and shows PHP 8.0.13, a vulnerable build; on our October 2026 system the same page reported PHP Version 8.0.30, Server API FPM/FastCGI and the 100M upload_max_filesize set above.

Apache MariaDB PHP LAMP on Rocky Linux 9 1

Server API should read FPM/FastCGI on your page too. Delete the test script, because phpinfo output tells an attacker your exact PHP build and module list:

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

PHP now runs through FPM behind Apache, and the next step gives the site its own virtual host.

5. Creating Apache Virtual Host file

For you to be able to access a site via the browser, you need to create a virtual host file for the site. In this guide, we will configure Apache to serve a sample page as shown.

Create the virtual host file:

sudo vi /etc/httpd/conf.d/yourdomain.conf

In the file, add the lines below:

<VirtualHost *:80>
    ServerName test.example.com
    DocumentRoot /var/www/html/
    ErrorLog /var/log/httpd/error.log
    CustomLog /var/log/httpd/requests.log combined
</VirtualHost>

Create the file in the document root specified:

sudo vi /var/www/html/index.html

Add the lines:

<html>
  <head>
    <title>This is a test page</title>
  </head>
  <body>
    <h1>LAMP Stack is working perfectly on Rocky Linux 9!</h1>
  </body>
</html>

Restart Apache:

sudo systemctl restart httpd

Now access the web page using the URL http://IP_address or http://domain_name

Apache MariaDB PHP LAMP on Rocky Linux 9 2

Remove test page:

sudo rm /var/www/html/index.html

Apache serves the virtual host, so the last test puts PHP and MariaDB on the same page.

6. Host PHP Application with Database

To test the better working of the LAMP stack, we will configure the PHP Websites to connect to the MySQL database. The below steps are used.

Begin by creating a database.

mysql -u root -p

Create a database with the below table

CREATE DATABASE db_contact;
USE db_contact;
CREATE TABLE tbl_contact (Id INT,fldName VARCHAR(50), fldEmail VARCHAR(30), fldPhone VARCHAR(15),fldMessage VARCHAR(1000));
EXIT

Create an HTML form for connecting to the database:

sudo mkdir /var/www/html/contacts
sudo vi /var/www/html/contacts/contact.html

Add the below lines to the file:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Contact Form - PHP/MySQL Demo Code</title>
</head>

<body>
<fieldset>
<legend>Contact Form</legend>
<form name="frmContact" method="post" action="contact.php">
<p>
<label for="Name">Name </label>
<input type="text" name="txtName" id="txtName">
</p>
<p>
<label for="email">Email</label>
<input type="text" name="txtEmail" id="txtEmail">
</p>
<p>
<label for="phone">Phone</label>
<input type="text" name="txtPhone" id="txtPhone">
</p>
<p>
<label for="message">Message</label>
<textarea name="txtMessage" id="txtMessage"></textarea>
</p>
<p>&nbsp;</p>
<p>
<input type="submit" name="Submit" id="Submit" value="Submit">
</p>
</form>
</fieldset>
</body>
</html>

Now create a PHP script to save data from HTML form to your database:

sudo vi /var/www/html/contacts/contact.php

Add the lines below. The script passes the form fields to MariaDB as bound parameters through mysqli_prepare() and mysqli_stmt_bind_param(), never by pasting them into the SQL string, because a form that builds its query from raw POST data lets any visitor run SQL against your database. On our test system a name of x'); DROP TABLE contacts;-- was stored as plain text and the table stayed intact. The REQUEST_METHOD check makes a plain GET of the script return an empty page instead of inserting an empty row:

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
	$con = mysqli_connect('localhost', 'root', 'root_password', 'db_contact');

	$stmt = mysqli_prepare($con, "INSERT INTO tbl_contact (Id, fldName, fldEmail, fldPhone, fldMessage) VALUES (0, ?, ?, ?, ?)");
	mysqli_stmt_bind_param($stmt, 'ssss', $_POST['txtName'], $_POST['txtEmail'], $_POST['txtPhone'], $_POST['txtMessage']);

	if (mysqli_stmt_execute($stmt)) {
		echo "Contact Records Inserted";
	}
}
?>

Replace root_password with the MariaDB root password you set in step 2. For anything beyond this test, create a dedicated database user with rights on db_contact only and put that account in the script instead of root. Once the files have been saved, load the page using the URL http://domain_name/contacts/contact.html

Apache MariaDB PHP LAMP on Rocky Linux 9 3 1

Fill in the form and submit the changes

Apache MariaDB PHP LAMP on Rocky Linux 9 4

Verify if data has been added to the database:

mysql -u root -p

At the MariaDB prompt, query the table:

SELECT * FROM  db_contact. tbl_contact;

Sample Output:

Apache MariaDB PHP LAMP on Rocky Linux 9 5

That is it!. We can now clean up the test:

sudo rm -rf /var/www/html/contacts

Recommended books to read:

Closing Thoughts

The stack is now Apache with the event MPM handing PHP to PHP-FPM, and MariaDB 10.5 behind it. Re-run the changelog or version check from step 3 after every PHP update, because on Rocky Linux 9 the version string alone does not show whether a backported fix is installed.

See more:

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 OpenLiteSpeed Web Server on Ubuntu 24.04 Web Hosting Install OpenLiteSpeed Web Server on Ubuntu 24.04

2 thoughts on “Install Apache, MariaDB, PHP (LAMP) on Rocky Linux 9”

Leave a Comment

Press ESC to close