How to Set Up a LEMP Stack on Ubuntu and Debian: Complete Installation Guide

The LEMP stack consists of Linux, Nginx (pronounced “Engine-X”), MySQL/MariaDB, and PHP. This powerful combination is preferred over LAMP for high-traffic websites due to Nginx’s superior performance in handling concurrent connections. This guide covers installing and configuring a complete LEMP stack on Ubuntu 22.04/24.04 and Debian 11/12.

LEMP Stack Components

  • Linux – The operating system
  • Engine-X (Nginx) – High-performance web server
  • MySQL/MariaDB – Database server
  • PHP – Server-side scripting language

Prerequisites

  • Ubuntu 22.04/24.04 or Debian 11/12 server
  • Root or sudo access
  • Domain name pointing to your server (optional but recommended)

Step 1: Update System Packages

# Update package index
sudo apt update

# Upgrade existing packages
sudo apt upgrade -y

Step 2: Install Nginx

# Install Nginx
sudo apt install nginx -y

# Start and enable Nginx
sudo systemctl start nginx
sudo systemctl enable nginx

# Verify installation
sudo systemctl status nginx

# Check Nginx version
nginx -v

Configure Firewall

# If using UFW
sudo ufw allow 'Nginx Full'
sudo ufw status

# Verify Nginx is running
curl -I localhost

Open your browser and visit http://your_server_ip. You should see the default Nginx welcome page.

Step 3: Install MariaDB (MySQL Alternative)

MariaDB is a drop-in replacement for MySQL with better performance and more features.

# Install MariaDB
sudo apt install mariadb-server mariadb-client -y

# Start and enable MariaDB
sudo systemctl start mariadb
sudo systemctl enable mariadb

# Secure the installation
sudo mysql_secure_installation

MariaDB Secure Installation Prompts

# Follow the prompts:
Enter current password for root: [Press Enter - no password set]
Switch to unix_socket authentication: n
Change the root password: Y
New password: [Enter strong password]
Remove anonymous users: Y
Disallow root login remotely: Y
Remove test database: Y
Reload privilege tables: Y

Verify MariaDB Installation

# Log into MariaDB
sudo mysql -u root -p

# Check version
SELECT VERSION();

# Exit
EXIT;

Step 4: Install PHP and PHP-FPM

PHP-FPM (FastCGI Process Manager) handles PHP processing for Nginx.

# Install PHP and common extensions
sudo apt install php-fpm php-mysql php-cli php-common php-curl \
    php-gd php-mbstring php-xml php-zip php-intl php-bcmath -y

# Check PHP version
php -v

# Check PHP-FPM status
sudo systemctl status php*-fpm

Verify PHP-FPM Socket

# Find PHP-FPM socket path
ls /run/php/

# Output example: php8.2-fpm.sock

Step 5: Configure Nginx for PHP

Create a Server Block

# Create configuration file
sudo nano /etc/nginx/sites-available/example.com

Add the following configuration:

server {
    listen 80;
    listen [::]:80;
    
    server_name example.com www.example.com;
    root /var/www/example.com;
    
    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/ /index.php?$query_string;
    }
    
    # PHP processing
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
    
    # Deny access to .htaccess files
    location ~ /\.ht {
        deny all;
    }
    
    # Cache static files
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|pdf|txt)$ {
        expires 7d;
        add_header Cache-Control "public, no-transform";
    }
}

Enable the Site

# Create document root
sudo mkdir -p /var/www/example.com

# Set ownership
sudo chown -R www-data:www-data /var/www/example.com

# Create symbolic link to enable site
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/

# Remove default site (optional)
sudo rm /etc/nginx/sites-enabled/default

# Test configuration
sudo nginx -t

# Reload Nginx
sudo systemctl reload nginx

Step 6: Test PHP Processing

# Create PHP test file
sudo nano /var/www/example.com/info.php

Add:

<?php
phpinfo();
?>

Visit http://your_server_ip/info.php to see PHP information.

# Remove test file after verification (security)
sudo rm /var/www/example.com/info.php

