Nauris
LEMP Setup

Install LEMP Stack on Fedora 41

Install Nginx, MariaDB, PHP 8, PHP-FPM, and phpMyAdmin on Fedora 41 with FirewallD, Nginx virtual hosts, and a dedicated PHP-FPM pool.

This guide walks through a full LEMP stack setup on Fedora 41 using Nginx, MariaDB, PHP 8, PHP-FPM, and phpMyAdmin.

By the end, you will have:

  • a sudo-enabled admin user
  • updated system packages and RPM Fusion enabled
  • SSH on a custom port
  • FirewallD configured
  • systemd-resolved configured
  • Nginx, MariaDB, PHP, and phpMyAdmin installed
  • a dedicated PHP-FPM pool and Nginx virtual host for a site

Create a New Sudo User

Create a new user:

useradd -m -s /bin/bash username

Set a password:

passwd username

Add the user to the wheel group:

usermod -aG wheel username

Verify the group membership:

id username

Switch to the new user and verify sudo access:

su - username
sudo whoami

If the command returns root, sudo is configured correctly.

Enable Repositories and Update Packages

Install the DNF helper packages:

sudo dnf install dnf-utils dnf-plugins-core

Enable RPM Fusion:

sudo dnf install --nogpgcheck https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm
sudo dnf install --nogpgcheck https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm

Update the system:

sudo dnf update

Change the SSH Port

Edit the SSH config:

sudo vim /etc/ssh/sshd_config

Set a custom port and disable root login:

Port 52225
PermitRootLogin no

Restart SSH:

sudo systemctl restart sshd
sudo systemctl status sshd

Install and Configure FirewallD

Install FirewallD:

sudo dnf install firewalld

Enable and start it:

sudo systemctl enable firewalld
sudo systemctl start firewalld

Allow HTTP, HTTPS, and your custom SSH port:

sudo firewall-cmd --permanent --add-port=80/tcp
sudo firewall-cmd --permanent --add-port=443/tcp
sudo firewall-cmd --permanent --add-port=52225/tcp

Reload the rules:

sudo firewall-cmd --reload

Check the open ports:

sudo firewall-cmd --list-ports

Connect over the new SSH port:

ssh -p 52225 user@your_server_ip

Starting the firewall can drop the current SSH session. Reconnect using the new port if needed.

Configure DNS Resolver

Install and enable systemd-resolved:

sudo dnf install systemd-resolved
sudo systemctl enable systemd-resolved
sudo systemctl start systemd-resolved
sudo systemctl status systemd-resolved

Edit the resolver config:

sudo vim /usr/lib/systemd/resolved.conf

Use Cloudflare and Quad9:

[Resolve]
DNS=1.1.1.1 1.0.0.1 2606:4700:4700::1111 2606:4700:4700::1001
FallbackDNS=9.9.9.9 149.112.112.112 2620:fe::fe 2620:fe::9
DNSOverTLS=opportunistic

Point resolv.conf at the stub resolver and restart:

sudo ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
sudo systemctl restart systemd-resolved

Check resolver status:

resolvectl status

Generate an SSL Certificate

Create a self-signed certificate for local testing or internal use:

sudo openssl req -x509 -newkey rsa:2048 -nodes -keyout /etc/ssl/certs/default.key -out /etc/ssl/certs/default.crt -days 3650

Install PHP 8 and PHP-FPM

Install PHP and the common extensions:

sudo dnf install php php-cli php-fpm php-mysqlnd php-zip php-devel php-mbstring php-xml php-curl php-gd php-opcache php-common php-bcmath php-gmp --setopt=install_weak_deps=False

Edit the PHP-FPM pool config:

sudo vim /etc/php-fpm.d/www.conf

Use:

[www]
user = nginx
group = nginx

listen = /run/php-fpm/www.sock

listen.acl_users = apache,nginx

Disable commonly abused functions:

sudo vim /etc/php.ini

Set:

disable_functions = exec,passthru,shell_exec,system,proc_open,popen

Enable and restart PHP-FPM:

