$linuxjunkies
>

How to Set Up a LEMP Stack

Install and configure Nginx, MariaDB, and PHP-FPM on a fresh Linux server. Covers Debian, Ubuntu, Fedora, RHEL, Rocky, and Arch with systemd service management.

IntermediateUbuntuDebianFedoraArch9 min readUpdated June 7, 2026

Before you start

  • A fresh server with a non-root sudo user
  • SSH access and a terminal
  • A domain name pointed at the server IP (optional but recommended for TLS later)
  • Basic familiarity with a terminal text editor such as nano or vim

A LEMP stack — Linux, Nginx, MariaDB (or MySQL), and PHP — is a proven foundation for hosting web applications, WordPress sites, and custom PHP projects. This guide walks through installing and wiring together each component on a fresh server, then verifies the stack works end-to-end before you deploy anything real.

Install Nginx

Debian / Ubuntu

sudo apt update
sudo apt install -y nginx

Fedora / RHEL / Rocky

sudo dnf install -y nginx

Arch

sudo pacman -S --noconfirm nginx

Enable and start Nginx with systemd:

sudo systemctl enable --now nginx

If a firewall is active, open HTTP and HTTPS traffic:

# ufw (Debian/Ubuntu)
sudo ufw allow 'Nginx Full'

# firewalld (Fedora/RHEL/Rocky)
sudo firewall-cmd --permanent --add-service=http --add-service=https
sudo firewall-cmd --reload

Confirm Nginx is serving by visiting http://YOUR_SERVER_IP in a browser. You should see the default welcome page.

Install MariaDB

MariaDB is the recommended drop-in replacement for MySQL on most distros. If your application specifically requires Oracle MySQL, substitute mysql-server in the commands below — the configuration steps are identical.

Debian / Ubuntu

sudo apt install -y mariadb-server

Fedora / RHEL / Rocky

sudo dnf install -y mariadb-server

Arch

sudo pacman -S --noconfirm mariadb
sudo mariadb-install-db --user=mysql --basedir=/usr --datadir=/var/lib/mysql

Enable and start the service, then run the security script:

sudo systemctl enable --now mariadb
sudo mariadb-secure-installation

The interactive script asks you to set a root password, remove anonymous users, disallow remote root login, and drop the test database. Accept all recommendations unless you have a specific reason not to.

Create an application database and user

Never let your application connect as the MariaDB root user. Create a dedicated account:

sudo mariadb -u root -p
CREATE DATABASE myapp CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'myapp_user'@'localhost' IDENTIFIED BY 'StrongPassw0rd!';
GRANT ALL PRIVILEGES ON myapp.* TO 'myapp_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;

Install PHP-FPM

Nginx does not execute PHP natively; it passes requests to PHP-FPM (FastCGI Process Manager) over a Unix socket. Install PHP-FPM and the extensions most applications need:

Debian / Ubuntu

sudo apt install -y php-fpm php-mysql php-mbstring php-xml php-curl php-zip

On Ubuntu/Debian the service name includes the version, e.g. php8.3-fpm. Check with:

systemctl list-units 'php*fpm*'

Fedora / RHEL / Rocky

sudo dnf install -y php-fpm php-mysqlnd php-mbstring php-xml php-curl php-zip

Arch

sudo pacman -S --noconfirm php-fpm php-gd

Enable and start PHP-FPM:

sudo systemctl enable --now php-fpm

On Debian/Ubuntu use the versioned unit name, for example:

sudo systemctl enable --now php8.3-fpm

Confirm the socket path

You need the Unix socket path to configure Nginx. The default locations are:

  • Debian/Ubuntu: /run/php/php8.x-fpm.sock
  • Fedora/RHEL/Rocky: /run/php-fpm/www.sock
  • Arch: /run/php-fpm/php-fpm.sock

Verify the socket exists and is active:

ls -l /run/php-fpm/www.sock   # adjust path for your distro

Configure Nginx to Use PHP-FPM

Create a server block for your site. Replace example.com and the socket path as needed.

sudo nano /etc/nginx/sites-available/example.com

On Fedora/RHEL/Rocky, the equivalent directory is /etc/nginx/conf.d/; create example.com.conf there instead and skip the symlink step.

