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.