Linux Tutorials

Install LAMP Stack on Fedora 45 / 44 (Apache, MariaDB, PHP)

Apache on Fedora does not run PHP itself. It hands every .php request to PHP-FPM over a Unix socket.

Original content from computingforgeeks.com - post 4465

That split explains most of what goes wrong on a fresh LAMP server. SELinux blocks the database call, PHP can’t write to the upload folder, and you end up chasing the wrong service.

We built this stack in October 2026 on Fedora 45 with Apache 2.4.68, MariaDB 12.3.2 and PHP 8.5.11, then repeated the install on Fedora 44. Both ran with SELinux enforcing, and the SELinux errors below are ones we hit on the Fedora 45 server.

What Fedora 45 and 44 install

Everything comes from the Fedora repositories, so you have no third-party repo to add.

PHP is the same version on both releases. MariaDB differs by a whole series, and Fedora 44 picked up a newer Apache from updates than Fedora 45 had in its base repository.

ComponentPackageFedora 45Fedora 44
Apachehttpd2.4.682.4.69
MariaDBmariadb-server12.3.211.8.8
PHPphp, php-fpm8.5.118.5.11
Certbotcertbot5.8.05.8.0

If you need a different MariaDB series or want MySQL in its place, see MariaDB on Fedora or MySQL on Fedora. The rest of this guide works with either.

Install Apache, MariaDB and PHP

One transaction installs the three servers and the PHP extensions most apps expect.

sudo dnf install -y httpd mariadb-server php php-mysqlnd php-gd php-intl php-mbstring php-xml php-pecl-zip

On our Fedora 45 Cloud image that came to 70 packages, including the MySQL driver, GD, intl, mbstring, XML and zip.

You will not find php-fpm in the command, yet it’s pulled in as a weak dependency of php, along with php-cli.

Next, enable Apache and MariaDB.

PHP-FPM does not need its own enable. The php-fpm package drops Wants=php-fpm.service into /usr/lib/systemd/system/httpd.service.d/, so Apache starts it on every boot even though systemctl is-enabled php-fpm says disabled.

sudo systemctl enable --now httpd mariadb
systemctl is-active httpd mariadb php-fpm

All three report active, and the version checks match the table.

Apache, MariaDB and PHP-FPM active on Fedora 45 with httpd -v, mariadb --version and php -v output

The hand-off to PHP-FPM is defined in /etc/httpd/conf.d/php.conf. With comments stripped, the handler is the line that matters.

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

If you change PHP settings in /etc/php.ini or /etc/php.d/, run sudo systemctl restart php-fpm, not an Apache restart.

The PHP on Fedora guide covers php.ini drop-ins and running PHP 8.4 or 8.3 from Remi if your app is not ready for 8.5.

Create the application database

MariaDB’s root account on Fedora logs in through the Unix socket, so sudo mariadb gets you in with no password.

Use it to create a database and a user for your app. Read the password into a variable first so it stays out of your shell history. Keep it to letters and digits here, since a quote, slash or dollar sign breaks the SQL and sed commands that reuse it.

read -rsp 'App DB password: ' DB_PASS; echo
sudo mariadb -e "CREATE DATABASE appdb; CREATE USER 'appuser'@'localhost' IDENTIFIED BY '$DB_PASS'; GRANT ALL ON appdb.* TO 'appuser'@'localhost';"

Listing the accounts afterwards shows no anonymous users on a fresh Fedora install.

sudo mariadb -e "SELECT user, host FROM mysql.user;"

Only the system accounts and the new one are returned on the Fedora 45 server.

User	Host
appuser	localhost
mariadb.sys	localhost
mysql	localhost
root	localhost

Connect PHP to MariaDB through SELinux

A one-line test page proves PHP can reach the database.

It takes the host from a query string, so you can try both the socket and TCP.

echo '<?php $h = $_GET["h"] ?? "localhost"; $db = new PDO("mysql:host=$h;dbname=appdb", "appuser", "DB_PASS_HERE"); echo "PHP ", PHP_VERSION, " connected to ", $db->query("SELECT VERSION()")->fetchColumn(), " via ", $h, PHP_EOL;' | sudo tee /var/www/html/dbtest.php >/dev/null
sudo sed -i "s/DB_PASS_HERE/$DB_PASS/" /var/www/html/dbtest.php

Run that block in the same shell where DB_PASS is set, or sed writes an empty password into the page. Then request it over the socket and over TCP.

curl -s http://localhost/dbtest.php
curl -s -o /dev/null -w '%{http_code}\n' 'http://localhost/dbtest.php?h=127.0.0.1'

With localhost, PDO uses the MariaDB socket and prints PHP 8.5.11 connected to 12.3.2-MariaDB via localhost.

With 127.0.0.1, which is how many app installers write the host, the second command prints 500. PHP-FPM writes the reason to /var/log/php-fpm/www-error.log.

