
Every few months a client comes back with the same complaint: “the site was fine yesterday, now it’s throwing 502s and nobody touched anything.” Nine times out of ten, the culprit traces back to a rushed LEMP stack setup where somebody skipped the SELinux context, forgot to lock down the firewall, or left PHP-FPM running under the wrong user. Fedora Linux 44, released in late April 2026, ships with fresh toolchain updates including glibc 2.43 and a newer kernel baseline, which means some of the old copy-paste tutorials floating around for Fedora 38 or 40 no longer map cleanly onto package names or default configs.
This guide walks through installing Nginx, MariaDB, and PHP-FPM on Fedora 44 the way it should be done on a server you actually intend to keep running, not just a lab box you’ll nuke next week. LEMP, for anyone who stumbled onto this from a search results page, stands for Linux, Nginx (pronounced “engine-x,” hence the E), MySQL or MariaDB, and PHP. It’s the backbone stack behind WordPress installs, Laravel apps, custom PHP APIs, and a huge chunk of the web that isn’t running on Apache.
Fedora isn’t the most common choice for production web servers, most shops lean toward Ubuntu Server or AlmaLinux for that job, but Fedora’s six-month release cycle and bleeding-edge package versions make it a genuinely good fit for developers who want to test against the newest PHP and Nginx builds before they trickle down to enterprise distros. It’s also just a solid workstation OS that doubles as a local dev server. Either way, the steps below apply whether you’re running Fedora 44 Server Edition on bare metal, in a VM, or inside a cloud instance.
We’ll cover the installation itself, but also the parts most tutorials gloss over: SELinux contexts, firewalld zones, PHP-FPM pool tuning, and the troubleshooting steps you actually need when something breaks at 2 a.m.
Before You Start: What You Need
You’ll need root or sudo access on a fresh or reasonably clean Fedora 44 install, a stable internet connection for package downloads, and roughly 20-30 minutes if nothing goes sideways. A minimum of 1GB RAM works for testing, but anything serving real traffic should have at least 2GB, especially once MariaDB’s buffer pool and PHP-FPM’s worker pool start competing for memory.
Confirm your Fedora version first, because half the “it doesn’t work” support tickets trace back to someone running an older release and following instructions meant for a newer one:
cat /etc/fedora-release
You should see something like Fedora release 44 (Adams) or similar codename output. If it’s not 44, double-check which release you’re actually on before applying any package names below, since Fedora occasionally renames or splits packages between versions.
Update the system before touching anything else. This isn’t optional housekeeping, it’s the single most common fix for weird dependency conflicts during a fresh LEMP install:
sudo dnf upgrade --refresh -y
sudo reboot
The reboot matters if a kernel update was part of that upgrade. Skipping it and moving straight to service installs is how people end up debugging phantom kernel-module mismatches later.
Step 1: Installing Nginx on Fedora 44
Nginx lives in Fedora’s default repositories, so there’s no need to add third-party repos like you would on some older CentOS setups.
sudo dnf install nginx -y
Once the install finishes, enable and start the service in one command so it also survives reboots:
sudo systemctl enable --now nginx
Check the status to confirm it’s actually running rather than silently failing:
systemctl status nginx
You’re looking for active (running) in green. If it says failed, jump ahead to the troubleshooting section, but usually a port conflict with another web server (Apache lingering from a previous setup) is the cause.
Test it locally before worrying about firewall rules:
curl -I http://localhost
A 200 OK response with an nginx server header means the base install is solid. If you’re on a remote box without a browser, that curl output is your quickest sanity check.
Firewalld Configuration for Nginx
Fedora ships with firewalld active by default, and it will silently block external requests to your shiny new web server unless you open the right services. This trips up a lot of people coming from Ubuntu, where ufw behaves differently.
sudo firewall-cmd --state
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
Verify the rules actually stuck:
sudo firewall-cmd --query-service=http
sudo firewall-cmd --query-service=https
Both should return yes. This two-step verification habit, apply then confirm, has saved me more debugging time than almost any other single practice in server administration.
Step 2: Installing MariaDB Server
Fedora dropped MySQL from its default repos years ago in favor of MariaDB, which is a fully compatible fork with better community-driven development. Unless you have a specific reason to run Oracle’s MySQL (some legacy applications insist on it), MariaDB is the sane default.
sudo dnf install mariadb-server mariadb -y
Enable and start it:
sudo systemctl enable --now mariadb
Check it’s listening properly:
sudo systemctl status mariadb
sudo mysqladmin -u root status
Now run the security script. Do not skip this step, ever. A default MariaDB install has no root password set and allows anonymous connections, which is basically leaving your front door open with a note that says “database inside.”
sudo mysql_secure_installation
Walk through the prompts: set a strong root password, remove anonymous users, disallow remote root login (unless you have a specific, isolated reason not to), remove the test database, and reload privilege tables. On a production box, remote root login should always be disabled. If you need remote database access, create a dedicated user scoped to a specific database and IP range instead.
Log in to confirm everything works:
sudo mysql -u root -p
From inside the MariaDB shell, it’s good practice to create the actual application database and a dedicated user rather than letting your application connect as root:
CREATE DATABASE app_production CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'a_genuinely_strong_password_here';
GRANT ALL PRIVILEGES ON app_production.* TO 'app_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;
Using utf8mb4 instead of plain utf8 matters more than people think. Plain utf8 in MySQL/MariaDB is a legacy 3-byte encoding that chokes on emoji and certain multilingual characters. utf8mb4 is the real, full UTF-8 implementation. Building on the older charset now just means a painful migration later.
Step 3: Installing PHP and PHP-FPM
This is where most tutorials get sloppy, either installing too few extensions and leaving you debugging missing function errors later, or installing everything under the sun and bloating the server. Here’s a reasonable middle ground for a general-purpose PHP application (WordPress, Laravel, or similar):
sudo dnf install php php-fpm php-cli php-mysqlnd php-opcache php-gd php-xml php-mbstring php-json php-pdo php-zip php-intl php-common curl -y
Each extension earns its place here. php-mysqlnd is the native driver for talking to MariaDB, php-opcache caches compiled PHP bytecode so scripts don’t get recompiled on every request, php-gd handles image manipulation, and php-mbstring handles multi-byte string operations that matter for anything non-English. Skip php-opcache and you’ll wonder later why response times are noticeably worse than a comparable stack elsewhere.
Check the version installed:
php -v
Fedora 44 typically ships with a current PHP release branch, so expect something in the PHP 8.4 or newer range depending on exact repo sync timing.
Configuring PHP-FPM to Run Under Nginx
By default, php-fpm’s pool configuration assumes an Apache-style setup with apache as the user and group. Since Nginx runs under its own user, this needs to change or PHP-FPM’s socket permissions won’t line up with what Nginx expects.
Open the pool config:
sudo nano /etc/php-fpm.d/www.conf
Find and change these lines:
user = nginx
group = nginx
Also worth checking, and adjusting if needed, the listen directive. The default is usually a Unix socket, which performs better than TCP for local connections between Nginx and PHP-FPM:
listen = /run/php-fpm/www.sock
listen.owner = nginx
listen.group = nginx
listen.mode = 0660
Save and exit, then enable and start PHP-FPM:
sudo systemctl enable --now php-fpm
sudo systemctl status php-fpm
Step 4: Wiring Nginx to PHP-FPM
This is the part where the “engine-x” and “PHP” pieces of LEMP actually talk to each other. Without correct configuration here, Nginx will either try to serve raw PHP source code as plain text or throw a 502 Bad Gateway.
Create a server block for your site rather than editing the default nginx.conf directly, since that keeps configurations organized once you’re managing more than one domain on the box:
sudo mkdir -p /var/www/example.com/html
sudo nano /etc/nginx/conf.d/example.com.conf
A reasonable starting configuration:
server {
listen 80;
server_name example.com www.example.com;
root /var/www/example.com/html;
index index.php index.html;
location / {
try_files $uri $uri/ =404;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php-fpm/www.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
location ~ /\.ht {
deny all;
}
}
That last block denying access to hidden .ht files is a leftover Apache convention, but it’s cheap insurance against accidentally exposing config files if a migration ever leaves them lying around.
Test the config syntax before reloading, since a typo here can take down every site on the server if you reload blindly:
sudo nginx -t
If it reports syntax is ok and test is successful, reload Nginx:
sudo systemctl reload nginx
Drop a quick test file to confirm PHP is actually executing rather than downloading as raw text:
echo "<?php phpinfo(); ?>" | sudo tee /var/www/example.com/html/info.php
Visit http://your-server-ip/info.php in a browser. A full PHP configuration page means the stack is wired correctly end to end. Delete that file immediately after testing, phpinfo() output is a gift-wrapped reconnaissance page for anyone probing your server for version-specific exploits.
sudo rm /var/www/example.com/html/info.php
SELinux: The Step Everyone Skips (and Regrets)
SELinux is enabled by default on Fedora, and it is not the enemy, despite what half the internet’s “just disable SELinux” advice suggests. Disabling it entirely on a production box is a security downgrade, not a fix. The right move is setting the correct contexts and booleans.
If your web root lives outside the default /usr/share/nginx/html path, like the /var/www/example.com/html used above, apply the correct SELinux context:
sudo semanage fcontext -a -t httpd_sys_content_t "/var/www/example.com/html(/.*)?"
sudo restorecon -Rv /var/www/example.com/html
If your application writes to files (upload directories, cache folders, log files), that path needs a writable context too:
sudo semanage fcontext -a -t httpd_sys_rw_content_t "/var/www/example.com/html/uploads(/.*)?"
sudo restorecon -Rv /var/www/example.com/html/uploads
For applications that make outbound network calls (payment gateways, external APIs, or a remote database connection instead of localhost), check and enable the relevant booleans:
getsebool httpd_can_network_connect httpd_can_network_connect_db httpd_can_sendmail
sudo setsebool -P httpd_can_network_connect on
sudo setsebool -P httpd_can_network_connect_db on
sudo setsebool -P httpd_can_sendmail on
The -P flag makes the change persistent across reboots, which is easy to forget and leads to “it worked yesterday” confusion after a maintenance restart.
If you genuinely can’t figure out why something’s being blocked, don’t guess, check the audit log:
sudo ausearch -m avc -ts recent
Or run the denial through audit2allow to see exactly what policy change it’s suggesting, then decide deliberately whether to apply it rather than blindly running setenforce 0.
Enabling HTTPS with Let’s Encrypt
No production site should be running plain HTTP in 2026. Certbot handles this cleanly on Fedora:
sudo dnf install certbot python3-certbot-nginx -y
sudo certbot --nginx -d example.com -d www.example.com
Certbot will automatically modify your Nginx server block to redirect HTTP to HTTPS and set up the SSL certificate paths. Confirm auto-renewal is scheduled, since expired certificates are one of the most avoidable outages in the industry:
sudo systemctl list-timers | grep certbot
Performance Tuning That Actually Matters
PHP-FPM Process Management
The default pm = dynamic setting in /etc/php-fpm.d/www.conf works fine for moderate traffic, but under a real traffic spike, the default pm.max_children value is often too conservative or, worse, too aggressive for available RAM. Calculate it based on average memory per PHP process:
ps aux | grep php-fpm | awk '{sum+=$6} END {print sum/NR/1024 " MB average"}'
Then set pm.max_children to roughly (available RAM for PHP) divided by (average process size), leaving headroom for MariaDB and Nginx. A server with 4GB RAM dedicating 2GB to PHP with 40MB average processes could reasonably run pm.max_children = 50, but always leave a safety margin rather than maxing out the math.
OPcache Configuration
Edit /etc/php.d/10-opcache.ini (or wherever your Fedora build places it) and confirm these are set for production, not development:
opcache.enable=1
opcache.memory_consumption=192
opcache.max_accelerated_files=10000
opcache.validate_timestamps=0
opcache.revalidate_freq=0
Setting validate_timestamps=0 means OPcache stops checking whether source files changed on disk, which is a meaningful performance win, but it also means you must manually reload PHP-FPM after every deployment. That tradeoff is worth it for a stable production app, less so for active development.
MariaDB Buffer Pool
If you’re running InnoDB tables (the modern default), the innodb_buffer_pool_size setting in /etc/my.cnf.d/mariadb-server.cnf should generally sit around 60-70% of available system RAM on a dedicated database server, or less if MariaDB shares the box with PHP and Nginx:
[mariadb]
innodb_buffer_pool_size = 1G
Undersizing this is one of the most common causes of a database that “feels slow” under load despite modest query volume, because every read that could’ve been served from memory is hitting disk instead.
Nginx Worker Tuning
Check CPU core count and match worker processes to it:
nproc
In /etc/nginx/nginx.conf:
worker_processes auto;
worker_connections 1024;
auto lets Nginx match the detected CPU cores automatically, which is almost always the right call unless you’re deliberately reserving cores for other workloads on the same box.
Troubleshooting Common Issues
502 Bad Gateway. This almost always means Nginx can’t reach PHP-FPM. Check that php-fpm is actually running (systemctl status php-fpm), confirm the socket path in your Nginx config matches the one in www.conf, and verify file permissions on the socket file itself with ls -l /run/php-fpm/.
403 Forbidden on every page. Usually a SELinux context mismatch or file permission issue, not a config typo. Run sudo restorecon -Rv /var/www/example.com/html first before touching the Nginx config, since that fixes the majority of these cases.
MariaDB won’t start after a config change. Check syntax errors in your .cnf file with mysqld --help --verbose or simply review the systemd journal:
sudo journalctl -xeu mariadb.service
PHP changes not reflecting on the site. If OPcache’s validate_timestamps is disabled, this is expected behavior, restart php-fpm after every deploy:
sudo systemctl restart php-fpm
Site works on HTTP but HTTPS redirect loops. Usually caused by Certbot’s redirect rule combined with a reverse proxy or CDN in front of the server also forcing HTTPS. Check for duplicate redirect logic between Nginx and any upstream layer.
Nginx fails to start with “address already in use.” Something else, often a leftover Apache instance, is bound to port 80. Check with:
sudo ss -tulpn | grep :80
Security Checklist Before Going Live
- Disable root SSH login and use key-based authentication instead of passwords.
- Keep firewalld scoped to only the services actually needed (http, https, ssh), nothing more.
- Run
sudo dnf update -yon a regular schedule, ideally automated withdnf-automaticfor security patches. - Never store database credentials in plaintext config files with world-readable permissions; use
chmod 640at minimum. - Set up fail2ban for SSH and, if relevant, for repeated failed login attempts against any admin panels.
- Rotate and monitor logs,
/var/log/nginx/and MariaDB’s error log both tell you things before a user complains.
A LEMP stack on Fedora 44 is genuinely fast once it’s tuned properly, but speed without hardening just means a fast way to get compromised. Treat the security checklist as part of the install, not an afterthought bolted on after launch.