sudo systemctl enable php-fpm
sudo systemctl restart php-fpm

Install Nginx

Install Nginx:

sudo dnf install nginx
sudo systemctl enable nginx
sudo systemctl start nginx
sudo systemctl status nginx

Verify the version:

nginx -v

Prepare the site config directories:

sudo mkdir -p /etc/nginx/sites-enabled
sudo mkdir -p /etc/nginx/sites-available

Back up the current config:

sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak

Edit the main config:

sudo vim /etc/nginx/nginx.conf

Replace it with:

user nginx;
worker_processes auto;
pid /var/run/nginx.pid;
error_log /var/log/nginx/error.log;
include /etc/nginx/modules/*.conf;

events {
  worker_connections 1024;
}

http {
  sendfile on;
  tcp_nopush on;
  keepalive_timeout 65;
  types_hash_max_size 4096;
  types_hash_bucket_size 128;

  access_log off;

  include /etc/nginx/mime.types;
  default_type application/octet-stream;

  gzip on;
  gzip_vary on;
  gzip_comp_level 5;
  gzip_min_length 512;
  gzip_buffers 8 64k;
  gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml+rss application/x-font-ttf image/svg+xml font/opentype;
  gzip_proxied any;
  gzip_disable "msie6";

  ssl_session_cache shared:SSL:10m;
  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_prefer_server_ciphers on;
  ssl_ciphers HIGH:!aNULL:!MD5;

  proxy_cache_path /var/cache/nginx levels=2 keys_zone=cache:10m inactive=60m max_size=1024m;
  proxy_cache_key "$host$request_uri";
  proxy_temp_path /var/cache/nginx/temp;
  proxy_ignore_headers Expires Cache-Control;
  proxy_cache_use_stale error timeout invalid_header http_502;
  proxy_cache_valid any 1d;

  open_file_cache max=10000 inactive=30s;
  open_file_cache_valid 60s;
  open_file_cache_min_uses 2;
  open_file_cache_errors off;

  map $http_cookie $no_cache {
    default 0;
    ~SESS 1;
    ~wordpress_logged_in 1;
  }

  include /etc/nginx/sites-enabled/*;
}

Create the default virtual host:

sudo vim /etc/nginx/sites-available/default

Use:

server {
  listen 80 default_server;
  listen [::]:80 default_server;
  listen 443 ssl default_server;
  listen [::]:443 ssl default_server;
  server_name _;

  ssl_certificate /etc/ssl/certs/default.crt;
  ssl_certificate_key /etc/ssl/certs/default.key;

  root /usr/share/nginx/html;
  index index.html index.htm index.php;

  location / {
    try_files $uri $uri/ /index.php?$args;

    location ~ \.php$ {
      root /usr/share/nginx/html;
      fastcgi_pass unix:/run/php-fpm/www.sock;
      fastcgi_index index.php;
      fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
      include fastcgi_params;
    }
  }

  location ~ /\.ht {
    deny all;
  }

  error_page 500 502 503 504 /50x.html;
  location = /50x.html {
    root /usr/share/nginx/html;
  }
}

Enable the default site:

sudo ln -s /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default

Test and restart Nginx:

sudo nginx -t
sudo systemctl restart nginx

Create a temporary PHP test file:

echo "<?php
phpinfo();
?>" | sudo tee /usr/share/nginx/html/test.php

Ensure the session directory is owned by nginx:

sudo chown -R nginx:nginx /var/lib/php/session

Open the test page:

http://server_ip/test.php

Remove the test file when finished:

sudo rm /usr/share/nginx/html/test.php

Install MariaDB

Install MariaDB:

sudo dnf install mariadb-server mariadb

Enable and start the service:

sudo systemctl start mariadb
sudo systemctl enable mariadb
sudo systemctl status mariadb

Secure the installation:

sudo mysql_secure_installation

Recommended answers:

  • press Enter to leave the root password empty if you want local-only development first
  • answer Y to switch to Unix socket authentication
  • answer Y to remove anonymous users
  • answer Y to disallow remote root login
  • answer Y to remove the test database
  • answer Y to reload privilege tables

The main server config is here:

sudo vim /etc/my.cnf.d/mariadb-server.cnf

Install phpMyAdmin

Check the latest upstream release before downloading:

https://www.phpmyadmin.net/downloads

Download the release archive used in this guide:

cd /tmp
curl -O https://files.phpmyadmin.net/phpMyAdmin/5.2.2/phpMyAdmin-5.2.2-english.tar.gz

Extract it:

sudo tar -xvzf phpMyAdmin-5.2.2-english.tar.gz

Move it into place:

sudo mkdir -p /usr/share/phpmyadmin
sudo mv /tmp/phpMyAdmin-5.2.2-english/* /usr/share/phpmyadmin
ls -l /usr/share/phpmyadmin

Create the temp directory:

sudo mkdir -p /usr/share/phpmyadmin/tmp
sudo chown -R nginx:nginx /usr/share/phpmyadmin/tmp

Create the phpMyAdmin database and control user:

sudo mariadb

Run:

CREATE DATABASE phpmyadmin;
CREATE USER 'pma'@'localhost' IDENTIFIED BY 'strong_password_here';
GRANT SELECT, INSERT, UPDATE, DELETE ON phpmyadmin.* TO 'pma'@'localhost';
FLUSH PRIVILEGES;
exit

Generate a random blowfish secret:

openssl rand -base64 24

Copy the sample config and edit it:

sudo cp /usr/share/phpmyadmin/config.sample.inc.php /usr/share/phpmyadmin/config.inc.php
sudo vim /usr/share/phpmyadmin/config.inc.php

Set the generated secret:

$cfg['blowfish_secret'] = 'your_code';

Add the temp directory and hide system databases:

$cfg['TempDir'] = '/usr/share/phpmyadmin/tmp';
$cfg['Servers'][$i]['hide_db'] = '^information_schema|mysql|phpmyadmin|performance_schema|sys$';

Set the control user credentials:

$cfg['Servers'][$i]['controluser'] = 'pma';
$cfg['Servers'][$i]['controlpass'] = 'strong_password_here';

Enable storage tables:

$cfg['Servers'][$i]['pmadb'] = 'phpmyadmin';
$cfg['Servers'][$i]['bookmarktable'] = 'pma__bookmark';
$cfg['Servers'][$i]['relation'] = 'pma__relation';
$cfg['Servers'][$i]['table_info'] = 'pma__table_info';
$cfg['Servers'][$i]['table_coords'] = 'pma__table_coords';
$cfg['Servers'][$i]['pdf_pages'] = 'pma__pdf_pages';
$cfg['Servers'][$i]['column_info'] = 'pma__column_info';
$cfg['Servers'][$i]['history'] = 'pma__history';
$cfg['Servers'][$i]['table_uiprefs'] = 'pma__table_uiprefs';
$cfg['Servers'][$i]['tracking'] = 'pma__tracking';
$cfg['Servers'][$i]['userconfig'] = 'pma__userconfig';
$cfg['Servers'][$i]['recent'] = 'pma__recent';
$cfg['Servers'][$i]['favorite'] = 'pma__favorite';
$cfg['Servers'][$i]['users'] = 'pma__users';
$cfg['Servers'][$i]['usergroups'] = 'pma__usergroups';
$cfg['Servers'][$i]['navigationhiding'] = 'pma__navigationhiding';
$cfg['Servers'][$i]['savedsearches'] = 'pma__savedsearches';
$cfg['Servers'][$i]['central_columns'] = 'pma__central_columns';
$cfg['Servers'][$i]['designer_settings'] = 'pma__designer_settings';
$cfg['Servers'][$i]['export_templates'] = 'pma__export_templates';

Create the phpMyAdmin tables:

sudo mysql -u root -p phpmyadmin < /usr/share/phpmyadmin/sql/create_tables.sql

Create a password hash:

openssl passwd

Create the Nginx basic auth file:

sudo vim /etc/nginx/pma_pass

Use:

pma:password_hash

Create the phpMyAdmin site config:

sudo vim /etc/nginx/sites-available/phpmyadmin

Use:

server {
  listen 80;
  listen [::]:80;
  listen 443 ssl;
  listen [::]:443 ssl;
  server_name pma.example.com;

  ssl_certificate /etc/ssl/certs/default.crt;
  ssl_certificate_key /etc/ssl/certs/default.key;

  access_log off;
  error_log /var/log/nginx/pma.example.com.error.log error;

  root /usr/share/phpmyadmin;
  index index.html index.htm index.php;

  location / {
    try_files $uri $uri/ /index.php?$args;
    auth_basic "Admin Login";
    auth_basic_user_file /etc/nginx/pma_pass;

    location ~ \.php$ {
      root /usr/share/phpmyadmin;
      fastcgi_pass unix:/run/php-fpm/www.sock;
      fastcgi_index index.php;
      fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
      include fastcgi_params;
    }
  }

  location ~ /\.ht {
    deny all;
  }

  error_page 500 502 503 504 /50x.html;
  location = /50x.html {
    root /usr/share/nginx/html;
  }
}

Replace pma.example.com with your actual subdomain.

Enable the site:

sudo ln -s /etc/nginx/sites-available/phpmyadmin /etc/nginx/sites-enabled/phpmyadmin

Test and restart Nginx:

sudo nginx -t
sudo systemctl restart nginx

Create a database and user for your application:

sudo mariadb

Run:

CREATE USER 'example_user'@'localhost' IDENTIFIED BY 'password';
CREATE DATABASE example_db;
GRANT ALL PRIVILEGES ON example_db.* TO 'example_user'@'localhost';
FLUSH PRIVILEGES;
exit

You should now be able to sign in to phpMyAdmin at https://pma.example.com.

Create a Dedicated Site User

Create a system user for the hosted application:

sudo adduser --system --create-home dolphin

Create the document root and fix ownership:

sudo mkdir -p /var/www/dolphin/example.com
sudo chmod 711 /var/www/dolphin
sudo chown root:root /var/www
sudo chown -R dolphin:dolphin /var/www/dolphin
sudo chmod -R 755 /var/www/dolphin/example.com

Copy the PHP-FPM pool config:

sudo cp /etc/php-fpm.d/www.conf /etc/php-fpm.d/dolphin.conf
sudo vim /etc/php-fpm.d/dolphin.conf

Use:

[dolphin]
user = dolphin
group = dolphin

listen = /run/php-fpm/dolphin.sock

listen.acl_users = apache,nginx

Restart PHP-FPM:

sudo systemctl restart php-fpm

Create the site virtual host:

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

Use:

server {
  listen 80;
  listen [::]:80;
  listen 443 ssl;
  listen [::]:443 ssl;
  server_name example.com www.example.com;

  ssl_certificate /etc/ssl/certs/default.crt;
  ssl_certificate_key /etc/ssl/certs/default.key;

  access_log off;
  error_log /var/log/nginx/example.com.error.log error;

  root /var/www/dolphin/example.com;
  index index.html index.htm index.php;

  location / {
    try_files $uri $uri/ /index.php?$args;

    location ~ \.php$ {
      root /var/www/dolphin/example.com;
      fastcgi_pass unix:/run/php-fpm/dolphin.sock;
      fastcgi_index index.php;
      fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
      include fastcgi_params;
    }
  }

  location ~ /\.ht {
    deny all;
  }

  error_page 500 502 503 504 /50x.html;
  location = /50x.html {
    root /usr/share/nginx/html;
  }
}

Enable the site:

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

Test and restart Nginx:

sudo nginx -t
sudo service nginx restart

Install WordPress

Switch to the site user:

sudo su - dolphin

Download and extract WordPress:

cd /var/www/dolphin/example.com
curl -O https://wordpress.org/latest.tar.gz
tar -xvzf latest.tar.gz
mv wordpress/* .
rm latest.tar.gz && rm -fr wordpress

Open https://example.com in your browser to continue the WordPress installer.

On this page