server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/example.com/public;
    index index.php index.html;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;   # Debian/Ubuntu
        # On RHEL/Arch replace the line above with:
        # fastcgi_split_path_info ^(.+\.php)(/.+)$;
        # include fastcgi_params;
        # fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

        fastcgi_pass unix:/run/php/php8.3-fpm.sock;  # adjust socket path
    }

    location ~ /\.ht {
        deny all;
    }
}

On Debian/Ubuntu, enable the site and remove the default:

sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default

Test the configuration syntax before reloading:

sudo nginx -t

Expected output (will vary slightly): nginx: configuration file /etc/nginx/nginx.conf test is successful

sudo systemctl reload nginx

Create the Web Root and a Test Page

sudo mkdir -p /var/www/example.com/public
sudo chown -R www-data:www-data /var/www/example.com   # use 'nginx' on RHEL/Arch
echo '

Visit http://YOUR_SERVER_IP/info.php (or your domain). You should see the PHP information page, confirming Nginx is passing PHP requests to PHP-FPM successfully. The page should show the Server API as FPM/FastCGI.

Remove the info page once you have confirmed it works — it exposes server internals:

sudo rm /var/www/example.com/public/info.php

Verify the Full Stack

Check that all three services are running and enabled at boot:

systemctl is-active nginx mariadb php-fpm   # or php8.3-fpm on Debian/Ubuntu

Output should show active for each. Verify MariaDB accepts connections with the application user:

mariadb -u myapp_user -p -e "SELECT VERSION();" myapp

Troubleshooting

502 Bad Gateway

This almost always means Nginx cannot reach PHP-FPM. Check that the socket path in your Nginx config exactly matches the path shown by ls /run/php*/. Also confirm PHP-FPM is running: sudo systemctl status php-fpm.

Permission denied on the socket

The Nginx worker process user (www-data on Debian/Ubuntu, nginx on RHEL) must be able to read the PHP-FPM socket. Open /etc/php-fpm.d/www.conf (or the distro equivalent) and set:

listen.owner = nginx
listen.group = nginx
listen.mode = 0660

Then restart PHP-FPM: sudo systemctl restart php-fpm.

SELinux blocking connections (RHEL/Rocky/Fedora)

If you see AVC denials in sudo ausearch -m avc -ts recent, allow Nginx to connect to PHP-FPM:

sudo setsebool -P httpd_can_network_connect 1

Nginx config changes have no effect

Always run sudo nginx -t before reloading. A syntax error will prevent the reload from applying and Nginx continues with the old config — but the command exit code will be non-zero and the error will be printed to the terminal.

tested on:Ubuntu 24.04Debian 12Rocky 9Arch rolling

Frequently asked questions

Can I use MySQL instead of MariaDB?
Yes. Substitute mysql-server (Debian/Ubuntu) or mysql-community-server (RHEL/Fedora via the MySQL repo) in the install step. The configuration, SQL commands, and systemd unit name (mysqld on RHEL, mysql on Debian) differ slightly, but the PHP-FPM and Nginx setup is identical.
How do I add HTTPS / TLS to Nginx?
Install Certbot and the nginx plugin (certbot-nginx), then run sudo certbot --nginx -d example.com. Certbot rewrites your server block and sets up auto-renewal via a systemd timer.
Should I run multiple PHP versions on one server?
You can install multiple php-fpm packages simultaneously on Debian/Ubuntu (each gets its own versioned socket and systemd unit). Point different Nginx server blocks at different sockets. On RHEL you need the Remi repository to access non-default PHP versions.
What user should own files in the web root?
Files should be owned by your deploy user or root, not the Nginx/PHP worker user. Set group ownership to www-data (or nginx) and use 644/755 permissions so the worker can read but not write. Only upload directories need group write permission, and ideally those live outside the web root.
Why use a Unix socket instead of a TCP port for PHP-FPM?
A Unix socket avoids the TCP stack overhead for local communication, giving slightly lower latency and eliminating the need to bind a port. For single-server setups it is always preferred. Use TCP (127.0.0.1:9000) only when Nginx and PHP-FPM run on separate hosts.

Related guides