LEMP Stack Installation (Linux, Nginx, MySQL, PHP): Complete Production Guide
Introduction
The LEMP stack (Linux, Nginx, MySQL, PHP) represents a modern, high-performance alternative to the traditional LAMP stack. This powerful combination has become the preferred choice for many high-traffic websites and applications due to Nginx's exceptional performance, low resource consumption, and ability to handle concurrent connections efficiently. The LEMP stack powers some of the world's busiest websites including Netflix, Airbnb, and WordPress.com.
In this comprehensive guide, you'll learn how to build a production-ready LEMP stack from the ground up, with emphasis on security, performance optimization, and best practices that will ensure your web applications run smoothly and securely at scale.
What You'll Learn
- Complete LEMP stack installation on Ubuntu/Debian and CentOS/Rocky Linux
- Nginx web server configuration and server blocks setup
- MySQL/MariaDB installation with security hardening and optimization
- PHP-FPM installation and configuration for optimal performance
- SSL/TLS certificate installation with Let's Encrypt
- Security best practices and hardening techniques
- Performance tuning for high-traffic websites
- Monitoring and troubleshooting strategies
- Real-world deployment scenarios
Why Choose LEMP Over LAMP?
The LEMP stack offers several compelling advantages:
- Superior Performance: Nginx handles concurrent connections more efficiently than Apache
- Lower Resource Usage: Nginx consumes significantly less memory per connection
- Better for Static Content: Nginx serves static files much faster than Apache
- Event-Driven Architecture: Non-blocking I/O model handles thousands of concurrent connections
- Built-in Reverse Proxy: Excellent for microservices and load balancing
- Modern Design: Designed for the modern web with focus on performance
- Scalability: Better suited for high-traffic applications and horizontal scaling
LEMP vs LAMP: When to Choose LEMP
Choose LEMP when you need:
- High concurrent connection handling
- Low memory footprint
- Excellent static file serving
- Reverse proxy capabilities
- Better performance for modern web applications
Prerequisites
Before beginning the LEMP stack installation, ensure you have the following:
System Requirements
- Operating System: Ubuntu 20.04/22.04 LTS, Debian 10/11, CentOS 7/8, or Rocky Linux 8/9
- RAM: Minimum 1GB (2GB+ recommended for production environments)
- Disk Space: At least 10GB free space (20GB+ recommended for production)
- CPU: 1+ cores (2+ cores recommended for production workloads)
- Network: Stable internet connection for package downloads
Access Requirements
- Root or sudo privileges on the server
- SSH access to the server
- Basic understanding of Linux command line
- Familiarity with text editors (vim, nano)
- Understanding of DNS and networking concepts
Pre-Installation Checklist
- Server is up to date with latest security patches
- Firewall is configured (UFW for Ubuntu/Debian, firewalld for CentOS/Rocky)
- DNS records are configured and pointing to your server
- Domain names are ready for virtual host configuration
- Backup plan is in place
- You have planned your directory structure
Installation
Step 1: System Update and Preparation
Always start with a fully updated system to ensure security and stability.
For Ubuntu/Debian:
# Update package repository cache
sudo apt update
# Upgrade all installed packages
sudo apt upgrade -y
# Install essential build tools and utilities
sudo apt install -y curl wget vim git unzip software-properties-common apt-transport-https ca-certificates gnupg2
# Optional: Install helpful utilities
sudo apt install -y htop net-tools
For CentOS/Rocky Linux:
# Update all packages
sudo dnf update -y
# Install EPEL repository (Extra Packages for Enterprise Linux)
sudo dnf install -y epel-release
# Install essential utilities
sudo dnf install -y curl wget vim git unzip
# Optional: Install helpful utilities
sudo dnf install -y htop net-tools
Step 2: Nginx Installation
Nginx (pronounced "engine-x") is the web server component of the LEMP stack.
Ubuntu/Debian Installation:
# Install Nginx
sudo apt install -y nginx
# Enable Nginx to start on boot
sudo systemctl enable nginx
# Start Nginx service
sudo systemctl start nginx
# Verify Nginx is running
sudo systemctl status nginx
# Check Nginx version
nginx -v
CentOS/Rocky Linux Installation:
# Install Nginx
sudo dnf install -y nginx
# Enable Nginx to start on boot
sudo systemctl enable nginx
# Start Nginx service
sudo systemctl start nginx
# Verify Nginx is running
sudo systemctl status nginx
# Check Nginx version
nginx -v
Configure Firewall for Nginx:
Ubuntu/Debian (UFW):
# Allow Nginx through firewall
sudo ufw allow 'Nginx Full'
# Alternative: Allow specific ports
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Enable firewall if not already enabled
sudo ufw enable
# Verify firewall rules
sudo ufw status verbose
CentOS/Rocky Linux (firewalld):
# Allow HTTP traffic
sudo firewall-cmd --permanent --add-service=http
# Allow HTTPS traffic
sudo firewall-cmd --permanent --add-service=https
# Reload firewall to apply changes
sudo firewall-cmd --reload
# Verify firewall rules
sudo firewall-cmd --list-all
Verify Nginx Installation:
Open your web browser and navigate to your server's IP address:
http://your_server_ip
You should see the Nginx default welcome page with "Welcome to nginx!" message.
Step 3: MySQL/MariaDB Installation
MySQL (or its open-source fork MariaDB) provides the database management system for the LEMP stack.
Ubuntu/Debian Installation:
# Install MariaDB server (recommended)
sudo apt install -y mariadb-server mariadb-client
# Alternative: Install MySQL
# sudo apt install -y mysql-server mysql-client
# Enable MariaDB to start on boot
sudo systemctl enable mariadb
# Start MariaDB service
sudo systemctl start mariadb
# Verify MariaDB is running
sudo systemctl status mariadb
# Check MariaDB version
mysql --version
CentOS/Rocky Linux Installation:
# Install MariaDB server
sudo dnf install -y mariadb-server mariadb
# Enable MariaDB to start on boot
sudo systemctl enable mariadb
# Start MariaDB service
sudo systemctl start mariadb
# Verify MariaDB is running
sudo systemctl status mariadb
# Check MariaDB version
mysql --version
Secure MySQL/MariaDB Installation:
Run the security script to harden your database installation:
sudo mysql_secure_installation
Follow these recommendations:
- Enter current password for root: Press Enter (no password set initially)
- Switch to unix_socket authentication: No (if already root)
- Change the root password: Yes - Set a strong password
- Remove anonymous users: Yes
- Disallow root login remotely: Yes (recommended for security)
- Remove test database and access to it: Yes
- Reload privilege tables now: Yes
Test MySQL/MariaDB Connection:
# Login to MySQL as root
sudo mysql -u root -p
# Once logged in, run some test commands
SELECT VERSION();
SHOW DATABASES;
SELECT USER();
# Exit MySQL
EXIT;
Step 4: PHP and PHP-FPM Installation
PHP-FPM (FastCGI Process Manager) is optimized for high-performance PHP processing with Nginx.
Ubuntu/Debian Installation:
# Install PHP-FPM and common extensions
sudo apt install -y php-fpm php-mysql php-cli php-curl php-gd php-mbstring php-xml php-xmlrpc php-zip php-intl php-bcmath php-json php-soap php-readline php-common php-opcache
# Check PHP version
php -v
# Find PHP-FPM socket path (needed for Nginx configuration)
ls -la /var/run/php/
# You should see something like: php8.1-fpm.sock
# Check PHP-FPM status
sudo systemctl status php8.1-fpm
# Enable PHP-FPM to start on boot
sudo systemctl enable php8.1-fpm
CentOS/Rocky Linux Installation:
# Install PHP and PHP-FPM
sudo dnf install -y php php-fpm php-mysqlnd php-cli php-curl php-gd php-mbstring php-xml php-xmlrpc php-zip php-intl php-bcmath php-json php-soap php-opcache
# Check PHP version
php -v
# Enable PHP-FPM to start on boot
sudo systemctl enable php-fpm
# Start PHP-FPM service
sudo systemctl start php-fpm
# Check PHP-FPM status
sudo systemctl status php-fpm
Configure PHP-FPM for Nginx:
For CentOS/Rocky Linux, configure PHP-FPM to use Unix socket:
# Edit PHP-FPM pool configuration
sudo vim /etc/php-fpm.d/www.conf
# Find and modify these lines:
# Change from:
# listen = 127.0.0.1:9000
# To:
listen = /run/php-fpm/www.sock
# Ensure these settings are configured:
listen.owner = nginx
listen.group = nginx
listen.mode = 0660
# Save and restart PHP-FPM
sudo systemctl restart php-fpm
Test PHP Installation:
Create a PHP info file:
# Create PHP info file
sudo bash -c 'cat > /usr/share/nginx/html/info.php <<EOF
<?php
phpinfo();
?>
EOF'
# Set proper permissions
sudo chmod 644 /usr/share/nginx/html/info.php
We'll test this file after configuring Nginx to process PHP files in the next section.
Step 5: Configure Nginx to Process PHP
Configure Nginx to pass PHP requests to PHP-FPM.
Edit Default Nginx Configuration:
Ubuntu/Debian:
# Edit default server block
sudo vim /etc/nginx/sites-available/default
CentOS/Rocky Linux:
# Edit default configuration
sudo vim /etc/nginx/nginx.conf
Configure PHP Processing:
For a basic configuration, add or modify the server block:
Ubuntu/Debian (/etc/nginx/sites-available/default):
server {
listen 80 default_server;
listen [::]:80 default_server;
root /usr/share/nginx/html;
index index.php index.html index.htm;
server_name _;
location / {
try_files $uri $uri/ =404;
}
# Pass PHP scripts to PHP-FPM
location ~ \.php$ {
include snippets/fastcgi-php.conf;
# For Ubuntu 20.04 (PHP 7.4)
# fastcgi_pass unix:/run/php/php7.4-fpm.sock;
# For Ubuntu 22.04 (PHP 8.1)
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
# Deny access to .htaccess files
location ~ /\.ht {
deny all;
}
}
CentOS/Rocky Linux - Add to server block in /etc/nginx/nginx.conf:
server {
listen 80;
listen [::]:80;
server_name _;
root /usr/share/nginx/html;
index index.php index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
location ~ \.php$ {
try_files $uri =404;
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;
}
}
Test and Reload Nginx:
# Test Nginx configuration for syntax errors
sudo nginx -t
# If test passes, reload Nginx
sudo systemctl reload nginx
# Or restart Nginx
sudo systemctl restart nginx
Verify PHP Processing:
Now test the PHP info file we created earlier:
http://your_server_ip/info.php
You should see the PHP information page displaying your PHP configuration.
Important Security Note: Remove the info.php file after verification:
sudo rm /usr/share/nginx/html/info.php
Step 6: Verify Complete LEMP Stack
Create a test script that connects to MySQL to verify all components work together:
sudo bash -c 'cat > /usr/share/nginx/html/test_db.php <<EOF
<?php
\$servername = "localhost";
\$username = "root";
\$password = "your_mysql_root_password";
// Create connection
\$conn = new mysqli(\$servername, \$username, \$password);
// Check connection
if (\$conn->connect_error) {
die("Connection failed: " . \$conn->connect_error);
}
echo "<h1>LEMP Stack is Working!</h1>";
echo "<p>PHP version: " . phpversion() . "</p>";
echo "<p>Successfully connected to MySQL/MariaDB</p>";
\$conn->close();
?>
EOF'
Access the test script:
http://your_server_ip/test_db.php
Important: Delete this test file after verification:
sudo rm /usr/share/nginx/html/test_db.php
Congratulations! Your LEMP stack is now fully installed and functional.
Configuration
Nginx Server Blocks (Virtual Hosts)
Server blocks allow you to host multiple websites on a single server. This is equivalent to Apache's virtual hosts.
Create Directory Structure:
# Create directory for your website
sudo mkdir -p /var/www/example.com/html
# Set ownership (Ubuntu/Debian)
sudo chown -R $USER:$USER /var/www/example.com/html
# Set ownership (CentOS/Rocky)
sudo chown -R nginx:nginx /var/www/example.com/html
# Set proper permissions
sudo chmod -R 755 /var/www/example.com
# Create sample index page
sudo bash -c 'cat > /var/www/example.com/html/index.html <<EOF
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Example.com</title>
<style>
body { font-family: Arial, sans-serif; margin: 50px; }
h1 { color: #0066cc; }
</style>
</head>
<body>
<h1>Success! example.com is working!</h1>
<p>This is a test page for the LEMP stack server block.</p>
</body>
</html>
EOF'
Create Server Block Configuration:
Ubuntu/Debian:
# Create server block configuration file
sudo bash -c 'cat > /etc/nginx/sites-available/example.com <<EOF
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com/html;
index index.php index.html index.htm;
# Logging
access_log /var/log/nginx/example.com-access.log;
error_log /var/log/nginx/example.com-error.log;
# Main location block
location / {
try_files \$uri \$uri/ =404;
}
# PHP processing
location ~ \.php\$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
include fastcgi_params;
}
# Deny access to hidden files
location ~ /\. {
deny all;
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
}
EOF'
# Enable server block by creating symbolic link
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
# Test Nginx configuration
sudo nginx -t
# Reload Nginx
sudo systemctl reload nginx
CentOS/Rocky Linux:
# Create server block configuration file
sudo bash -c 'cat > /etc/nginx/conf.d/example.com.conf <<EOF
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com/html;
index index.php index.html index.htm;
# Logging
access_log /var/log/nginx/example.com-access.log;
error_log /var/log/nginx/example.com-error.log;
# Main location block
location / {
try_files \$uri \$uri/ =404;
}
# PHP processing
location ~ \.php\$ {
try_files \$uri =404;
fastcgi_pass unix:/run/php-fpm/www.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
include fastcgi_params;
}
# Deny access to hidden files
location ~ /\. {
deny all;
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
}
EOF'
# Test Nginx configuration
sudo nginx -t
# Reload Nginx
sudo systemctl reload nginx
Configure SELinux (CentOS/Rocky Only):
# Allow Nginx to read web content
sudo chcon -R -t httpd_sys_content_t /var/www/example.com/html
# Allow Nginx to write to specific directories (if needed)
sudo chcon -R -t httpd_sys_rw_content_t /var/www/example.com/html/uploads
# Make SELinux changes permanent
sudo semanage fcontext -a -t httpd_sys_content_t "/var/www/example.com/html(/.*)?"
sudo restorecon -Rv /var/www/example.com/html
MySQL/MariaDB Configuration
Create Database and User for Your Application:
# Login to MySQL
sudo mysql -u root -p
# Create database
CREATE DATABASE myapp_production;
# Create dedicated user
CREATE USER 'myapp_user'@'localhost' IDENTIFIED BY 'SecurePassword123!';
# Grant privileges
GRANT ALL PRIVILEGES ON myapp_production.* TO 'myapp_user'@'localhost';
# For read-only user (optional)
CREATE USER 'myapp_readonly'@'localhost' IDENTIFIED BY 'ReadOnlyPass123!';
GRANT SELECT ON myapp_production.* TO 'myapp_readonly'@'localhost';
# Flush privileges
FLUSH PRIVILEGES;
# Verify users
SELECT User, Host FROM mysql.user WHERE User LIKE 'myapp%';
# Exit
EXIT;
Optimize MySQL Configuration:
For Ubuntu/Debian, edit:
sudo vim /etc/mysql/mariadb.conf.d/50-server.cnf
For CentOS/Rocky, edit:
sudo vim /etc/my.cnf.d/mariadb-server.cnf
Add these optimizations (adjust based on your server's RAM):
[mysqld]
# Basic settings
max_connections = 150
connect_timeout = 10
wait_timeout = 600
max_allowed_packet = 64M
thread_cache_size = 128
sort_buffer_size = 4M
bulk_insert_buffer_size = 16M
tmp_table_size = 64M
max_heap_table_size = 64M
# Query cache (if using MySQL 5.7 or MariaDB < 10.5)
query_cache_type = 1
query_cache_limit = 2M
query_cache_size = 64M
query_cache_min_res_unit = 2k
# InnoDB settings (for 2GB RAM server)
innodb_buffer_pool_size = 768M
innodb_log_file_size = 128M
innodb_log_buffer_size = 8M
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT
innodb_file_per_table = 1
innodb_buffer_pool_instances = 1
# Logging
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow-query.log
long_query_time = 2
log_queries_not_using_indexes = 0
# Binary logging (for replication/backups)
# server-id = 1
# log_bin = /var/log/mysql/mysql-bin.log
# expire_logs_days = 7
# max_binlog_size = 100M
Restart MySQL to apply changes:
sudo systemctl restart mariadb
PHP Configuration and Optimization
Main PHP Configuration Files:
- Ubuntu/Debian:
/etc/php/8.1/fpm/php.ini - CentOS/Rocky:
/etc/php.ini
Edit PHP Configuration:
# Ubuntu/Debian
sudo vim /etc/php/8.1/fpm/php.ini
# CentOS/Rocky
sudo vim /etc/php.ini
Recommended PHP Settings for Production:
;;;;;;;;;;;;;;;;;;;
; Resource Limits ;
;;;;;;;;;;;;;;;;;;;
memory_limit = 256M
max_execution_time = 300
max_input_time = 300
max_input_vars = 3000
;;;;;;;;;;;;;;;;
; File Uploads ;
;;;;;;;;;;;;;;;;
file_uploads = On
upload_max_filesize = 64M
max_file_uploads = 20
post_max_size = 64M
;;;;;;;;;;;;;;;;;;;;;;
; Error Handling ;
;;;;;;;;;;;;;;;;;;;;;;
# Production settings - hide errors from users
display_errors = Off
display_startup_errors = Off
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
log_errors = On
error_log = /var/log/php-fpm/error.log
# Development settings - show errors
# display_errors = On
# display_startup_errors = On
# error_reporting = E_ALL
;;;;;;;;;;;;;;
; Security ;
;;;;;;;;;;;;;;
expose_php = Off
allow_url_fopen = On
allow_url_include = Off
disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source
;;;;;;;;;;;;;;;;
; Session ;
;;;;;;;;;;;;;;;;
session.save_handler = files
session.save_path = "/var/lib/php/session"
session.cookie_httponly = 1
session.cookie_secure = 1
session.use_strict_mode = 1
session.cookie_samesite = "Lax"
;;;;;;;;;;;;;;;;
; Timezone ;
;;;;;;;;;;;;;;;;
date.timezone = America/New_York
;;;;;;;;;;;;;;;;
; OPcache ;
;;;;;;;;;;;;;;;;
opcache.enable = 1
opcache.memory_consumption = 256
opcache.interned_strings_buffer = 16
opcache.max_accelerated_files = 20000
opcache.revalidate_freq = 60
opcache.fast_shutdown = 1
opcache.enable_cli = 0
opcache.validate_timestamps = 1
Configure PHP-FPM Pool:
Ubuntu/Debian:
sudo vim /etc/php/8.1/fpm/pool.d/www.conf
CentOS/Rocky:
sudo vim /etc/php-fpm.d/www.conf
Optimize PHP-FPM pool settings (for 2GB RAM server):
[www]
user = www-data ; Ubuntu/Debian
; user = nginx ; CentOS/Rocky
group = www-data ; Ubuntu/Debian
; group = nginx ; CentOS/Rocky
listen = /run/php/php8.1-fpm.sock ; Ubuntu/Debian
; listen = /run/php-fpm/www.sock ; CentOS/Rocky
listen.owner = www-data ; Ubuntu/Debian
; listen.owner = nginx ; CentOS/Rocky
listen.group = www-data ; Ubuntu/Debian
; listen.group = nginx ; CentOS/Rocky
listen.mode = 0660
; Process manager settings
pm = dynamic
pm.max_children = 20
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 10
pm.max_requests = 500
; Performance settings
pm.status_path = /status
ping.path = /ping
request_slowlog_timeout = 5s
slowlog = /var/log/php-fpm/slow.log
; Security
php_admin_value[error_log] = /var/log/php-fpm/www-error.log
php_admin_flag[log_errors] = on
Restart PHP-FPM to apply changes:
# Ubuntu/Debian
sudo systemctl restart php8.1-fpm
# CentOS/Rocky
sudo systemctl restart php-fpm
Nginx Performance Tuning
Edit Main Nginx Configuration:
Ubuntu/Debian:
sudo vim /etc/nginx/nginx.conf
CentOS/Rocky:
sudo vim /etc/nginx/nginx.conf
Optimized Nginx Configuration:
user www-data; # Ubuntu/Debian
# user nginx; # CentOS/Rocky
# Set to number of CPU cores
worker_processes auto;
# Maximum number of open files per worker
worker_rlimit_nofile 65535;
pid /run/nginx.pid;
events {
# Maximum concurrent connections per worker
worker_connections 4096;
# Optimized connection processing
use epoll;
multi_accept on;
}
http {
##
# Basic Settings
##
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 100;
types_hash_max_size 2048;
server_tokens off;
# Buffer sizes
client_body_buffer_size 128k;
client_max_body_size 64M;
client_header_buffer_size 1k;
large_client_header_buffers 4 32k;
include /etc/nginx/mime.types;
default_type application/octet-stream;
##
# SSL Settings
##
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
##
# Logging Settings
##
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
##
# Gzip Compression
##
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript
application/json application/javascript application/xml+rss
application/rss+xml font/truetype font/opentype
application/vnd.ms-fontobject image/svg+xml;
gzip_disable "msie6";
##
# FastCGI Cache (optional but recommended)
##
fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=PHPCACHE:100m inactive=60m;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
##
# Virtual Host Configs
##
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*; # Ubuntu/Debian only
}
Create cache directory:
sudo mkdir -p /var/cache/nginx
sudo chown -R www-data:www-data /var/cache/nginx # Ubuntu/Debian
sudo chown -R nginx:nginx /var/cache/nginx # CentOS/Rocky
Deployment
Deploying PHP Applications
Method 1: Manual File Upload
# Create application directory
sudo mkdir -p /var/www/myapp.com/html
# Upload files using SCP
scp -r /path/to/local/app/* user@server:/var/www/myapp.com/html/
# Or use rsync for efficient transfers
rsync -avz --progress /path/to/local/app/ user@server:/var/www/myapp.com/html/
# Set proper ownership
sudo chown -R www-data:www-data /var/www/myapp.com/html # Ubuntu/Debian
sudo chown -R nginx:nginx /var/www/myapp.com/html # CentOS/Rocky
# Set proper permissions
sudo find /var/www/myapp.com/html -type d -exec chmod 755 {} \;
sudo find /var/www/myapp.com/html -type f -exec chmod 644 {} \;
Method 2: Git-Based Deployment
# Navigate to web directory
cd /var/www/myapp.com
# Clone repository
sudo git clone https://github.com/username/myapp.git html
# Set proper ownership
sudo chown -R www-data:www-data html # Ubuntu/Debian
sudo chown -R nginx:nginx html # CentOS/Rocky
# Create deployment script
sudo bash -c 'cat > /usr/local/bin/deploy-myapp.sh <<EOF
#!/bin/bash
cd /var/www/myapp.com/html
git pull origin main
composer install --no-dev --optimize-autoloader
php artisan migrate --force
php artisan config:cache
php artisan route:cache
php artisan view:cache
sudo systemctl reload php-fpm
EOF'
sudo chmod +x /usr/local/bin/deploy-myapp.sh
Method 3: Deploying WordPress
Complete WordPress installation on LEMP:
# Download WordPress
cd /tmp
wget https://wordpress.org/latest.tar.gz
tar -xzf latest.tar.gz
# Move to web directory
sudo mv wordpress /var/www/mysite.com/html
# Create WordPress configuration
cd /var/www/mysite.com/html
sudo cp wp-config-sample.php wp-config.php
# Set database credentials
sudo vim wp-config.php
# Update DB_NAME, DB_USER, DB_PASSWORD
# Generate security keys
curl -s https://api.wordpress.org/secret-key/1.1/salt/
# Paste the keys into wp-config.php
# Set proper permissions
sudo chown -R www-data:www-data /var/www/mysite.com/html # Ubuntu/Debian
sudo chown -R nginx:nginx /var/www/mysite.com/html # CentOS/Rocky
sudo find /var/www/mysite.com/html -type d -exec chmod 755 {} \;
sudo find /var/www/mysite.com/html -type f -exec chmod 644 {} \;
# Create uploads directory with write permissions
sudo mkdir -p /var/www/mysite.com/html/wp-content/uploads
sudo chmod 775 /var/www/mysite.com/html/wp-content/uploads
Nginx configuration for WordPress:
server {
listen 80;
server_name mysite.com www.mysite.com;
root /var/www/mysite.com/html;
index index.php index.html;
# WordPress permalinks
location / {
try_files $uri $uri/ /index.php?$args;
}
# PHP processing
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
# Deny access to sensitive files
location ~ /\.(ht|git|svn) {
deny all;
}
location = /xmlrpc.php {
deny all;
}
# Browser caching for static assets
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf)$ {
expires 365d;
add_header Cache-Control "public, immutable";
}
}
SSL/TLS Certificate Installation
Secure your websites with free Let's Encrypt certificates.
Install Certbot:
Ubuntu/Debian:
sudo apt install -y certbot python3-certbot-nginx
CentOS/Rocky:
sudo dnf install -y certbot python3-certbot-nginx
Obtain SSL Certificate:
# For single domain
sudo certbot --nginx -d example.com
# For multiple domains
sudo certbot --nginx -d example.com -d www.example.com
# Non-interactive mode
sudo certbot --nginx -d example.com -d www.example.com --non-interactive --agree-tos -m [email protected]
Certbot will automatically:
- Obtain the certificate
- Configure Nginx for HTTPS
- Set up automatic renewal
Test Certificate Renewal:
sudo certbot renew --dry-run
Manual Certificate Renewal (if needed):
sudo certbot renew
Certificates are automatically renewed via cron or systemd timer.
Verify SSL Configuration:
# Test SSL setup online
# Visit: https://www.ssllabs.com/ssltest/analyze.html?d=example.com
Monitoring
Nginx Monitoring
Enable Nginx Status Page:
Add to your Nginx configuration:
server {
listen 127.0.0.1:80;
server_name localhost;
location /nginx_status {
stub_status on;
access_log off;
allow 127.0.0.1;
deny all;
}
}
Access status:
curl http://127.0.0.1/nginx_status
Monitor Nginx Logs:
# Real-time access log
sudo tail -f /var/log/nginx/access.log
# Real-time error log
sudo tail -f /var/log/nginx/error.log
# Analyze top URLs
awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
# Analyze HTTP status codes
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
# Analyze top IPs
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
# Find 404 errors
grep " 404 " /var/log/nginx/access.log | tail -20
# Find 5xx server errors
grep " 5[0-9][0-9] " /var/log/nginx/error.log | tail -20
PHP-FPM Monitoring
Enable PHP-FPM Status Page:
Already configured in pool settings. Access it via Nginx:
Add to server block:
location ~ ^/(status|ping)$ {
access_log off;
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
Access status:
curl http://localhost/status
curl http://localhost/ping
Monitor PHP-FPM Logs:
# PHP-FPM error log
sudo tail -f /var/log/php-fpm/error.log
# PHP-FPM slow log
sudo tail -f /var/log/php-fpm/slow.log
# PHP errors
sudo tail -f /var/log/php-fpm/www-error.log
MySQL Monitoring
# Login to MySQL
sudo mysql -u root -p
# Show processlist
SHOW PROCESSLIST;
# Show status
SHOW STATUS;
# Show variables
SHOW VARIABLES LIKE '%cache%';
# Connection statistics
SHOW STATUS LIKE 'Threads%';
SHOW STATUS LIKE 'Connections';
SHOW STATUS LIKE 'Max_used_connections';
# Query cache statistics (if enabled)
SHOW STATUS LIKE 'Qcache%';
# InnoDB buffer pool usage
SHOW STATUS LIKE 'Innodb_buffer_pool%';
System Monitoring
# CPU and memory usage
htop
# Disk usage
df -h
# Disk I/O
iostat -x 2
# Network connections
ss -tunlp
netstat -tunlp
# Active connections to Nginx
ss -n | grep :80 | wc -l
# Active PHP-FPM processes
ps aux | grep php-fpm | wc -l
# Check service status
sudo systemctl status nginx
sudo systemctl status php-fpm
sudo systemctl status mariadb
Automated Monitoring Script
sudo bash -c 'cat > /usr/local/bin/lemp-monitor.sh <<'EOF'
#!/bin/bash
echo "========================================="
echo " LEMP Stack Monitoring Report"
echo " $(date)"
echo "========================================="
echo ""
echo "=== Service Status ==="
systemctl is-active nginx && echo "Nginx: RUNNING" || echo "Nginx: STOPPED"
systemctl is-active php-fpm && echo "PHP-FPM: RUNNING" || echo "PHP-FPM: STOPPED"
systemctl is-active mariadb && echo "MariaDB: RUNNING" || echo "MariaDB: STOPPED"
echo ""
echo "=== Nginx Connections ==="
NGINX_CONN=$(ss -n | grep :80 | wc -l)
echo "Active connections: $NGINX_CONN"
echo ""
echo "=== PHP-FPM Processes ==="
PHP_PROC=$(ps aux | grep php-fpm | grep -v grep | wc -l)
echo "Active processes: $PHP_PROC"
echo ""
echo "=== MySQL Connections ==="
MYSQL_CONN=$(ss -n | grep :3306 | wc -l)
echo "Active connections: $MYSQL_CONN"
echo ""
echo "=== System Resources ==="
echo "Load average: $(uptime | awk -F'load average:' '{print $2}')"
echo "Memory usage:"
free -h | grep -E "Mem|Swap"
echo ""
echo "=== Disk Usage ==="
df -h | grep -E "Filesystem|/dev/" | grep -v "tmpfs"
echo ""
echo "========================================="
EOF'
sudo chmod +x /usr/local/bin/lemp-monitor.sh
# Run the script
sudo /usr/local/bin/lemp-monitor.sh
Troubleshooting
Nginx Won't Start
Check for configuration errors:
# Test configuration
sudo nginx -t
# View detailed error messages
sudo systemctl status nginx
sudo journalctl -xe -u nginx
# Check error log
sudo tail -n 50 /var/log/nginx/error.log
Common issues:
- Port already in use:
# Find what's using port 80
sudo lsof -i :80
sudo ss -tunlp | grep :80
# Kill the conflicting process or change Nginx port
- Permission errors:
# Check file ownership
ls -la /var/www/
# Fix ownership
sudo chown -R www-data:www-data /var/www # Ubuntu/Debian
sudo chown -R nginx:nginx /var/www # CentOS/Rocky
- SELinux blocking (CentOS/Rocky):
# Check SELinux denials
sudo ausearch -m avc -ts recent
# Temporarily disable for testing
sudo setenforce 0
# Fix SELinux contexts
sudo chcon -R -t httpd_sys_content_t /var/www/
PHP Not Processing (Downloading Instead)
Solutions:
- Check PHP-FPM is running:
sudo systemctl status php-fpm
sudo systemctl start php-fpm
- Verify PHP-FPM socket:
# Check if socket exists
ls -la /run/php-fpm/www.sock # CentOS/Rocky
ls -la /run/php/php8.1-fpm.sock # Ubuntu/Debian
# Check permissions
sudo chmod 666 /run/php-fpm/www.sock
- Check Nginx PHP configuration:
# Ensure fastcgi_pass path is correct in server block
sudo nginx -t
sudo systemctl reload nginx
502 Bad Gateway Error
This typically means PHP-FPM is not responding.
Diagnostic steps:
# Check PHP-FPM status
sudo systemctl status php-fpm
# View PHP-FPM error log
sudo tail -n 50 /var/log/php-fpm/error.log
# Check if socket exists and has correct permissions
ls -la /run/php-fpm/www.sock
# Restart PHP-FPM
sudo systemctl restart php-fpm
Common causes:
- PHP-FPM not running:
sudo systemctl start php-fpm
- Socket permission issues:
# Set correct permissions in /etc/php-fpm.d/www.conf
listen.owner = nginx
listen.group = nginx
listen.mode = 0660
sudo systemctl restart php-fpm
- Too many concurrent requests:
# Increase max_children in /etc/php-fpm.d/www.conf
pm.max_children = 50
sudo systemctl restart php-fpm
504 Gateway Timeout
PHP script is taking too long to execute.
Solutions:
- Increase PHP execution time:
# Edit php.ini
max_execution_time = 300
# Restart PHP-FPM
sudo systemctl restart php-fpm
- Increase Nginx timeout:
# Add to server block
fastcgi_read_timeout 300s;
proxy_read_timeout 300s;
# Reload Nginx
sudo systemctl reload nginx
- Optimize slow PHP code:
# Enable slow log in PHP-FPM
request_slowlog_timeout = 5s
slowlog = /var/log/php-fpm/slow.log
# Check slow log
sudo tail -f /var/log/php-fpm/slow.log
MySQL Connection Errors
"Can't connect to MySQL server":
# Check MySQL is running
sudo systemctl status mariadb
sudo systemctl start mariadb
# Test connection
mysql -u root -p
# Check MySQL error log
sudo tail -n 50 /var/log/mysql/error.log
# Verify socket file
ls -la /var/run/mysqld/mysqld.sock
Access denied errors:
# Verify user credentials
sudo mysql -u root -p
SELECT User, Host FROM mysql.user WHERE User = 'youruser';
# Grant proper permissions
GRANT ALL PRIVILEGES ON database.* TO 'user'@'localhost';
FLUSH PRIVILEGES;
High Memory Usage
Optimize PHP-FPM:
# Edit /etc/php-fpm.d/www.conf
pm = dynamic
pm.max_children = 10
pm.start_servers = 3
pm.min_spare_servers = 2
pm.max_spare_servers = 5
pm.max_requests = 500
sudo systemctl restart php-fpm
Optimize MySQL:
# Reduce buffer pool
innodb_buffer_pool_size = 512M
sudo systemctl restart mariadb
Monitor memory usage:
# Per-process memory
ps aux --sort=-%mem | head -10
# PHP-FPM memory
ps --no-headers -o "rss,cmd" -C php-fpm | awk '{ sum+=$1 } END { printf ("%d%s\n", sum/NR/1024,"M") }'
Permission Denied Errors
Fix file permissions:
# Set proper ownership
sudo chown -R www-data:www-data /var/www/site # Ubuntu/Debian
sudo chown -R nginx:nginx /var/www/site # CentOS/Rocky
# Set directory permissions
find /var/www/site -type d -exec chmod 755 {} \;
# Set file permissions
find /var/www/site -type f -exec chmod 644 {} \;
# Writable directories
chmod 775 /var/www/site/storage
chmod 775 /var/www/site/cache
SELinux issues (CentOS/Rocky):
# Set correct context
sudo chcon -R -t httpd_sys_content_t /var/www/site
# For writable directories
sudo chcon -R -t httpd_sys_rw_content_t /var/www/site/storage
# Make permanent
sudo semanage fcontext -a -t httpd_sys_content_t "/var/www/site(/.*)?"
sudo restorecon -Rv /var/www/site
Security Best Practices
Nginx Security Hardening
# Hide Nginx version
server_tokens off;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
# Disable unwanted methods
if ($request_method !~ ^(GET|POST|HEAD)$ ) {
return 405;
}
# Limit request size
client_max_body_size 10M;
client_body_buffer_size 128k;
# Rate limiting
limit_req_zone $binary_remote_addr zone=one:10m rate=30r/m;
limit_req zone=one burst=5;
# Deny access to sensitive files
location ~ /\.(ht|git|svn) {
deny all;
}
PHP Security Configuration
# Disable dangerous functions
disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source,phpinfo
# Hide PHP version
expose_php = Off
# Restrict file access
open_basedir = /var/www:/tmp
# Disable remote file inclusion
allow_url_fopen = On
allow_url_include = Off
# Session security
session.cookie_httponly = 1
session.cookie_secure = 1
session.use_strict_mode = 1
session.cookie_samesite = Lax
MySQL Security
# Secure installation
sudo mysql_secure_installation
# Disable remote root
DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');
# Remove test database
DROP DATABASE IF EXISTS test;
# Use strong passwords
ALTER USER 'user'@'localhost' IDENTIFIED BY 'StrongPassword123!';
# Limit privileges
GRANT SELECT, INSERT, UPDATE, DELETE ON database.* TO 'user'@'localhost';
# Bind to localhost only
bind-address = 127.0.0.1
Install Fail2Ban
# Install
sudo apt install -y fail2ban # Ubuntu/Debian
sudo dnf install -y fail2ban # CentOS/Rocky
# Configure
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
# Edit configuration
sudo vim /etc/fail2ban/jail.local
# Add Nginx protection
[nginx-http-auth]
enabled = true
filter = nginx-http-auth
port = http,https
logpath = /var/log/nginx/error.log
maxretry = 3
bantime = 3600
[nginx-noscript]
enabled = true
port = http,https
filter = nginx-noscript
logpath = /var/log/nginx/access.log
maxretry = 6
bantime = 3600
# Start service
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
# Check status
sudo fail2ban-client status
Performance Optimization
Enable FastCGI Caching
# In http block
fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=PHPCACHE:100m inactive=60m max_size=1g;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
# In server or location block
location ~ \.php$ {
# ... fastcgi_pass configuration ...
fastcgi_cache PHPCACHE;
fastcgi_cache_valid 200 301 302 60m;
fastcgi_cache_valid 404 10m;
fastcgi_cache_bypass $http_pragma $http_authorization;
fastcgi_cache_revalidate on;
fastcgi_cache_min_uses 1;
fastcgi_cache_use_stale error timeout updating http_500 http_503;
fastcgi_cache_background_update on;
fastcgi_cache_lock on;
add_header X-FastCGI-Cache $upstream_cache_status;
}
Enable HTTP/2
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
# ... rest of configuration ...
}
Browser Caching
# Cache static assets
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
Optimize Images
# Install image optimization tools
sudo apt install -y jpegoptim optipng
# Optimize existing images
find /var/www -name "*.jpg" -exec jpegoptim --strip-all {} \;
find /var/www -name "*.png" -exec optipng -o2 {} \;
Conclusion
Congratulations! You now have a fully functional, production-ready LEMP stack configured with comprehensive security measures, performance optimizations, and monitoring capabilities. This setup provides an excellent foundation for hosting high-performance PHP applications.
Key Achievements
- ✅ Complete LEMP stack installation (Linux, Nginx, MySQL, PHP-FPM)
- ✅ SSL/TLS encryption with Let's Encrypt
- ✅ Security hardening with firewall and Fail2Ban
- ✅ Performance optimizations including caching and compression
- ✅ Monitoring tools and log analysis techniques
- ✅ Troubleshooting strategies for common issues
Best Practices Summary
- Keep Everything Updated: Regularly update all components for security patches
- Monitor Continuously: Set up automated monitoring and alerting
- Backup Regularly: Implement automated daily backups
- Security First: Always use HTTPS, strong passwords, and restrict access
- Optimize Proactively: Monitor performance and optimize before issues arise
- Document Changes: Keep records of configuration changes
Next Steps
- Set Up Automated Backups: Implement database and file backup automation
- Configure Monitoring: Install Prometheus, Grafana, or similar tools
- Implement CI/CD: Automate deployment processes
- Load Testing: Test your stack under realistic load conditions
- High Availability: Consider implementing load balancing and replication
- Security Audits: Regularly scan for vulnerabilities
Additional Resources
- Nginx Documentation: https://nginx.org/en/docs/
- PHP-FPM Documentation: https://www.php.net/manual/en/install.fpm.php
- MariaDB Knowledge Base: https://mariadb.com/kb/en/
- Let's Encrypt: https://letsencrypt.org/docs/
- Security Best Practices: https://www.nginx.com/blog/
The LEMP stack's performance advantages make it ideal for modern web applications. With proper configuration and maintenance, your LEMP stack will deliver fast, secure, and reliable service for years to come.
Happy deploying!