PHP Fatal error:  Uncaught PDOException: SQLSTATE[HY000] [2002] Permission denied in /var/www/html/dbtest.php:1

“Permission denied” on a TCP connect is SELinux, even though ausearch -m avc came back empty for us.

The block comes from the httpd_can_network_connect_db boolean, which Fedora leaves off on a new install.

PHP-FPM can reach the socket but not a TCP port until you turn it on. Make it permanent with -P.

sudo setsebool -P httpd_can_network_connect_db on

Once you repeat the request with curl -s 'http://localhost/dbtest.php?h=127.0.0.1', it connects over TCP.

PDO over 127.0.0.1 returns HTTP 500 until httpd_can_network_connect_db is enabled on Fedora 45

Database ports are all this boolean covers. If your app also calls external APIs or an SMTP relay, it needs httpd_can_network_connect or httpd_can_sendmail as well.

Delete the test page when you are done, since it holds the database password.

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

Open HTTP and HTTPS in firewalld

Our Fedora Cloud image did not include firewalld.

If firewall-cmd is missing on yours too, install and start it first. The default zone allows the ssh service on port 22 only.

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

If sshd on a remote server listens on another port, open it between those two commands. firewall-cmd can’t do that yet because the daemon isn’t running, but firewall-offline-cmd can. We tested sudo firewall-offline-cmd --add-port=2222/tcp on Fedora 44, and firewall-cmd --list-ports showed 2222/tcp once firewalld started.

Once firewalld is running, allow both web services and reload.

sudo firewall-cmd --permanent --add-service={http,https}
sudo firewall-cmd --reload
sudo firewall-cmd --list-services

Both should appear in the list.

dhcpv6-client http https mdns ssh

Create an Apache virtual host

Keep each site in its own directory under /var/www and its own file in /etc/httpd/conf.d/.

The domain is set once and reused by every command below.

DOMAIN=app.example.com
sudo mkdir -p /var/www/${DOMAIN}
echo '<?php echo "vhost " . $_SERVER["SERVER_NAME"] . " on PHP " . PHP_VERSION . PHP_EOL;' | sudo tee /var/www/${DOMAIN}/index.php

Write the vhost with printf, which fills your domain into every line.

printf '<VirtualHost *:80>\n    ServerName %s\n    DocumentRoot /var/www/%s\n    <Directory /var/www/%s>\n        AllowOverride All\n        Require all granted\n    </Directory>\n    ErrorLog /var/log/httpd/%s-error.log\n    CustomLog /var/log/httpd/%s-access.log combined\n</VirtualHost>\n' "$DOMAIN" "$DOMAIN" "$DOMAIN" "$DOMAIN" "$DOMAIN" | sudo tee /etc/httpd/conf.d/${DOMAIN}.conf

tee echoes the finished file.

<VirtualHost *:80>
    ServerName app.example.com
    DocumentRoot /var/www/app.example.com
    <Directory /var/www/app.example.com>
        AllowOverride All
        Require all granted
    </Directory>
    ErrorLog /var/log/httpd/app.example.com-error.log
    CustomLog /var/log/httpd/app.example.com-access.log combined
</VirtualHost>

AllowOverride All lets WordPress, Laravel and similar apps manage their own rewrite rules in .htaccess.

Run a config test before reloading.

sudo apachectl configtest

On a fresh Fedora server it passes but warns about the server name.

AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using fe80::be24:11ff:fef2:a361%eth0. Set the 'ServerName' directive globally to suppress this message
Syntax OK

It is harmless, but it repeats on every restart and buries real errors in the journal.

A global ServerName in its own file silences it. Use the server’s own hostname here, not the site’s.

echo 'ServerName web01.example.com' | sudo tee /etc/httpd/conf.d/00-servername.conf
sudo apachectl configtest
sudo systemctl reload httpd

With the 00- prefix, the file stays at the top of conf.d where you will find it later.

To test the site before DNS points at the server, send a Host header.

curl -s -H "Host: ${DOMAIN}" http://localhost/

Apache picks the vhost and PHP-FPM runs the page.

vhost app.example.com on PHP 8.5.11

Directories you create under /var/www inherit the httpd_sys_content_t label, so SELinux lets Apache read them without any extra step. Check with ls -Zd /var/www/${DOMAIN} if a page returns 403.

Give PHP a writable directory

Uploads, caches and session folders fail for the same reason.

The default label under that tree is read-only for PHP, and ownership alone is not enough. We created a storage directory, gave it to the apache user that PHP-FPM runs as, and added a test page that writes a file into it.

echo '<?php $f = __DIR__ . "/storage/test.txt"; echo file_put_contents($f, "ok") !== false ? "wrote $f" : "write failed", PHP_EOL;' | sudo tee /var/www/${DOMAIN}/write.php >/dev/null
sudo mkdir -p /var/www/${DOMAIN}/storage
sudo chown apache:apache /var/www/${DOMAIN}/storage
curl -s -H "Host: ${DOMAIN}" http://localhost/write.php

