Set up LAMP stack

Install Apache, MariaDB, and PHP using `dnf`, then enable the services to start automatically on boot.

You just finished a fresh Fedora install and want to run a PHP application

You type the installation command, start the services, and point your browser to localhost. The browser hangs. The terminal shows nothing. The web server is running, but it is refusing to talk to your network, and SELinux is quietly blocking file access. This is the exact moment most new Fedora sysadmins hit a wall. The packages are installed, but the operating system is doing its job by keeping them isolated until you explicitly tell it otherwise.

What is actually happening

A LAMP stack is not a single application. It is four independent services that must negotiate with each other and with the operating system. Apache listens for HTTP requests. PHP processes the dynamic code. MariaDB stores the data. SELinux and firewalld sit in the middle and enforce boundaries. Think of it like a restaurant kitchen. Apache is the host taking orders. PHP is the chef cooking them. MariaDB is the walk-in fridge holding the ingredients. SELinux is the health inspector making sure the chef does not wander into the accounting office. If any one of them is misconfigured, the order never leaves the kitchen.

Fedora ships with security enabled by default. That means Apache cannot read files outside its expected directories. The firewall drops incoming packets on ports 80 and 443 until you add a rule. MariaDB refuses to start if its data directory lacks proper ownership. The system is not broken. It is waiting for explicit permission to connect these pieces.

Read the service states before you guess. Run systemctl status and check the active line.

Install and configure the stack

You need the core packages first. Fedora uses MariaDB as the default MySQL replacement. The standard PHP package includes the interpreter, but you must explicitly pull in the database driver and the FastCGI process manager.

Here is how to install the required packages in a single transaction.

sudo dnf install httpd mariadb-server php php-mysqlnd php-fpm -y
# httpd provides the Apache web server and mod_proxy_fcgi
# mariadb-server installs the database engine and initialization scripts
# php provides the interpreter and core language extensions
# php-mysqlnd gives PHP native drivers for MariaDB/MySQL communication
# php-fpm runs PHP as a persistent background process for better performance

Fedora separates package files from user configuration. Files in /usr/lib/ ship with the package and get overwritten on updates. Files in /etc/ are yours to modify. Apache configuration lives in /etc/httpd/conf.d/. MariaDB configuration lives in /etc/my.cnf.d/. Never edit files under /usr/lib/.

Start the services and enable them to persist across reboots. Systemd manages the lifecycle, so you tell it to enable the unit files and start the processes immediately.

Here is how to activate both services in one step.

sudo systemctl enable --now httpd mariadb
# enable creates symlinks in /etc/systemd/system for boot persistence
# --now starts the service immediately without requiring a second command
# httpd handles incoming HTTP/HTTPS requests and routes them to PHP
# mariadb initializes the system tables and listens on the local socket

Check the status of both units before moving forward. The systemctl status command shows recent log lines and the current state in a single view. Always verify the active line says active (running) before assuming the service is healthy.

The firewall blocks incoming web traffic by default. Fedora uses zones to group network interfaces and rules. The public zone is the default for most desktop and server installations. You must add the HTTP and HTTPS service definitions to that zone, then reload the runtime configuration.

Here is how to open the web ports and apply the changes.

sudo firewall-cmd --permanent --add-service=http
# --permanent writes the rule to the persistent configuration file
# http maps to port 80/tcp and is recognized by firewalld's service definitions
sudo firewall-cmd --permanent --add-service=https
# https maps to port 443/tcp for encrypted traffic
sudo firewall-cmd --reload
# reload merges the persistent rules into the active runtime without dropping connections

SELinux controls which processes can access which files. Apache expects web content in /var/www/html. If you place your application elsewhere, SELinux will deny access and return a 403 Forbidden error. You must label the new directory with the correct security context and restore it recursively.

Here is how to create a custom web root and apply the correct SELinux labels.

sudo mkdir -p /srv/myapp
# creates the directory structure if it does not exist
sudo chown -R apache:apache /srv/myapp
# sets the file owner to the apache user so the web server can read it
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/myapp(/.*)?"
# adds a permanent policy rule telling SELinux this path holds web content
sudo restorecon -Rv /srv/myapp
# applies the new context to existing files and prints verbose output