Step 7: Test Database Connection

Create a Test Database and User

# Log into MariaDB
sudo mysql -u root -p

# Create database
CREATE DATABASE testdb;

# Create user
CREATE USER 'testuser'@'localhost' IDENTIFIED BY 'StrongPassword123!';

# Grant privileges
GRANT ALL PRIVILEGES ON testdb.* TO 'testuser'@'localhost';

# Apply changes
FLUSH PRIVILEGES;

EXIT;

Create Database Test Script

sudo nano /var/www/example.com/dbtest.php
<?php
$host = 'localhost';
$db = 'testdb';
$user = 'testuser';
$pass = 'StrongPassword123!';

try {
    $pdo = new PDO("mysql:host=$host;dbname=$db", $user, $pass);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    echo "<h1>Database Connection Successful!</h1>";
    echo "<p>LEMP Stack is working correctly.</p>";
} catch(PDOException $e) {
    echo "Connection failed: " . $e->getMessage();
}
?>

Visit http://your_server_ip/dbtest.php to test.

# Remove test file
sudo rm /var/www/example.com/dbtest.php

Step 8: Add SSL with Let’s Encrypt

# Install Certbot
sudo apt install certbot python3-certbot-nginx -y

# Obtain SSL certificate
sudo certbot --nginx -d example.com -d www.example.com

# Follow prompts to complete installation

# Verify auto-renewal
sudo certbot renew --dry-run

PHP-FPM Configuration

Optimize PHP-FPM Pool

sudo nano /etc/php/8.2/fpm/pool.d/www.conf

Key settings to adjust:

; Process manager settings
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 35
pm.max_requests = 500

; Timeouts
request_terminate_timeout = 300

; Error logging
php_admin_flag[log_errors] = on
php_admin_value[error_log] = /var/log/php/error.log

PHP Configuration

sudo nano /etc/php/8.2/fpm/php.ini

Recommended settings:

; Memory and execution
memory_limit = 256M
max_execution_time = 300
max_input_time = 300

; Upload limits
upload_max_filesize = 64M
post_max_size = 64M

; Error handling
display_errors = Off
log_errors = On
error_log = /var/log/php/error.log

; Security
expose_php = Off
allow_url_fopen = Off

; Session security
session.cookie_httponly = 1
session.cookie_secure = 1
session.use_strict_mode = 1
# Create log directory
sudo mkdir -p /var/log/php
sudo chown www-data:www-data /var/log/php

# Restart PHP-FPM
sudo systemctl restart php8.2-fpm

Nginx Performance Optimization

sudo nano /etc/nginx/nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
    worker_connections 1024;
    multi_accept on;
    use epoll;
}

