
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
You have a hardened Ubuntu box and now you need somewhere to actually serve a site. A LEMP stack on Ubuntu — Linux, Nginx, MySQL, and PHP — is the standard foundation for WordPress, Laravel, and most PHP applications, and it is fast, lean, and free. This guide installs each piece on Ubuntu 24.04 in the right order, wires Nginx to PHP 8.4 through a FastCGI socket, secures MySQL 8, and verifies the whole chain with a throwaway phpinfo() page. If you have not locked the server down yet, start with my initial Ubuntu server setup guide first, then come back here. Prefer to hand it off? My DevOps and cloud services cover provisioning end to end.
.php requests to the PHP-FPM socket via fastcgi_pass, run mysql_secure_installation, then confirm it works with a temporary phpinfo page you delete afterwards.What is a LEMP stack and why use it on Ubuntu?
LEMP stands for Linux, (E)Nginx, MySQL, and PHP — the "E" is the phonetic spelling of Nginx ("engine-x"). It is the same idea as the classic LAMP stack, but it swaps Apache for Nginx, which handles high concurrency with a smaller memory footprint because it uses an event-driven model instead of one process per connection. On Ubuntu 24.04 LTS every component installs straight from the official repositories, so you get security patches for years without third-party PPAs.
The key architectural difference from LAMP is how PHP runs. Nginx has no embedded PHP module; instead it forwards dynamic requests to a separate PHP-FPM (FastCGI Process Manager) service over a Unix socket. That separation is a feature — Nginx stays lean and fast at serving static assets, while PHP-FPM manages its own worker pool independently.
How do you install Nginx, MySQL, and PHP on Ubuntu 24.04?
Start from an updated package index, then install the three services. Ubuntu 24.04 ships PHP 8.3 by default, but PHP 8.4 is available cleanly through the well-maintained ondrej/php PPA, which is the standard source most teams use in 2026:
sudo apt update && sudo apt -y upgrade
sudo apt install -y nginx mysql-server
sudo add-apt-repository -y ppa:ondrej/php
sudo apt update
sudo apt install -y php8.4-fpm php8.4-mysql php8.4-cli php8.4-curl php8.4-mbstring php8.4-xml php8.4-zip That gives you Nginx, MySQL 8, and PHP 8.4-FPM plus the extensions most applications need. Confirm each service is installed and running before wiring them together:
systemctl status nginx --no-pager
systemctl status mysql --no-pager
systemctl status php8.4-fpm --no-pager All three should report active (running). If you have the UFW firewall enabled from your initial hardening, open the web ports so Nginx is reachable — leave the MySQL port closed to the internet:
sudo ufw allow 'Nginx Full'
sudo ufw status The Nginx Full profile opens both port 80 (HTTP) and 443 (HTTPS). Visiting your server's IP in a browser should now show the default Nginx welcome page.
How do you secure MySQL 8 after installing it?
A fresh MySQL 8 install has no root password and a few insecure defaults left over for convenience. The bundled mysql_secure_installation script fixes them interactively:
sudo mysql_secure_installation Work through the prompts and answer as follows for a production server:
- Validate password component — enable it (choose
Y) and pick at least theMEDIUMpolicy so weak passwords are rejected. - Remove anonymous users — yes; anonymous accounts let anyone connect without credentials.
- Disallow root login remotely — yes; root should only ever connect from localhost.
- Remove the test database — yes; it is world-writable by default and serves no purpose in production.
- Reload privilege tables — yes, so every change takes effect immediately.
On Ubuntu, the MySQL root user authenticates through the auth_socket plugin by default, meaning you log in with sudo mysql and no password. That is fine for administration, but create a dedicated application user rather than letting your app connect as root:
sudo mysql
CREATE DATABASE appdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'appuser'@'localhost' IDENTIFIED BY 'a-strong-password-here';
GRANT ALL PRIVILEGES ON appdb.* TO 'appuser'@'localhost';
FLUSH PRIVILEGES;
EXIT; Scoping the grant to appdb.* and binding the user to localhost means a leaked application credential cannot touch other databases or connect from another host.
How do you configure an Nginx server block to run PHP?
An Nginx server block (Apache calls it a virtual host) defines how one site is served. Create a document root, then a config file that serves static files directly and passes .php requests to the PHP-FPM socket. First the web root:
sudo mkdir -p /var/www/example.com/html
sudo chown -R www-data:www-data /var/www/example.com Now the server block. The critical line is fastcgi_pass, which points Nginx at the PHP 8.4-FPM Unix socket:
sudo tee /etc/nginx/sites-available/example.com > /dev/null <<'EOF'
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com/html;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
}
location ~ /\.ht {
deny all;
}
}
EOF Enable the site by symlinking it into sites-enabled, test the configuration syntax, and reload Nginx. Always run nginx -t before reloading — it catches typos before they take the site down:
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx Here is what each block does:
rootandindex— set the document root and the files Nginx tries as a directory index.location /— thetry_filesfallback to/index.phpis what makes front-controller frameworks like Laravel and WordPress route correctly.location ~ \.php$— matches PHP files and forwards them over FastCGI to PHP-FPM.location ~ /\.ht— denies access to hidden Apache-style files that occasionally get copied in.
fastcgi_pass directive is the single line that connects the Nginx server block to the PHP-FPM socket — mismatch the socket path and PHP files download instead of running.How do you test that PHP works with Nginx?
With everything wired, confirm the full chain end to end. Create a temporary info.php in the document root that calls phpinfo():
echo "<?php phpinfo(); ?>" | sudo tee /var/www/example.com/html/info.php Visit http://your-server-ip/info.php (or the domain if DNS is pointed). You should see the PHP 8.4 information page. Scroll down and confirm the mysqli and PDO sections are present — that proves PHP can talk to MySQL. If instead the browser downloads the file, the location ~ \.php$ block or the socket path is wrong.
The phpinfo page exposes your exact versions, module list, and paths, so delete it the moment the test passes — leaving it live is a real information-disclosure risk:
sudo rm /var/www/example.com/html/info.php Your LEMP stack is now live and serving PHP. The natural next steps are adding a real application and a TLS certificate. For a framework deployment, follow my guide to deploying Laravel on an Ubuntu VPS with Nginx, and to encrypt traffic see how to set up free SSL with Let's Encrypt and Certbot.
Conclusion
A LEMP stack on Ubuntu 24.04 comes down to four moves: install Nginx, MySQL 8, and PHP 8.4-FPM from apt; secure MySQL with mysql_secure_installation and a scoped application user; write a server block whose fastcgi_pass points at the PHP-FPM socket; then verify with a phpinfo page you delete straight after. Do it in that order and you have a fast, patchable foundation for almost any PHP application. Want your stack provisioned, tuned, and monitored for production? See real deployments in my DevOps portfolio or contact me to set it up for you.