The page printed write failed. A plain permission error was logged in /var/log/php-fpm/www-error.log, and once again there was no AVC to search for.

PHP Warning:  file_put_contents(/var/www/app.example.com/storage/test.txt): Failed to open stream: Permission denied in /var/www/app.example.com/write.php on line 1

Label the directory httpd_sys_rw_content_t with a persistent rule, then apply it.

sudo semanage fcontext -a -t httpd_sys_rw_content_t "/var/www/${DOMAIN}/storage(/.*)?"
sudo restorecon -Rv /var/www/${DOMAIN}/storage

restorecon reports the relabel, and the same curl now writes the file.

PHP write to an apache-owned directory fails until it is labelled httpd_sys_rw_content_t with semanage and restorecon

Prefer semanage over chcon. A chcon label is lost the next time anything runs restorecon on /var/www, and the uploads break again with no change on your side. Repeat the rule for every path the app writes to, such as wp-content/uploads for WordPress. Then remove the test page with sudo rm -f /var/www/${DOMAIN}/write.php.

Enable HTTPS with mod_ssl and Certbot

Install mod_ssl first.

It adds the *:443 listener, and a self-signed certificate for localhost is generated on the next restart.

sudo dnf install -y mod_ssl
sudo systemctl restart httpd
sudo ls /etc/pki/tls/certs/localhost.crt /etc/pki/tls/private/localhost.key

Port 443 answers straight away, but only through the _default_:443 vhost in ssl.conf. A normal curl exits with code 60 because nothing trusts that certificate.

If you ask for the site over HTTPS, you get Fedora’s test page with a 403, not your app.

curl -sk -o /dev/null -w "%{http_code}\n" https://localhost/

That prints 403, and adding -H "Host: ${DOMAIN}" returns the same Fedora test page. Your vhost only exists on port 80 until something writes a *:443 copy of it.

For a public site, let Certbot do that with its Apache plugin.

sudo dnf install -y certbot python3-certbot-apache
sudo certbot --apache -d ${DOMAIN}

Let’s Encrypt has to reach the server on port 80 at the name you pass to -d, so point public DNS at it first. Our lab servers sit on a private network, so we stopped at installing the plugin. certbot plugins lists apache next to standalone and webroot.

Check renewals before you walk away.

The package’s scriptlet enables certbot-renew.timer but doesn’t start it, so it sits inactive until the next reboot.

sudo systemctl start certbot-renew.timer
systemctl list-timers certbot-renew.timer --no-pager

Now the listing shows the next run.

NEXT                        LEFT LAST PASSED UNIT                ACTIVATES
Wed 2026-10-14 19:56:35 UTC  13h -         - certbot-renew.timer certbot-renew.service

1 timers listed.

From here the stack is ready for an app. phpMyAdmin on Fedora drops into this same Apache and MariaDB setup. On Fedora 44, back up your databases before the upgrade to Fedora 45, because MariaDB moves from the 11.8 series to 12.3 in the process.

Keep reading

How to Upgrade Fedora 44 to Fedora 45 (GUI, CLI and Rollback) Fedora How to Upgrade Fedora 44 to Fedora 45 (GUI, CLI and Rollback) PHP2Shell (CVE-2026-109618, CVE-2026-109619): Check and Patch PHP Php PHP2Shell (CVE-2026-109618, CVE-2026-109619): Check and Patch PHP Enable RPM Fusion and Install Multimedia Codecs on Fedora 45 / 44 / 43 Fedora Enable RPM Fusion and Install Multimedia Codecs on Fedora 45 / 44 / 43 Switch Fedora From GRUB to systemd-boot With bootctl Fedora Switch Fedora From GRUB to systemd-boot With bootctl DNF5 Cheatsheet for Fedora 45 / 44: Tested Commands and Fixes Fedora DNF5 Cheatsheet for Fedora 45 / 44: Tested Commands and Fixes How to Configure MongoDB Authentication Databases How to Configure MongoDB Authentication

7 thoughts on “Install LAMP Stack on Fedora 45 / 44 (Apache, MariaDB, PHP)”

  1. After the phpinfo.php file is created the command “sudo systemctl reload httpd” does not work. The proper command is “sudo systemctl restart httpd”. Then everything works like it should, and without using SUDO before launching the phpinfo.php in the browser.

    Reply
  2. Hi Josephat! I noticed someone on Stack Exchange referring to this answer. Rather than the somewhat-long section on disabling SELinux, why not just explain `sudo chcon -R -t httpd_sys_content_t /srv/www/root` (or whatever path to files)? Seems like that’s actually just as easy, and leaves SELinux protection in place for other things.

    Reply

Leave a Comment

Press ESC to close