How To Install LEMP Stack on Fedora 44

Install LEMP Stack on Fedora 44

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 -y on a regular schedule, ideally automated with dnf-automatic for security patches.
  • Never store database credentials in plaintext config files with world-readable permissions; use chmod 640 at 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.

r00t is an experienced Linux enthusiast and technical writer with a passion for open-source software. With years of hands-on experience in various Linux distributions, r00t has developed a deep understanding of the Linux ecosystem and its powerful tools. He holds certifications in SCE and has contributed to several open-source projects. r00t is dedicated to sharing her knowledge and expertise through well-researched and informative articles, helping others navigate the world of Linux with confidence.

Related Posts