A LEMP stack is a web-hosting platform made of Linux, Engine‑x (Nginx), MariaDB/MySQL and PHP. This guide sets up a production‑ready LEMP server on Ubuntu 24.04 LTS — installing Nginx, MariaDB and PHP 8.3‑FPM, wiring Nginx to PHP over a Unix socket, securing the host with a firewall and free TLS, and tuning the PHP‑FPM pool.
Heads‑up: this article was rewritten for 2026. Do not install PHP 7 — PHP 7.4 reached end of life on 28 November 2021 and receives no security patches. Use PHP 8.3 or 8.4, the branches still under active or security support.
Key takeaways
- LEMP = Linux + Nginx + MariaDB/MySQL + PHP — Nginx replaces Apache as the web server and reverse proxy.
- Nginx does not embed PHP. It forwards
.phprequests to PHP‑FPM over a Unix socket (/run/php/php8.3-fpm.sock). - Ubuntu 24.04's default repositories ship PHP 8.3; add the
ondrej/phpPPA only if you need the very latest (8.4+). - The two config lines that matter are
include snippets/fastcgi-php.conf;andfastcgi_pass unix:/run/php/php8.3-fpm.sock;. - Use
try_files $uri $uri/ /index.php?$query_string;for front‑controller apps (WordPress, Laravel, Symfony). - Always lock down with ufw, add Let's Encrypt TLS, and delete any
info.phptest file before going live.
What is a LEMP stack and what does each part do?
LEMP is the Nginx‑based sibling of the classic LAMP stack. The "E" is pronounced from Engine‑X. Each component owns a clear job:
| Component | Role | Common package on Ubuntu 24.04 |
|---|---|---|
| Linux | Operating system | Ubuntu 24.04 LTS (Noble Numbat) |
| Nginx | Web server, static file serving, reverse proxy, TLS termination | nginx |
| MariaDB / MySQL | Relational database | mariadb-server (or mysql-server) |
| PHP‑FPM | FastCGI process manager that executes PHP | php8.3-fpm |
Nginx serves static assets directly and hands dynamic .php requests to PHP‑FPM, which runs your application code and returns the response.
Why choose Nginx + PHP-FPM over Apache + mod_php?
Apache traditionally embedded PHP in‑process with mod_php, spawning a process or thread per connection. Nginx uses an event‑driven model and offloads PHP to a separate FPM pool, which usually means lower memory use under load and cleaner separation between the web server and the runtime.
| Aspect | Nginx + PHP‑FPM | Apache + mod_php |
|---|---|---|
| Concurrency model | Event‑driven, non‑blocking | Process/thread per request (prefork) |
| Memory under load | Lower, predictable | Higher with many idle keep‑alives |
| Static file serving | Very fast | Fast |
| PHP execution | Out‑of‑process (FPM pool) | In‑process module |
.htaccess support |
No (config in server blocks) | Yes |
The trade‑off: there is no .htaccess, so all rewrites and access rules live in the Nginx server block.
Which PHP version should you install in 2026?
PHP follows a predictable cadence: each minor release gets roughly two years of active bug fixes plus one more year of security‑only fixes, then it is end of life (EOL). Running an EOL version means no security patches — a real risk for an internet‑facing server.
| PHP version | Status in 2026 | Recommendation |
|---|---|---|
| 8.4 | Current stable, active support | Latest features, longest runway |
| 8.3 | Supported (Ubuntu 24.04 default) | Safe default for most sites |
| 8.2 | Security‑only fixes | Plan an upgrade |
| 8.1 | End of life (Dec 2025) | Upgrade now |
| 7.x and older | End of life since Nov 2021 | Never deploy |
For a fresh Ubuntu 24.04 server, install PHP 8.3 from the default repos, or use the ondrej/php PPA to get PHP 8.4.
How do you install the LEMP stack on Ubuntu 24.04?
Start by updating the package index and installing Nginx.
# Update the system
sudo apt update && sudo apt upgrade -y
# Install the Nginx web server
sudo apt install nginx -yNext install PHP‑FPM and the extensions most apps need. PHP 8.3 is in Ubuntu 24.04's default repositories. If you want the newest branch (8.4), add the well‑maintained ondrej/php PPA first.
# Option A: PHP 8.3 from Ubuntu's default repos
sudo apt install php8.3-fpm php8.3-mysql php8.3-cli \
php8.3-curl php8.3-mbstring php8.3-xml php8.3-zip php8.3-gd -y
# Option B: newest PHP (8.4) via the ondrej/php PPA
sudo add-apt-repository ppa:ondrej/php -y
sudo apt update
sudo apt install php8.4-fpm php8.4-mysql php8.4-cli -y
# Confirm the version and that the FPM service is running
php -v
systemctl status php8.3-fpmNow install the database. MariaDB is a drop‑in, MySQL‑compatible default on Ubuntu; substitute mysql-server if you prefer Oracle MySQL. Run the interactive hardening script to set the root password and remove insecure defaults.
# Install MariaDB
sudo apt install mariadb-server -y
# Harden the install (set root auth, drop anonymous users & test DB)
sudo mysql_secure_installationCreate a dedicated database and an application user — never let your app connect as root.
sudo mysql
CREATE DATABASE app_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'use-a-strong-password';
GRANT ALL PRIVILEGES ON app_db.* TO 'app_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;How do you configure the Nginx server block for PHP-FPM?
This is the part the old guide got wrong. Nginx must pass .php requests to the PHP‑FPM Unix socket and set SCRIPT_FILENAME correctly. Put your code under /var/www/example.com/public, then create a server block in /etc/nginx/sites-available/.
sudo nano /etc/nginx/sites-available/example.com.confserver {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com/public;
index index.php index.html;
# Front-controller routing for WordPress, Laravel, Symfony, etc.
location / {
try_files $uri $uri/ /index.php?$query_string;
}
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;
}
# Deny access to hidden files such as .env or .htaccess
location ~ /\. {
deny all;
}
}A few notes on the directives above:
include snippets/fastcgi-php.conf;ships with the Ubuntu Nginx package and supplies the standard FastCGI parameters and a safefastcgi_split_path_info.fastcgi_passmust match your PHP version's socket — for PHP 8.4 it is/run/php/php8.4-fpm.sock.try_files $uri $uri/ /index.php?$query_string;is what makes pretty URLs work in modern PHP frameworks.
Enable the site, test the syntax, and reload Nginx.
# Enable the site and remove the default placeholder
sudo ln -s /etc/nginx/sites-available/example.com.conf /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
# Test config, then reload (reload is zero-downtime vs restart)
sudo nginx -t
sudo systemctl reload nginxHow do you test that PHP-FPM is working?
Drop a temporary info.php into your web root and load it in a browser. It prints the full PHP configuration — useful, but it also leaks internals, so delete it the moment you're done.
# Create a temporary probe
echo "<?php phpinfo();" | sudo tee /var/www/example.com/public/info.php
# Visit http://example.com/info.php — you should see the PHP info page.
# IMPORTANT: remove it immediately afterwards
sudo rm /var/www/example.com/public/info.phpHow do you secure the stack with a firewall and TLS?
Lock the firewall down to SSH and web traffic, then issue a free Let's Encrypt certificate with Certbot. For the full walkthrough see our guide on configuring SSL with Let's Encrypt and Nginx.
# Firewall: allow SSH + Nginx (HTTP/HTTPS), then enable
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
# Free TLS certificate + auto-renewal via Certbot
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d example.com -d www.example.comNeed to restrict a staging environment? See HTTP basic password authentication with Nginx. Moving old URLs? Our note on 301 redirections with Nginx covers clean redirects.
How do you tune the PHP-FPM pool?
The default pool is conservative. Edit /etc/php/8.3/fpm/pool.d/www.conf to match your traffic and RAM. With pm = dynamic, PHP‑FPM keeps a pool of workers between the spare bounds and scales up to pm.max_children. Size pm.max_children to (available RAM ÷ average worker memory).
; /etc/php/8.3/fpm/pool.d/www.conf
pm = dynamic
pm.max_children = 20
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6
pm.max_requests = 500# Apply pool changes
sudo systemctl restart php8.3-fpmYour Ubuntu server now serves a hardened, TLS‑secured LEMP stack on a supported PHP version. If you also run WordPress, see hosting a WordPress blog as a sub‑directory alongside Django in Nginx.
Keeping a Linux server patched, monitored and backed up is ongoing work. MicroPyramid has run production infrastructure since 2014 across 50+ projects — if you'd rather hand it off, explore our server maintenance services.
Frequently Asked Questions
What does LEMP stand for?
LEMP stands for Linux, Engine‑x (Nginx), MariaDB/MySQL and PHP. It is the Nginx‑based counterpart to the LAMP stack, which uses Apache instead of Nginx.
Can I still install PHP 7 on Ubuntu in 2026?
You shouldn't. PHP 7.4 — the last 7.x release — reached end of life on 28 November 2021 and gets no security fixes. Install PHP 8.3 (Ubuntu 24.04's default) or PHP 8.4 instead.
Why does Nginx need PHP-FPM?
Unlike Apache's mod_php, Nginx cannot execute PHP itself. It forwards .php requests over FastCGI to PHP‑FPM, a separate process manager that runs your PHP code and returns the result. They communicate over the Unix socket /run/php/php8.3-fpm.sock.
Should I use a Unix socket or a TCP port for fastcgi_pass?
For a single server where Nginx and PHP‑FPM run on the same host, a Unix socket (unix:/run/php/php8.3-fpm.sock) is slightly faster and is the Ubuntu default. Use a TCP address like 127.0.0.1:9000 only when PHP‑FPM runs on a different host or inside a separate container.
MariaDB or MySQL — which should I pick?
For most LEMP sites MariaDB is the pragmatic default: it is the MySQL‑compatible package shipped by Ubuntu and works as a drop‑in replacement. Choose Oracle MySQL if a specific application or feature requires it.
How many PHP-FPM workers (pm.max_children) should I set?
Size it by memory: divide the RAM you can dedicate to PHP by the average memory a single worker uses (often 30–60 MB). For example, ~1 GB free at ~50 MB per worker is roughly pm.max_children = 20. Watch your logs for "server reached pm.max_children" warnings and adjust.