How to Set Up a LAMP/LEMP Stack on Fedora

Install Apache, MySQL, and PHP for LAMP or Nginx and PHP-FPM for LEMP on Fedora using dnf and systemctl.

You installed the packages, but the browser shows a 403 or 502 error

You want to host a web application on Fedora. You install Apache, MariaDB, and PHP, start the services, and open your browser. The result is a 403 Forbidden error, a 502 Bad Gateway, or a blank page that times out. You copied a tutorial from three years ago, and the file paths no longer match. SELinux is rejecting connections you didn't know were being made. The firewall is dropping packets you forgot to allow. This guide builds a working LAMP or LEMP stack using Fedora's defaults, respects SELinux policies, and avoids the configuration drift that breaks upgrades.

What's actually happening

A web stack is a chain of handoffs. The web server receives the HTTP request. If the request targets a static file, the server reads the file and sends it back. If the request targets a PHP script, the server must pass the script to a PHP processor. The PHP processor executes the code, queries the database if needed, and returns HTML. SELinux enforces mandatory access control on every step. If the web server tries to write to a directory labeled for reading only, the kernel blocks the operation. Fedora enables SELinux in enforcing mode by default. A stack that works with SELinux disabled will fail in production.

Fedora uses MariaDB as the default database server. MariaDB is a drop-in replacement for MySQL with the same command-line tools and protocol. Fedora also prefers PHP-FPM over the older mod_php module. PHP-FPM runs PHP in a separate process pool, which isolates crashes and improves performance. The web server proxies PHP requests to PHP-FPM via a Unix socket or TCP port. Unix sockets are faster and more secure because they stay on the local filesystem.

Install and configure LAMP

LAMP stands for Linux, Apache, MariaDB, and PHP. Apache handles the HTTP requests and proxies PHP scripts to PHP-FPM. MariaDB stores the data. PHP-FPM processes the scripts.

Here's how to install the LAMP components and enable the services.

sudo dnf install httpd mariadb-server php-fpm php-mysqlnd
# WHY: Installs Apache, MariaDB, PHP-FPM, and the driver for PHP to talk to MariaDB.
# WHY: Fedora uses MariaDB as the default drop-in replacement for MySQL.
# WHY: php-fpm provides process isolation. mod_php is deprecated for new deployments.

sudo systemctl enable --now httpd mariadb php-fpm
# WHY: Starts the services immediately and ensures they start on boot.
# WHY: Order matters. Apache and PHP-FPM must be running before you test PHP.

Apache on Fedora uses proxy_fcgi to communicate with PHP-FPM. The configuration lives in /etc/httpd/conf.d/php.conf. Do not edit files in /usr/lib/httpd/. Those files are owned by packages and get overwritten on upgrade. Edit files in /etc/. The default Fedora configuration already proxies to the PHP-FPM socket. You only need to change it if you are running multiple PHP versions or custom pools.

SELinux requires Apache to have permission to access the document root and connect to the database. The boolean httpd_can_network_connect_db allows Apache to talk to MariaDB over the network. Enable it if your application connects to the database via TCP instead of a Unix socket.

sudo setsebool -P httpd_can_network_connect_db on
# WHY: Allows Apache to connect to MariaDB over TCP.
# WHY: The -P flag makes the change persistent across reboots.
# WHY: Local socket connections do not require this boolean.

Open the firewall for HTTP and HTTPS traffic. Fedora's firewall blocks incoming connections by default.

sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
# WHY: Opens ports 80 and 443 in the firewall.
# WHY: --permanent saves the rule for the next boot.
# WHY: --reload applies the change to the running firewall immediately.

SELinux blocks the request before Apache sees it. Check the context before blaming the config.

Install and configure LEMP

LEMP replaces Apache with Nginx. Nginx is lighter on memory and handles concurrent connections more efficiently. Nginx does not process PHP directly. It proxies PHP requests to PHP-FPM.

Here's how to install the LEMP components.

sudo dnf install nginx mariadb-server php-fpm php-mysqlnd
sudo systemctl enable --now nginx mariadb php-fpm
# WHY: Installs Nginx, MariaDB, PHP-FPM, and the database driver.
# WHY: Nginx requires PHP-FPM. There is no equivalent to mod_php for Nginx.

Nginx needs a configuration block to proxy PHP requests to PHP-FPM. Fedora's PHP-FPM listens on a Unix socket at /run/php-fpm/www.sock by default. Create a configuration file in /etc/nginx/conf.d/ to handle PHP.

