π Understanding the LEMP Stack
The LEMP stack is a battle-tested suite of open-source software components used for building and running high-performance web applications.
LEMP stands for:
- Linux β The foundational operating system.
- Nginx (Engine-X) β A high-performance, event-driven web server.
- MySQL β A relational database system for structured data storage.
- PHP β A server-side scripting language for generating dynamic content.
Itβs the younger, faster cousin of the LAMP stack (which uses Apache), prized for its ability to handle heavy concurrent traffic with minimal overhead.
π Why the LEMP Stack Still Reigns in 2025
In an era dominated by serverless functions, containers, and JAMstack architectures, the LEMP stack remains highly relevant because it delivers:
- Performance β Nginx is built for scale and concurrency.
- Flexibility β Replace components easily (MariaDB for MySQL, Python for PHP, etc.).
- Cost Efficiency β Fully open-source, no licensing fees.
- Global Support β Backed by one of the largest developer communities.
- Proven Stability β Powering production workloads for well over a decade.
π οΈ Common Use Cases
The LEMP stack is a Swiss Army knife for web application development:
- Static websites β Lightning-fast HTML/CSS delivery via Nginx.
- Dynamic sites β PHP + MySQL to deliver personalized, data-driven pages.
- API backends β RESTful or GraphQL APIs serving mobile/web clients.
- CMS platforms β Optimized hosting for WordPress, Drupal, Joomla, and more.
ποΈ LEMP Stack Architecture
Hereβs how the components interact in a typical request-response cycle:
Browser (User Request: example.com)
β
βΌ
βββββββββββββββββ
β Nginx β
β Serves static β
β Routes PHP β
βββββββββββββββββ
β
PHP Request
βΌ
βββββββββββββββββ
β PHP-FPM β
β Executes PHP β
β Talks to MySQLβ
βββββββββββββββββ
β
SQL Queries
βΌ
βββββββββββββββββ
β MySQL β
β Stores/Retr. β
βββββββββββββββββ
β
HTML Output
βΌ
βββββββββββββββββ
β Browser β
β Renders page β
βββββββββββββββββ
π Full Deployment Guide (Tested on Ubuntu Server 24.04 LTS)
This section walks through a from-scratch LEMP setup on AWS EC2 β no skipped steps, no guesswork.
π Prerequisites
Before diving into deploying a LEMP stack on AWS, ensure you have the following:
1. AWS Account
Sign up at AWS Console.
Youβll need billing enabled (a valid credit/debit card) to provision EC2 instances.
Make sure youβre familiar with AWS free tier limits to avoid unexpected charges.
2. Basic Linux Command-Line Skills
Knowledge of commands like cd, ls, apt, and nano will be useful.
If youβre new, check out:
π Beginnerβs Guide to Linux Commands
3. Git Installed Locally
To clone or push project files to GitHub.
Install:
sudo apt update && sudo apt install git -y
Or download from git-scm.com
4. Provisioning an AWS EC2 Instance
π‘ Weβll use this EC2 server as the environment for our LEMP stack.
Steps:
- Log in to your AWS Console.
- Navigate to EC2 β Click Launch Instance.
- Name your instance:
LEMP-Server
.
- Select Amazon Machine Image (AMI): Ubuntu Server 24.04 LTS (Free tier eligible).
- Choose Instance Type: t2.micro (Free tier eligible).
- Create or choose a Key Pair for SSH access.
- Configure Security Group:
- Allow SSH (Port 22) from your IP.
- Allow HTTP (Port 80) from anywhere.
- Allow HTTPS (Port 443) from anywhere.
- Click Launch Instance.
- Once running, copy the Public IPv4 address β youβll use it to connect via SSH.
5. SSH Access
From your local terminal:
chmod 400 ~/<your key pair>.pem
ssh -i /path/to/key.pem ubuntu@<Your-EC2-Public-IP>
β‘ With these prerequisites in place, youβre ready to start setting up the LEMP stack on AWS!
Step 1: Update the System
sudo apt update
π‘ Breakdown
Command | Purpose |
---|---|
sudo apt update |
Updates the package list from repositories. |
sudo apt upgrade -y |
Installs the latest updates for all packages. |
Step 2: Install Nginx
sudo apt install nginx
π‘ Breakdown
Command | Purpose |
---|---|
sudo apt install nginx |
Installs the Nginx web server and prompt for confirmation. |
Step 3: Check the status of Nginx
sudo systemctl status nginx
Command | Purpose |
---|---|
systemctl status nginx |
Shows Nginx status. |
Step 4: Test locally on the instance. If you see HTTP/1.1 200 OK or the default Nginx HTML, Nginx is serving.
curl -I http://localhost
# or
curl http://127.0.0.1:80
Step 5: Test on a browser
http://<EC2-Public-IP>:80
Step 6: Install MySQL
sudo apt install mysql-server
π‘ Breakdown
Command | Purpose |
---|---|
apt install mysql-server |
Installs the MySQL database server. |
Step 5: Secure MySQL
sudo mysql_secure_installation
π‘ Breakdown
Step | Action |
---|---|
Password policy | Choose password validation level. |
Remove anonymous users | Prevents anonymous access to DB. |
Disallow remote root login | Increases security. |
Remove test database | Cleans up default DB. |
Step 7: Install PHP
sudo apt install php-fpm php-mysql
π‘ Breakdown
Package | Purpose |
---|---|
php-fpm |
PHP FastCGI Process Manager for Nginx. |
php-mysql |
PHP module to interact with MySQL. |
- Create project folder and set owner:
sudo mkdir -p /var/www/projectLEMP
sudo chown -R $USER:$USER /var/www/projectLEMP
3οΈβ£ Configure Nginx for PHP Processing
- Create a new Nginx server block:
sudo nano /etc/nginx/sites-available/projectLEMP
Paste this configuration:
server {
listen 80;
server_name projectLEMP www.projectLEMP;
root /var/www/projectLEMP;
index index.php index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.ht {
deny all;
}
}
π‘ Breakdown
Line | Purpose |
---|---|
listen 80; |
Tells Nginx to listen for HTTP requests on port 80. |
server_name projectLEMP www.projectLEMP; |
Defines the domain names handled by this block. |
root /var/www/projectLEMP; |
Directory containing site files. |
index index.php index.html index.htm; |
Default files to serve. |
location / { try_files $uri $uri/ =404; } |
Tries to serve the file, else returns 404. |
location ~ \.php$ { ... } |
Configures PHP file handling via PHP-FPM. |
location ~ /\.ht { deny all; } |
Blocks .htaccess files for security. |
- Enable and link the configuration:
sudo ln -s /etc/nginx/sites-available/projectLEMP /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
π‘ Breakdown
Command | Purpose |
---|---|
ln -s ... |
Creates a symbolic link to enable the config. |
nginx -t |
Tests the Nginx configuration syntax. |
systemctl reload nginx |
Reloads Nginx without downtime. |
4οΈβ£ Test PHP Processing
- Create a test file:
sudo nano /var/www/projectLEMP/info.php
Add:
<?php
phpinfo();
?>
π‘ Breakdown
Code | Purpose |
---|---|
<?php ... ?> |
PHP opening and closing tags. |
phpinfo(); |
Outputs PHP configuration details. |
Visit http://your_server_ip/info.php
to confirm PHP is working.
5οΈβ£ MySQL Database Setup
- Login to MySQL:
sudo mysql -p
- Create DB, user, and grant privileges:
CREATE DATABASE example_database;
CREATE USER 'example_user'@'%' IDENTIFIED WITH mysql_native_password BY 'password';
GRANT ALL PRIVILEGES ON example_database.* TO 'example_user'@'%';
EXIT;
π‘ Breakdown
Command | Purpose |
---|---|
CREATE DATABASE ... |
Creates a new database. |
CREATE USER ... |
Creates a DB user with password. |
GRANT ALL PRIVILEGES ... |
Gives user full access to DB. |
6οΈβ£ Working with Your MySQL Database
Step 1: Login as Your Database User
mysql -u example_user -p
π‘ Breakdown
Command | Purpose |
---|---|
mysql |
Launches MySQL client. |
-u example_user |
Logs in as example_user . |
-p |
Prompts for your password. |
Step 2: Confirm Access to the Database
SHOW DATABASES;
π‘ Breakdown
Command | Purpose |
---|---|
SHOW DATABASES; |
Lists all available databases for the logged-in user. |
Step 3: Create a todo_list Table
CREATE TABLE example_database.todo_list (
item_id INT AUTO_INCREMENT,
content VARCHAR(255),
PRIMARY KEY(item_id)
);
π‘ Breakdown
Line | Purpose |
---|---|
CREATE TABLE ... |
Creates a new table. |
item_id INT AUTO_INCREMENT |
Adds a unique ID for each row. |
content VARCHAR(255) |
Stores text content up to 255 characters. |
PRIMARY KEY(item_id) |
Makes item_id the unique identifier. |
Step 4: Insert Sample Data
INSERT INTO example_database.todo_list (content) VALUES
('My first important item');
π‘ Breakdown
Part | Purpose |
---|---|
INSERT INTO ... (content) |
Specifies the table and column to insert data into. |
VALUES ('My first important item') |
Data to be stored in the content field. |
Step 5: View Data in the Table
SELECT * FROM example_database.todo_list;
π‘ Breakdown
Command | Purpose |
---|---|
SELECT * |
Retrieves all columns from the table. |
FROM example_database.todo_list |
Specifies the source table. |
Step 6: Exit MySQL
EXIT;
π‘ Breakdown
Command | Purpose |
---|---|
EXIT; |
Closes the MySQL client session. |
7οΈβ£ Retrieve Data with PHP (PDO Example)
Step 1: Create the PHP Script
sudo nano /var/www/projectLEMP/todo_list.php
π‘ Breakdown
Command | Purpose |
---|---|
nano /var/www/projectLEMP/todo_list.php |
Opens a new PHP file for editing in Nano text editor. |
Step 2: Add the PHP Code
<?php
$user = "example_user";
$password = "PassWord.1";
$database = "example_database";
$table = "todo_list";
try {
$db = new PDO("mysql:host=localhost;dbname=$database", $user, $password);
echo "<h2>TODO</h2><ol>";
foreach ($db->query("SELECT content FROM $table") as $row) {
echo "<li>" . $row['content'] . "</li>";
}
echo "</ol>";
} catch (PDOException $e) {
print "Error!: " . $e->getMessage() . "<br/>";
die();
}
?>
π‘ Breakdown
Line | Purpose |
---|---|
$user = "example_user"; |
Stores the database username. |
$password = "PassWord.1"; |
Stores the database password. |
$database = "example_database"; |
Stores the DB name. |
$table = "todo_list"; |
Stores the table name. |
new PDO("mysql:host=localhost;... |
Creates a database connection using PDO. |
foreach ($db->query(...)) |
Loops through each row of query results. |
echo "<li>" . $row['content'] . "</li>"; |
Outputs each item as an HTML list element. |
catch (PDOException $e) |
Handles any connection/query errors. |
Step 3: Test in Browser
Visit:
http://<EC2-Public-IP>/todo_list.php
If successful, youβll see your TODO list items displayed in a browser.
8οΈβ£ Final Testing
You now have a fully functional LEMP stack.
You can deploy web apps like WordPress, Laravel, or custom PHP apps.
π Common Troubleshooting Tips
If you hit snags during your LEMP setup, these quick checks can save hours:
- SSH Problems β Ensure correct key permissions (chmod 400), right username (ubuntu for Ubuntu AMIs), and valid public IP/Security Group rules. Remove outdated host keys with ssh-keygen -R.
- Connection Timeouts β Check route tables, Internet Gateway, and NACLs allow inbound/outbound traffic on required ports (22 for SSH, 80 for HTTP).
- Nginx/PHP Issues β Make sure index.php is in the index directive and that fastcgi_pass points to the correct PHP-FPM socket.
- MySQL Quirks β Close unbalanced quotes to avoid '> prompt, quit with \q if stuck, and inspect logs if MySQL fails to start.
- Windows/WSL Key Handling β Move .pem into WSL home before SSH, set proper permissions.
- Port 80 Timeouts β If curl localhost works but browser doesnβt, verify firewall rules, public IP, and that Nginx is listening on 0.0.0.0:80.
β Conclusion
Youβve:
- Installed Linux, Nginx, MySQL, and PHP.
- Configured Nginx for PHP-FPM.
- Created and secured a MySQL database.
- Verified PHP is working.
This process is production-ready with minor tweaks for SSL and security hardening.
Top comments (0)