How to Install a LEMP Stack on Ubuntu 24.04 (Step by Step)

How to Install a LEMP Stack on Ubuntu 24.04
To install a LEMP stack on Ubuntu 24.04, you install Nginx, MySQL, and PHP-FPM from the default Ubuntu repositories with apt, then configure Nginx to pass PHP requests to PHP-FPM through a Unix socket. The whole process takes four commands to get the software on disk and one short config file to wire it together. Ubuntu 24.04 LTS (Noble Numbat) ships current, well-tested versions of all three components, so you don't need any third-party PPAs for a standard setup.
This guide walks through every step on a fresh Ubuntu 24.04 server: installing each component, securing MySQL, connecting Nginx to PHP-FPM, and verifying the stack end to end with a test PHP page. Every command is shown exactly as you should type it, and we explain what each one changes on your system.
What you need before starting:
- A server (VPS or local VM) running Ubuntu 24.04 LTS
- A non-root user with
sudoprivileges - SSH access to the server
If you're still deciding between web servers, our comparison of Nginx vs Apache covers the trade-offs — the short version is that Nginx's event-driven model and lighter memory footprint make it the usual choice for a new stack, which is exactly why the "E" in LEMP replaced the "A" in LAMP.
What Is a LEMP Stack?
A LEMP stack is a set of open-source software for serving dynamic websites: Linux (the operating system), Engine-X — Nginx (the web server), MySQL (the database), and PHP (the application language). Nginx accepts HTTP requests, hands PHP files to the PHP-FPM process manager for execution, and PHP talks to MySQL for data. It's the stack behind a huge share of WordPress, Laravel, and custom PHP deployments.
On Ubuntu 24.04, the default repositories provide:
| Component | Package | Version in Ubuntu 24.04 repos |
|---|---|---|
| Web server | nginx |
1.24.x |
| Database | mysql-server |
MySQL 8.0.x |
| Language | php8.3-fpm |
PHP 8.3 |
These versions are maintained with security patches by Ubuntu for the life of the LTS release, which is why we recommend the default repos over third-party sources unless you specifically need a newer PHP.
Step 1 — Update the System and Install Nginx
Start by refreshing the package index and applying any pending updates:
sudo apt update
sudo apt upgrade -y
Then install Nginx:
sudo apt install nginx -y
Ubuntu starts and enables the Nginx service automatically on install. Confirm it's running:
systemctl status nginx
You should see active (running). Press q to exit the status view.
Open the Firewall
Ubuntu 24.04 ships with ufw available (and on many cloud images, enabled). Allow HTTP traffic — and SSH, so you don't lock yourself out:
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx HTTP'
sudo ufw enable
If ufw was already enabled, skip the last command. The 'Nginx HTTP' profile opens port 80 only; you'll open 443 later when you add HTTPS with Let's Encrypt.
Now visit your server's IP address in a browser (http://your_server_ip). You should see the default "Welcome to nginx!" page. If you don't know the IP, ip addr on the server or your provider's dashboard will show it.
Step 2 — Install MySQL
Install the MySQL server package:
sudo apt install mysql-server -y
The service starts automatically. Verify:
systemctl status mysql
Secure the MySQL Installation
MySQL ships with a helper script that tightens the default configuration:
sudo mysql_secure_installation
The script asks a series of yes/no questions:
- VALIDATE PASSWORD component — optional; it enforces password strength rules for MySQL accounts. Fine to enable on a production box.
- Remove anonymous users — yes.
- Disallow root login remotely — yes.
- Remove test database — yes.
- Reload privilege tables — yes.
One Ubuntu-specific detail worth knowing: on Ubuntu, the MySQL root account authenticates via the auth_socket plugin by default, meaning you log in with sudo mysql rather than a password. This is more secure for local administration than a password, and we recommend leaving it that way. If the secure-installation script prompts you to set a root password, you can do so, but the socket-based login continues to work for the root system user.
Create an Application Database and User
Don't let your applications connect as root. Log in and create a dedicated database and user (replace the names and choose a strong password):
sudo mysql
CREATE DATABASE example_db;
CREATE USER 'example_user'@'localhost' IDENTIFIED BY 'choose_a_strong_password';
GRANT ALL PRIVILEGES ON example_db.* TO 'example_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;
Test the new account:
mysql -u example_user -p
Enter the password when prompted, then EXIT; to leave.
Step 3 — Install PHP-FPM
With Apache, PHP typically runs inside the web server process. Nginx works differently: it doesn't execute PHP itself, so you install PHP-FPM (FastCGI Process Manager), a separate service that runs PHP and communicates with Nginx over a socket.
Install PHP-FPM plus the MySQL extension so PHP can talk to your database:
sudo apt install php8.3-fpm php8.3-mysql -y
On Ubuntu 24.04, php-fpm resolves to PHP 8.3; we name the version explicitly so the commands below (which reference the versioned socket path) match exactly. Check the service:
systemctl status php8.3-fpm
Most real applications need a few more extensions. Common ones for WordPress, Laravel, and similar apps:
sudo apt install php8.3-curl php8.3-gd php8.3-mbstring php8.3-xml php8.3-zip -y
You can always add extensions later; after installing one, restart PHP-FPM with sudo systemctl restart php8.3-fpm.
Step 4 — Configure Nginx to Use PHP-FPM
This is the step that turns three separate services into a stack. You'll create an Nginx server block — the Nginx equivalent of an Apache virtual host — that serves files from a directory and passes anything ending in .php to PHP-FPM.
First, create a web root for your site and give your user ownership:
sudo mkdir -p /var/www/example
sudo chown -R $USER:$USER /var/www/example
Now create the server block file:
sudo nano /etc/nginx/sites-available/example
Paste this configuration, replacing example.com with your domain (or your server's IP if you don't have a domain yet):
server {
listen 80;
server_name example.com www.example.com;
root /var/www/example;
index index.php index.html;
location / {
try_files $uri $uri/ =404;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
location ~ /\.ht {
deny all;
}
}
What each part does:
root— the directory Nginx serves files from.index index.php index.html;— tryindex.phpfirst when a directory is requested, which is what PHP applications expect.try_files $uri $uri/ =404;— serve the file if it exists, otherwise return 404 instead of guessing.location ~ \.php$— matches requests for.phpfiles. The includedsnippets/fastcgi-php.conf(shipped with Ubuntu's Nginx package) sets the standard FastCGI parameters, andfastcgi_passsends the request to PHP-FPM's Unix socket at/run/php/php8.3-fpm.sock.location ~ /\.ht— blocks access to.htaccess-style files. Nginx never uses them, but if you migrate files from Apache they shouldn't be downloadable.
Enable the site by symlinking it into sites-enabled, and remove the default site so it doesn't conflict:
sudo ln -s /etc/nginx/sites-available/example /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
Always test before reloading. This habit will save you at some point:
sudo nginx -t
If you see syntax is ok and test is successful, apply the change:
sudo systemctl reload nginx
If the test fails, it prints the file and line number of the problem — fix it and test again. Nginx keeps running with the old config until a reload succeeds, so a failed test never takes your site down.
This single-site setup is all you need today, but the same mechanism scales to many sites on one server — see our full guide to Nginx server blocks for hosting multiple domains, choosing server_name values, and how sites-available and sites-enabled relate.
Step 5 — Test the Stack End to End
Create a PHP file in your web root:
nano /var/www/example/info.php
Add:
<?php
phpinfo();
Visit http://your_server_ip/info.php (or your domain). You should see the PHP information page showing PHP 8.3, with the Server API reported as FPM/FastCGI. That confirms the full chain: Nginx received the request, passed it to PHP-FPM, and PHP executed it.
Test the Database Connection
To confirm PHP can reach MySQL, replace the contents of info.php with a quick connection test using the user you created in Step 2:
<?php
$mysqli = new mysqli("localhost", "example_user", "choose_a_strong_password", "example_db");
if ($mysqli->connect_error) {
die("Connection failed: " . $mysqli->connect_error);
}
echo "Connected to MySQL successfully.";
Reload the page. "Connected to MySQL successfully." means all three layers are talking.
Now delete the test file. A phpinfo() page exposes detailed server configuration to anyone who finds it, and a file containing database credentials should never sit in a web root:
rm /var/www/example/info.php
Troubleshooting Common Issues
- 502 Bad Gateway — Nginx can't reach PHP-FPM. Check that the service is running (
systemctl status php8.3-fpm) and that the socket path infastcgi_passmatches your PHP version exactly. A config written forphp8.1-fpm.sockon a system running PHP 8.3 produces exactly this error. - Browser downloads the .php file instead of running it — the PHP
locationblock isn't matching, usually because the server block being used isn't the one you edited. Confirm your symlink exists insites-enabledand the default site is removed, thensudo nginx -t && sudo systemctl reload nginx. - 403 Forbidden — Nginx can't read the files, or no index file exists in the directory. Check file permissions and that
index.phporindex.htmlis present. - Site unreachable from outside — firewall. Run
sudo ufw statusand confirmNginx HTTPis allowed; on cloud providers, also check the provider-level firewall or security group.
Next Steps
You have a working LEMP stack, but a production server needs two more things before it hosts anything real:
- HTTPS. Serving over plain HTTP is no longer acceptable for anything with logins or forms. Follow our Let's Encrypt with Certbot tutorial to get a free, auto-renewing TLS certificate — it takes about ten minutes on the stack you just built, and Certbot will edit this same server block for you.
- Deploy an application. Point the
rootdirective at your application's public directory, install any PHP extensions it requires, and give it the database credentials from Step 2.
The stack you've built — Nginx 1.24, MySQL 8.0, PHP 8.3 on Ubuntu 24.04 LTS — is supported with security updates through the standard LTS window, so it's a foundation you can leave in place for years. Keep it patched with a periodic sudo apt update && sudo apt upgrade, and test config changes with nginx -t before every reload. Those two habits prevent most of the outages that beginner-run servers suffer.