http {
    # Basic Settings
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    server_tokens off;
    
    # Buffer sizes
    client_body_buffer_size 10K;
    client_header_buffer_size 1k;
    client_max_body_size 64m;
    large_client_header_buffers 4 32k;
    
    # Timeouts
    client_body_timeout 12;
    client_header_timeout 12;
    send_timeout 10;
    
    # Gzip compression
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types text/plain text/css text/xml application/json 
               application/javascript application/xml+rss 
               application/atom+xml image/svg+xml;
    
    # FastCGI cache
    fastcgi_cache_path /var/cache/nginx levels=1:2 
                       keys_zone=WORDPRESS:100m inactive=60m;
    fastcgi_cache_key "$scheme$request_method$host$request_uri";
    
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    
    # Logging
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;
    
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

MariaDB Optimization

sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf

Add under [mysqld]:

[mysqld]
# InnoDB settings
innodb_buffer_pool_size = 256M
innodb_log_file_size = 64M
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT

# Query cache (for MariaDB)
query_cache_type = 1
query_cache_size = 32M
query_cache_limit = 2M

# Connection settings
max_connections = 150
wait_timeout = 600

# Temporary tables
tmp_table_size = 64M
max_heap_table_size = 64M

# Logging
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
# Restart MariaDB
sudo systemctl restart mariadb

Installing WordPress on LEMP

# Create database for WordPress
sudo mysql -u root -p

CREATE DATABASE wordpress;
CREATE USER 'wpuser'@'localhost' IDENTIFIED BY 'StrongPassword!';
GRANT ALL PRIVILEGES ON wordpress.* TO 'wpuser'@'localhost';
FLUSH PRIVILEGES;
EXIT;

# Download WordPress
cd /tmp
wget https://wordpress.org/latest.tar.gz
tar -xzvf latest.tar.gz

# Move to web root
sudo mv wordpress/* /var/www/example.com/

# Set permissions
sudo chown -R www-data:www-data /var/www/example.com
sudo find /var/www/example.com -type d -exec chmod 755 {} \;
sudo find /var/www/example.com -type f -exec chmod 644 {} \;

Nginx Configuration for WordPress

server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/example.com;
    index index.php index.html;
    
    # Logging
    access_log /var/log/nginx/wordpress.access.log;
    error_log /var/log/nginx/wordpress.error.log;
    
    # WordPress permalinks
    location / {
        try_files $uri $uri/ /index.php?$args;
    }
    
    # PHP handling
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
    
    # Security - deny access to sensitive files
    location ~ /\.ht {
        deny all;
    }
    
    location = /wp-config.php {
        deny all;
    }
    
    location ~* /(?:uploads|files)/.*\.php$ {
        deny all;
    }
    
    # Cache static files
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
        expires 30d;
        add_header Cache-Control "public, no-transform";
    }
    
    # Block XML-RPC
    location = /xmlrpc.php {
        deny all;
        access_log off;
        log_not_found off;
    }
}

Security Hardening

Nginx Security Headers

# Add to server block
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 "strict-origin-when-cross-origin" always;

Secure File Permissions

# Web files
sudo chown -R www-data:www-data /var/www/example.com
sudo find /var/www/example.com -type d -exec chmod 755 {} \;
sudo find /var/www/example.com -type f -exec chmod 644 {} \;

# Config files (more restrictive)
sudo chmod 600 /var/www/example.com/wp-config.php

Useful Commands

# Service management
sudo systemctl status nginx php8.2-fpm mariadb
sudo systemctl restart nginx php8.2-fpm mariadb

# View logs
sudo tail -f /var/log/nginx/error.log
sudo tail -f /var/log/php/error.log
sudo tail -f /var/log/mysql/error.log

# Test Nginx configuration
sudo nginx -t

# Check PHP-FPM status
sudo php-fpm8.2 -t

# Database backup
sudo mysqldump -u root -p wordpress > backup.sql

Troubleshooting

502 Bad Gateway

  • Check PHP-FPM is running: sudo systemctl status php8.2-fpm
  • Verify socket path in Nginx config matches PHP-FPM socket
  • Check PHP-FPM logs: sudo tail /var/log/php8.2-fpm.log

Permission Denied Errors

sudo chown -R www-data:www-data /var/www/example.com
sudo chmod -R 755 /var/www/example.com

MySQL Connection Refused

# Check if MariaDB is running
sudo systemctl status mariadb

# Verify user has correct privileges
sudo mysql -u root -p
SHOW GRANTS FOR 'username'@'localhost';

Conclusion

Your LEMP stack is now ready for production use. This setup provides excellent performance for PHP applications like WordPress, Laravel, and other frameworks. Remember to keep your system updated, monitor logs regularly, and implement additional security measures like Fail2ban for comprehensive protection.

For high-traffic sites, consider adding Redis or Memcached for caching, and implementing a CDN for static content delivery.

Was this article helpful?

R

About Ramesh Sundararamaiah

Red Hat Certified Architect

Expert in Linux system administration, DevOps automation, and cloud infrastructure. Specializing in Red Hat Enterprise Linux, CentOS, Ubuntu, Docker, Ansible, and enterprise IT solutions.

🐧 Stay Updated with Linux Tips

Get the latest tutorials, news, and guides delivered to your inbox weekly.

Add Comment