sudo tee /etc/nginx/conf.d/php-fpm.conf > /dev/null << 'EOF'
location ~ \.php$ {
    fastcgi_pass unix:/run/php-fpm/www.sock;
    # WHY: Fedora PHP-FPM listens on a Unix socket by default, not port 9000.
    # WHY: Using the socket avoids TCP overhead and is more secure.
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
}
EOF
# WHY: Defines a location block for PHP files.
# WHY: fastcgi_pass points to the PHP-FPM socket.
# WHY: SCRIPT_FILENAME tells PHP-FPM where the script lives on disk.

Test the Nginx configuration before reloading. Nginx will refuse to restart if the syntax is invalid.

sudo nginx -t
# WHY: Checks configuration syntax without reloading.
# WHY: Prevents a broken config from dropping all traffic.
# WHY: Always run this before sudo systemctl reload nginx.

sudo systemctl reload nginx
# WHY: Applies the new configuration without dropping existing connections.

Nginx returns 502 when PHP-FPM is silent. Check the socket path and the FPM service status.

Verify the stack

Create a test PHP file to confirm the web server, PHP, and database driver are working. Place the file in the document root. Apache uses /var/www/html. Nginx uses /usr/share/nginx/html by default on Fedora, but you can change it in /etc/nginx/nginx.conf.

echo "<?php phpinfo(); ?>" | sudo tee /var/www/html/info.php
# WHY: Creates a PHP file that dumps configuration and loaded extensions.
# WHY: Verifies PHP is processing and the web server can read the file.

SELinux requires the file to have the correct context. If you created the file as root in a directory that already has the right context, restorecon fixes any drift.

sudo restorecon -v /var/www/html/info.php
# WHY: Resets the SELinux context to the default for that path.
# WHY: -v prints the change so you can see if the context was wrong.
# WHY: Manual chcon commands drift after package updates. restorecon is safer.

Open http://localhost/info.php in your browser. You should see the PHP information page. Look for the pdo_mysql or mysqli section to confirm the database driver is loaded. If you see the source code instead of the rendered page, the web server is serving the file as plain text. Check that the PHP-FPM service is running and the proxy configuration is correct.

Browser testing is slow. Use curl for instant feedback.

Common pitfalls and error messages

SELinux denials are the most common cause of failure. If the web server cannot read a file or connect to the database, SELinux logs a denial. Check the audit logs for a human-readable summary.

sudo journalctl -t setroubleshoot | tail -n 20
# WHY: Shows SELinux denial summaries with suggested fixes.
# WHY: setroubleshoot translates audit logs into actionable advice.
# WHY: Read the suggestion before disabling SELinux.

If you see Permission denied in the Apache or Nginx error log, the file permissions or SELinux context is wrong. Run ls -Z /var/www/html to check the context. The directory should be labeled httpd_sys_content_t. If you uploaded files via FTP or SCP, the context might be user_home_t or unconfined_t. Use restorecon -Rv /var/www/html to fix the entire tree.

MariaDB authentication can block connections. Fedora ships MariaDB with the unix_socket plugin for the root user. This means you must connect as the Linux root user via sudo mysql. If your application tries to connect with a password, you need to create a database user with a password.

sudo mysql -e "CREATE USER 'webapp'@'localhost' IDENTIFIED BY 'strong_password';"
sudo mysql -e "GRANT ALL PRIVILEGES ON webapp_db.* TO 'webapp'@'localhost';"
# WHY: Creates a user that authenticates with a password.
# WHY: Grants access to a specific database.
# WHY: Applications should never use the root account.

If Nginx returns 502 Bad Gateway, PHP-FPM is likely down or the socket path is wrong. Check the PHP-FPM status.

sudo systemctl status php-fpm
# WHY: Shows if PHP-FPM is active and recent log lines.
# WHY: A failed FPM service causes Nginx to return 502 for all PHP requests.
# WHY: Always check status before restarting.

Read the denial message. SELinux tells you exactly what was blocked and why.

When to use LAMP versus LEMP

Use Apache when you need .htaccess files for per-directory configuration or legacy applications that expect mod_rewrite rules in the document root. Use Nginx when you are serving high traffic with many concurrent connections and want lower memory overhead. Use PHP-FPM when you want process isolation and better performance tuning for PHP workloads. Use MariaDB when you need a drop-in replacement for MySQL with active Fedora community support. Use dnf module list php when you need a specific PHP version that differs from the default stream. Use firewall-cmd --reload after every rule change to keep runtime and persistent configurations in sync.

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

Where to go next