Install LEMP Stack on Rocky Linux 9
Install Nginx, MariaDB, PHP 8, PHP-FPM, and phpMyAdmin on Rocky Linux 9 with FirewallD, Nginx virtual hosts, and a dedicated PHP-FPM pool.
This guide walks through a full LEMP stack setup on Rocky Linux 9 using Nginx, MariaDB, PHP 8, PHP-FPM, and phpMyAdmin.
By the end, you will have:
- a sudo-enabled admin user
- updated system packages and extra repositories enabled
- SSH on a custom port
- FirewallD configured
systemd-resolvedconfigured- 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 usernameSet a password:
passwd usernameAdd the user to the wheel group:
usermod -aG wheel usernameVerify the group membership:
id usernameSwitch to the new user and verify sudo access:
su - username
sudo whoamiIf the command returns root, sudo is configured correctly.
Enable Repositories and Update Packages
Install EPEL and the DNF helper packages:
sudo dnf install epel-release dnf-utils dnf-plugins-coreEnable the CRB repository:
sudo dnf config-manager --set-enabled crbEnable RPM Fusion:
sudo dnf install --nogpgcheck https://mirrors.rpmfusion.org/free/el/rpmfusion-free-release-$(rpm -E %rhel).noarch.rpm
sudo dnf install --nogpgcheck https://mirrors.rpmfusion.org/nonfree/el/rpmfusion-nonfree-release-$(rpm -E %rhel).noarch.rpmUpdate the system:
sudo dnf updateChange the SSH Port
Edit the SSH config:
sudo vim /etc/ssh/sshd_configSet a custom port and disable root login:
Port 52225
PermitRootLogin noRestart SSH:
sudo systemctl restart sshd
sudo systemctl status sshdInstall and Configure FirewallD
Install FirewallD:
sudo dnf install firewalldEnable and start it:
sudo systemctl enable firewalld
sudo systemctl start firewalldAllow 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/tcpReload the rules:
sudo firewall-cmd --reloadCheck the open ports:
sudo firewall-cmd --list-portsConnect over the new SSH port:
ssh -p 52225 user@your_server_ipStarting 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-resolvedEdit the resolver config:
sudo vim /etc/systemd/resolved.confUse 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=opportunisticPoint resolv.conf at the stub resolver and restart:
sudo ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
sudo systemctl restart systemd-resolvedCheck resolver status:
resolvectl statusGenerate 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 3650Install PHP 8 and PHP-FPM
List available PHP module streams:
sudo dnf module list phpEnable PHP 8.2 and install the common extensions:
sudo dnf module enable php:8.2
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=FalseEdit the PHP-FPM pool config:
sudo vim /etc/php-fpm.d/www.confUse:
[www]
user = nginx
group = nginx
listen = /run/php-fpm/www.sock
listen.acl_users = apache,nginxDisable commonly abused functions:
sudo vim /etc/php.iniSet:
disable_functions = exec,passthru,shell_exec,system,proc_open,popenEnable and restart PHP-FPM:
sudo systemctl enable php-fpm
sudo systemctl restart php-fpmInstall Nginx
You can use either the distro package or the official mainline repository.
Install from the Rocky Linux repository:
sudo dnf install nginx
sudo systemctl enable nginx
sudo systemctl start nginx
sudo systemctl status nginxIf you want mainline Nginx instead, use:
sudo vim /etc/yum.repos.d/nginx.repoWith:
[nginx-mainline]
name=nginx mainline repo
baseurl=http://nginx.org/packages/mainline/rhel/$releasever/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=trueThen install and verify:
sudo dnf install nginx
sudo systemctl start nginx
sudo systemctl enable nginx
nginx -vPrepare the site config directories:
sudo mkdir -p /etc/nginx/sites-enabled
sudo mkdir -p /etc/nginx/sites-availableBack up the current config:
sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bakEdit the main config:
sudo vim /etc/nginx/nginx.confReplace 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/defaultUse:
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/defaultTest and restart Nginx:
sudo nginx -t
sudo systemctl restart nginxCreate a temporary PHP test file:
echo "<?php
phpinfo();
?>" | sudo tee /usr/share/nginx/html/test.phpEnsure the session directory is owned by nginx:
sudo chown -R nginx:nginx /var/lib/php/sessionOpen the test page:
http://server_ip/test.phpRemove the test file when finished:
sudo rm /usr/share/nginx/html/test.phpInstall MariaDB
Install MariaDB:
sudo dnf install mariadb-server mariadbEnable and start the service:
sudo systemctl start mariadb
sudo systemctl enable mariadb
sudo systemctl status mariadbSecure the installation:
sudo mysql_secure_installationRecommended answers:
- press
Enterto leave the root password empty if you want local-only development first - answer
Yto switch to Unix socket authentication - answer
Yto remove anonymous users - answer
Yto disallow remote root login - answer
Yto remove the test database - answer
Yto reload privilege tables
The main server config is here:
sudo vim /etc/my.cnf.d/mariadb-server.cnfInstall phpMyAdmin
Check the latest upstream release before downloading:
https://www.phpmyadmin.net/downloadsDownload 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.gzExtract it:
sudo tar -xvzf phpMyAdmin-5.2.2-english.tar.gzMove it into place:
sudo mkdir -p /usr/share/phpmyadmin
sudo mv /tmp/phpMyAdmin-5.2.2-english/* /usr/share/phpmyadmin
ls -l /usr/share/phpmyadminCreate the temp directory:
sudo mkdir -p /usr/share/phpmyadmin/tmp
sudo chown -R nginx:nginx /usr/share/phpmyadmin/tmpCreate the phpMyAdmin database and control user:
sudo mariadbRun:
CREATE DATABASE phpmyadmin;
CREATE USER 'pma'@'localhost' IDENTIFIED BY 'strong_password_here';
GRANT SELECT, INSERT, UPDATE, DELETE ON phpmyadmin.* TO 'pma'@'localhost';
FLUSH PRIVILEGES;
exitGenerate a random blowfish secret:
openssl rand -base64 24Copy 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.phpSet 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.sqlCreate a password hash:
openssl passwdCreate the Nginx basic auth file:
sudo vim /etc/nginx/pma_passUse:
pma:password_hashCreate the phpMyAdmin site config:
sudo vim /etc/nginx/sites-available/phpmyadminUse:
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/phpmyadminTest and restart Nginx:
sudo nginx -t
sudo systemctl restart nginxCreate a database and user for your application:
sudo mariadbRun:
CREATE USER 'example_user'@'localhost' IDENTIFIED BY 'password';
CREATE DATABASE example_db;
GRANT ALL PRIVILEGES ON example_db.* TO 'example_user'@'localhost';
FLUSH PRIVILEGES;
exitYou 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 dolphinCreate 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.comCopy 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.confUse:
[dolphin]
user = dolphin
group = dolphin
listen = /run/php-fpm/dolphin.sock
listen.acl_users = apache,nginxRestart PHP-FPM:
sudo systemctl restart php-fpmCreate the site virtual host:
sudo vim /etc/nginx/sites-available/example.comUse:
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.comTest and restart Nginx:
sudo nginx -t
sudo service nginx restartInstall WordPress
Switch to the site user:
sudo su - dolphinDownload 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 wordpressOpen https://example.com in your browser to continue the WordPress installer.