The 502 gateway error after a fresh install
You upgraded your server to Fedora and decided Apache was consuming too much memory under load. You want Nginx to handle the traffic, PHP-FPM to process the scripts, and MariaDB to hold the data. You run the install commands, point your browser to localhost, and get a 502 Bad Gateway. The packages are installed, but the pieces are not talking to each other.
How the stack actually routes a request
LEMP stands for Linux, Nginx, PHP-FPM, and MariaDB. It replaces Apache with Nginx and runs PHP as a separate FastCGI process manager instead of a loaded module. Think of it like a restaurant kitchen. Nginx is the host standing at the door, taking orders and handing out static menus instantly. PHP-FPM is the kitchen staff that actually cooks the complex dishes. MariaDB is the walk-in fridge storing the ingredients. If the host cannot reach the kitchen door, the customer gets a 502 error. If the kitchen locks the fridge, the chefs cannot cook.
Fedora enforces strict boundaries between these services using SELinux and Unix socket permissions. You must configure each boundary explicitly. Nginx never executes PHP code directly. It forwards the request to a Unix socket, waits for PHP-FPM to compile and run the script, then streams the output back to the client. This separation keeps memory usage predictable and isolates crashes. If PHP-FPM segfaults, Nginx stays up and simply returns an error page instead of dropping all connections.
Install the core services
Here is how to pull the packages from the default repositories and start the daemons.
sudo dnf install nginx php-fpm mariadb-server -y
# -y skips the confirmation prompt. Fedora's repos are signed and trusted.
sudo systemctl enable --now nginx mariadb php-fpm
# enable creates symlinks in /etc/systemd/system for boot persistence.
# --now starts the units immediately without a separate start command.
Run dnf upgrade --refresh weekly to pull in security patches. Do not confuse it with dnf system-upgrade, which is reserved for crossing major Fedora releases. The weekly refresh command only updates packages within the current release cycle.
Secure the database layer
MariaDB ships with a root account that has no password by default. You must lock it down before any application connects.
sudo mysql_secure_installation
# Prompts you to set a root password, remove anonymous users,
# disable remote root login, and drop the test database.
sudo mysql -u root -p -e "CREATE DATABASE fedora_test; CREATE USER 'fedora_user'@'localhost' IDENTIFIED BY 'strong_password'; GRANT ALL PRIVILEGES ON fedora_test.* TO 'fedora_user'@'localhost'; FLUSH PRIVILEGES;"
# Creates an isolated database and user. Applications should never run as root.
# FLUSH PRIVILEGES forces the server to reload the grant tables immediately.
Never give your application the database root account. If a PHP script gets compromised, the attacker inherits full administrative control over every database on the system. Scoped users limit blast radius.
Route PHP through the Nginx socket
Nginx needs a server block that tells it how to handle .php files. Create a custom configuration file in the drop-in directory. Config files in /etc/ are user-modified. Files in /usr/lib/ ship with the package. Always edit /etc/. Package updates will overwrite /usr/lib/ without warning.
sudo nano /etc/nginx/conf.d/lemp.conf
# Drop-in configs in conf.d are loaded alphabetically alongside the main config.
Add the following configuration. Replace /var/www/html with your actual document root if you are deploying to a different path.
server {
listen 80;
# Binds to port 80 on all interfaces. Use 443 after adding TLS.
server_name _;
# Catches all hostnames that do not match another server block.
root /var/www/html;
# Points to the directory where your PHP files live.
index index.php index.html;
# Tries index.php first, falls back to index.html.
location / {
try_files $uri $uri/ =404;
# Checks if the requested file exists. Serves it directly.
# Falls back to appending a slash for directories.
# Returns 404 if nothing matches. Prevents routing to PHP for missing files.
}
location ~ \.php$ {
include fastcgi_params;
# Loads standard CGI variables like HTTP_HOST and REQUEST_METHOD.
fastcgi_pass unix:/run/php-fpm/php-fpm.sock;
# Routes the request to the PHP-FPM Unix socket.
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# Tells PHP exactly which file to execute on disk.
fastcgi_param QUERY_STRING $query_string;
# Passes URL parameters so PHP can read $_GET variables.
}
}
Test the syntax before reloading. Nginx will refuse to restart if the configuration contains a typo.
sudo nginx -t
# Parses all config files and reports syntax errors or missing includes.
sudo systemctl reload nginx
# Gracefully reloads workers without dropping existing connections.
Run nginx -t before every config change. A single missing semicolon kills the entire web server.
Open the firewall and align SELinux contexts
Fedora ships with firewalld active and SELinux in enforcing mode. Both will block traffic until you explicitly allow it.
sudo firewall-cmd --permanent --add-service=http
# Adds a persistent rule for port 80. The --permanent flag writes to the config file.
sudo firewall-cmd --reload
# Applies the persistent rules to the running firewall. Always run this after changes.
SELinux controls which processes can access which files. Nginx runs under the nginx_t domain. Web content must carry the httpd_sys_content_t label. PHP-FPM needs permission to connect to the database socket.
sudo restorecon -R -v /var/www/html
# Recursively resets file contexts to the defaults defined in /etc/selinux/targeted/contexts/files/file_contexts.
# -v prints what changed. Use this instead of chown for web directories.
sudo setsebool -P httpd_can_network_connect_db 1
# Allows the web server domain to initiate TCP connections to the database.
# -P writes the boolean to disk so it survives reboots.
If you see [FAILED] Failed to start php-fpm.service during boot, check the socket path in /etc/php-fpm.d/www.conf. The Nginx fastcgi_pass directive and the PHP-FPM listen directive must match exactly.
Verify the stack is serving correctly
Create a minimal PHP script that prints the runtime configuration.
echo "<?php phpinfo(); ?>" | sudo tee /var/www/html/info.php
# Writes the script to the document root. tee preserves the sudo boundary.
Visit http://localhost/info.php in your browser. You should see a detailed table of PHP settings, loaded extensions, and environment variables. If you see a blank page or a download prompt, Nginx is serving the file as plain text instead of passing it to PHP-FPM. Check your location ~ \.php$ block.
Run journalctl -xeu php-fpm to inspect recent log lines and service state. The x flag adds explanatory text and the e flag jumps to the end. Most sysadmins type journalctl -xeu <unit> muscle-memory style. It shows recent log lines AND state in one view. Always check status before restarting.
Delete the test file immediately after verification. Leaving phpinfo() accessible exposes your exact PHP version, loaded modules, and server paths to anyone scanning the internet.
Common pitfalls and what the error looks like
The 502 Bad Gateway error means Nginx cannot reach PHP-FPM. The most common cause is a mismatched socket path. If PHP-FPM listens on 127.0.0.1:9000 but Nginx expects unix:/run/php-fpm/php-fpm.sock, the connection fails. Check /etc/php-fpm.d/www.conf for the listen directive. Match it exactly in your Nginx config.
SELinux denials show up in journalctl -t setroubleshoot with a one-line summary. Read those before disabling SELinux. You will usually see nginx_t denied connectto on php-fpm or read on /var/www/html. Running restorecon fixes file context drift. Setting httpd_can_network_connect_db fixes database connection blocks.
If nginx -t returns nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use), another service is occupying port 80. Run sudo ss -tlnp | grep :80 to identify the process. Apache, Caddy, or a leftover Nginx instance often cause this.
Check the socket permissions with ls -l /run/php-fpm/. The socket must be readable by the Nginx worker process. Fedora's default configuration handles this automatically, but manual edits to www.conf can break it.
When to use LEMP versus other stacks
Use LEMP when you need high concurrency with low memory overhead and your application relies on PHP. Use LAMP when you require Apache's .htaccess file-based routing or legacy mod_php compatibility. Use a reverse proxy with a dedicated application server when your workload is written in Python, Node.js, or Go. Stick to the default Fedora Workstation stack if you are only running local development environments and do not need production-grade isolation.