How to Set Up a LEMP Stack on RHEL 7
A LEMP stack — Linux, Nginx, MySQL, and PHP — is one of the most common foundations for modern dynamic web applications. On RHEL 7 the individual components are available from a combination of the base repositories, the EPEL community repository, the official MySQL community repository, and the Remi or Software Collections Library (SCL) for current PHP versions. Each component integrates with systemctl for service management and produces logs in /var/log/ in the standard RHEL format. This guide walks you through installing and configuring each layer of the stack, wiring Nginx to PHP-FPM via a Unix socket, validating the PHP integration with a phpinfo() page, and creating a test MySQL database with a dedicated application user.
Prerequisites
- RHEL 7 with root or sudo access and a valid subscription (or CentOS 7 with base/extras repos)
- EPEL repository available (
sudo yum install -y epel-release) - A hostname resolvable from the test machine (or add an entry to
/etc/hosts) - Firewalld running
- SELinux in enforcing mode
- Sufficient disk space: approximately 500 MB for packages and their dependencies
Step 1: Install Nginx from EPEL
The Nginx version in the base RHEL 7 repository can be outdated. The EPEL repository provides a more recent stable release:
sudo yum install -y epel-release
sudo yum install -y nginx
nginx -v
# nginx version: nginx/1.20.x
Enable and start Nginx:
sudo systemctl enable nginx
sudo systemctl start nginx
sudo systemctl status nginx
Open HTTP and HTTPS in the firewall:
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
Verify Nginx is serving its default page:
curl -sI http://localhost/ | grep "HTTP/"
# HTTP/1.1 200 OK
Step 2: Install MySQL 5.7 from the MySQL Community Repository
RHEL 7 ships MariaDB by default, but if your application requires MySQL 5.7 specifically, use the official MySQL Community repository:
# Add the MySQL 5.7 community repository
sudo rpm -Uvh https://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm
# If GPG key check fails, import the key
sudo rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2022
# Install MySQL server
sudo yum install -y mysql-community-server
mysql --version
# mysql Ver 14.14 Distrib 5.7.x ...
Enable and start MySQL:
sudo systemctl enable mysqld
sudo systemctl start mysqld
sudo systemctl status mysqld
MySQL 5.7 generates a temporary root password on first start. Retrieve it:
sudo grep 'temporary password' /var/log/mysqld.log
# ... A temporary password is generated for root@localhost: AbCdEfG12#
Run the secure installation wizard, providing the temporary password when prompted:
sudo mysql_secure_installation
Follow the prompts to set a strong root password, remove anonymous users, disallow remote root login, remove the test database, and reload the privilege tables.
Step 3: Install PHP 7.0 via SCL or PHP 7.4 via Remi Repository
Option A: PHP 7.0 from Red Hat Software Collections (SCL)
sudo yum install -y scl-utils
sudo yum install -y centos-release-scl 2>/dev/null ||
sudo subscription-manager repos --enable rhel-7-server-optional-rpms
--enable rhel-server-rhscl-7-rpms
sudo yum install -y rh-php70 rh-php70-php-fpm rh-php70-php-mysqlnd
rh-php70-php-mbstring rh-php70-php-xml rh-php70-php-gd
rh-php70-php-opcache rh-php70-php-json
# Make the SCL PHP available in the default path
echo 'source /opt/rh/rh-php70/enable' | sudo tee /etc/profile.d/php70.sh
source /opt/rh/rh-php70/enable
php -v
# PHP 7.0.x ...
Option B: PHP 7.4 from Remi Repository (recommended for newer applications)
sudo yum install -y epel-release
sudo yum install -y https://rpms.remirepo.net/enterprise/remi-release-7.rpm
sudo yum install -y yum-utils
sudo yum-config-manager --enable remi-php74
sudo yum install -y php php-fpm php-mysqlnd php-mbstring php-xml
php-gd php-opcache php-json php-zip
php -v
# PHP 7.4.x ...
Enable and start PHP-FPM:
sudo systemctl enable php-fpm
sudo systemctl start php-fpm
sudo systemctl status php-fpm
Step 4: Configure PHP-FPM to Use a Unix Socket
Using a Unix socket instead of a TCP port for communication between Nginx and PHP-FPM reduces overhead and is the recommended approach when both run on the same server.
Edit the PHP-FPM pool configuration:
sudo vi /etc/php-fpm.d/www.conf
Find and update the following lines:
; Change from TCP to Unix socket
listen = /var/run/php-fpm/php-fpm.sock
; Set socket ownership so Nginx can read/write it
listen.owner = nginx
listen.group = nginx
listen.mode = 0660
; Run PHP-FPM as the nginx user
user = nginx
group = nginx
; Process manager settings (adjust to your server RAM)
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 35
pm.max_requests = 500
Restart PHP-FPM to apply:
sudo systemctl restart php-fpm
# Verify the socket was created
ls -la /var/run/php-fpm/php-fpm.sock
# srw-rw---- 1 nginx nginx 0 ... /var/run/php-fpm/php-fpm.sock
Set the SELinux boolean that allows Nginx to connect to PHP-FPM over the socket:
sudo setsebool -P httpd_can_network_connect 1
Step 5: Configure Nginx to Pass PHP to PHP-FPM
Create a server block for your site. Replace example.com with your actual domain:
sudo vi /etc/nginx/conf.d/example.com.conf
server {
listen 80;
server_name example.com www.example.com;
root /var/www/example.com/html;
index index.php index.html index.htm;
access_log /var/log/nginx/example.com.access.log main;
error_log /var/log/nginx/example.com.error.log warn;
# Serve static files directly
location ~* .(jpg|jpeg|png|gif|ico|css|js|woff2|woff|svg|ttf)$ {
expires 30d;
add_header Cache-Control "public, no-transform";
try_files $uri =404;
}
# Main location block
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# Pass PHP scripts to PHP-FPM
location ~ .php$ {
try_files $uri =404;
fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_param HTTP_PROXY ""; # Mitigate HTTPoxy vulnerability
}
# Deny access to hidden files
location ~ /. {
deny all;
access_log off;
log_not_found off;
}
}
Create the document root and set correct ownership and SELinux context:
sudo mkdir -p /var/www/example.com/html
sudo chown -R nginx:nginx /var/www/example.com
sudo semanage fcontext -a -t httpd_sys_content_t "/var/www/example.com/html(/.*)?"
sudo restorecon -Rv /var/www/example.com/html
Test and reload Nginx:
sudo nginx -t
sudo systemctl reload nginx
Step 6: Test PHP Integration with phpinfo()
sudo tee /var/www/example.com/html/info.php <<'EOF'
<?php phpinfo(); ?>
EOF
Browse to http://example.com/info.php (or use curl):
curl -s http://example.com/info.php | grep -i "php version"
# <title>PHP 7.4.x - phpinfo()</title>
Look for the following in the output to confirm correct configuration:
- Server API:
FPM/FastCGI - Loaded Configuration File:
/etc/php.ini - mysqli and PDO_MySQL sections present (confirming database support)
Important: Remove this file immediately after testing to avoid exposing server details:
sudo rm /var/www/example.com/html/info.php
Step 7: Create a Test MySQL Database
Log in to MySQL as root and create a database and a dedicated application user:
mysql -u root -p
-- Create the application database
CREATE DATABASE lemp_test CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- Create a dedicated application user (never use root in application code)
CREATE USER 'lempuser'@'localhost' IDENTIFIED BY 'StrongAppPassword1!';
-- Grant only the required privileges
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER
ON lemp_test.*
TO 'lempuser'@'localhost';
FLUSH PRIVILEGES;
-- Verify
SHOW GRANTS FOR 'lempuser'@'localhost';
EXIT;
Test the connection from the command line:
mysql -u lempuser -p lemp_test -e "SELECT VERSION(); SHOW TABLES;"
Step 8: Test PHP Database Connectivity
Create a temporary PHP script to confirm PHP can reach MySQL:
sudo tee /var/www/example.com/html/dbtest.php <<'EOF'
<?php
$pdo = new PDO(
'mysql:host=localhost;dbname=lemp_test;charset=utf8mb4',
'lempuser',
'StrongAppPassword1!'
);
$stmt = $pdo->query('SELECT VERSION() AS version');
$row = $stmt->fetch(PDO::FETCH_ASSOC);
echo "MySQL version: " . htmlspecialchars($row['version']) . PHP_EOL;
EOF
curl http://example.com/dbtest.php
# MySQL version: 5.7.x
Remove the test file after confirming success:
sudo rm /var/www/example.com/html/dbtest.php
Step 9: Harden PHP-FPM and PHP Settings
Apply recommended production settings to /etc/php.ini:
sudo vi /etc/php.ini
; Disable information disclosure
expose_php = Off
; Limit script execution time
max_execution_time = 30
; Limit memory per script
memory_limit = 128M
; Restrict file uploads
upload_max_filesize = 10M
post_max_size = 12M
; Disable dangerous functions
disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source
; Enable OPcache for performance
opcache.enable = 1
opcache.memory_consumption = 128
opcache.max_accelerated_files = 10000
opcache.revalidate_freq = 2
Restart PHP-FPM to apply:
sudo systemctl restart php-fpm
Conclusion
You now have a fully functional LEMP stack on RHEL 7 with Nginx serving static files at native speed, MySQL 5.7 managing your relational data, and PHP 7.4 processing dynamic requests through PHP-FPM via a Unix socket. The architecture cleanly separates each component — you can upgrade PHP by swapping the Remi-managed package and restarting php-fpm, or migrate the database tier to a dedicated server simply by updating the MySQL hostname in your application configuration. With SELinux enforcing, firewalld restricting inbound ports, and PHP hardened to hide version information and disable dangerous functions, this stack is ready to host production applications. As a next step, consider adding a Let’s Encrypt TLS certificate with certbot to enable HTTPS.