If your PHP application needs to make outbound database connections or fetch remote APIs, you may need to enable an SELinux boolean. The httpd_can_network_connect boolean allows Apache and PHP-FPM to initiate network connections.

Here is how to enable network access for the web server permanently.

sudo setsebool -P httpd_can_network_connect on
# -P writes the change to the persistent policy database
# httpd_can_network_connect lifts the restriction on outbound TCP connections
# on enables the boolean until the next policy reload or reboot

Reload the firewall after every rule change. The runtime configuration and the persistent configuration will diverge if you skip this step.

Verify the installation

You need a simple script to confirm Apache, PHP, and MariaDB are talking to each other. The phpinfo() function dumps the entire runtime configuration, loaded extensions, and server environment variables. It is the standard diagnostic tool for web stacks.

Here is how to create a temporary test file in the default web root.

echo "<?php phpinfo(); ?>" | sudo tee /var/www/html/info.php
# tee writes the string to the file while preserving standard output
# sudo elevates privileges so the command can write to /var/www/html
# the PHP opening tag ensures the interpreter processes the file

Open http://localhost/info.php in your browser. You should see a large table of configuration values. Look for the Server API line. It should say FPM/FastCGI or Apache 2.0 Handler. Look for the mysqlnd section. It should show version information and confirm the driver is loaded. If you see the page, the stack is operational.

Delete the test file immediately after verification. Leaving phpinfo() accessible to the public internet exposes your PHP version, loaded extensions, environment variables, and server paths. Attackers use that information to craft targeted exploits.

Here is how to remove the diagnostic file securely.

sudo rm /var/www/html/info.php
# removes the file from the web root
# sudo ensures the apache-owned file can be deleted

Run journalctl -xeu httpd and journalctl -xeu mariadb to confirm there are no hidden warnings. The x flag adds explanatory text and the e flag jumps to the end. Most sysadmins type this muscle-memory style when debugging service startup.

Common pitfalls and error messages

SELinux denials are the most frequent blocker. The audit log records every denial with a precise reason. You will see a line like type=AVC msg=audit(1698765432.123:456): avc: denied { read } for pid=1234 comm="httpd" name="index.php" dev="sda1" ino=56789 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:default_t:s0 tclass=file. The tcontext shows the current label. The tclass shows the resource type. The denial happens because the file lacks the httpd_sys_content_t label. Run restorecon -Rv /path/to/dir to fix it. Do not disable SELinux. Read the denial and apply the correct context.

Firewall blocks show up as connection timeouts in the browser. The server is running, but the packet never reaches the socket. You will see Connection refused or Network is unreachable depending on the network stack. Run sudo firewall-cmd --list-all to verify the public zone contains http and https in the services list. If the services are missing, add them and reload.

MariaDB fails to start when the data directory has incorrect ownership or when a previous crash left a lock file. The journal will print InnoDB: Unable to lock ./ibdata1 error: 11. The error code 11 means EAGAIN or resource temporarily unavailable. Stop the service, remove the stale lock files in /var/lib/mysql/, and restart. Never delete the actual database files. Only remove .pid and .lock files.

PHP-FPM refuses to communicate with Apache when the socket path is mismatched. Apache expects a Unix socket at /run/php-fpm/www.sock or a TCP port at 127.0.0.1:9000. Check /etc/httpd/conf.d/php.conf and /etc/php-fpm.d/www.conf. The listen directive in the FPM pool must match the ProxyPassMatch or SetHandler directive in Apache. Mismatched paths cause 502 Bad Gateway errors.

Trust the package manager. Manual file edits drift, snapshots stay.

When to use this versus alternatives

Use LAMP on Fedora Workstation when you are learning PHP development and want direct access to the filesystem and debugging tools. Use LAMP on Fedora Server when you are running legacy applications that depend on Apache's .htaccess rewrite rules and mod_php compatibility. Use Nginx with PHP-FPM when you need high concurrency, static file serving, and lower memory overhead per connection. Use containerized stacks with Podman when you want isolated environments, reproducible deployments, and zero host-level configuration drift. Use PostgreSQL instead of MariaDB when your application requires advanced JSON handling, complex relational integrity, or geographic data extensions.

Where to go next