How to install a LEMP stack on Ubuntu 24.04, step by step
Install a LEMP stack on Ubuntu 24.04 (and Debian 12): firewall, nginx, MariaDB, PHP-FPM, server blocks, Let's Encrypt SSL and per-site isolation done right.

Key takeaways
- Ubuntu 24.04 ships nginx 1.24, PHP 8.3 and MariaDB 10.11 in its official repositories.
- nginx hands PHP files to PHP-FPM through the Unix socket /run/php/php8.3-fpm.sock.
- Each website should have its own Linux user and PHP-FPM pool so one hacked site cannot read another.
- Always allow SSH in UFW before you enable the firewall.
To install a LEMP stack on Ubuntu 24.04, the official repositories are all you need: apt install nginx mariadb-server php8.3-fpm php8.3-mysql, run mariadb-secure-installation, then create an nginx server block that passes .php files to the /run/php/php8.3-fpm.sock socket. On a fresh VPS the whole job, including a free SSL certificate, takes about 20 minutes.
This guide walks through each step with commands checked on Ubuntu 24.04 (with notes for Debian 12), explains why each step exists, and covers the parts short tutorials skip: the firewall, file permissions and a separate PHP pool for every site. It ends with an honest look at when to do this by hand and when a control panel makes more sense.
What is LEMP, and why nginx instead of Apache?
LEMP is Linux, Engine-X (nginx), MariaDB (or MySQL) and PHP. Unlike LAMP with Apache and mod_php, nginx does not run PHP inside the web server. It serves static files very efficiently and hands PHP requests to PHP-FPM, a separate service that manages PHP workers.
That split has two practical benefits: nginx stays light on RAM with many concurrent connections, and each website can run PHP as its own Linux user. The trade-off: nginx ignores .htaccess, so rewrite rules live in the nginx configuration.
Which versions do you get when you install a LEMP stack on Ubuntu 24.04?
| Component | Ubuntu 24.04 | Debian 12 |
|---|---|---|
| nginx | 1.24 | 1.22 |
| PHP | 8.3 | 8.2 |
| MariaDB | 10.11 | 10.11 |
These branches stay fixed for the life of the release, with security fixes backported. Check exactly what you get with apt policy nginx mariadb-server php8.3-fpm. If you need several PHP versions side by side, you have to add a third-party repository (Ondřej Surý's PPA on Ubuntu, packages.sury.org on Debian); this guide sticks to the official repositories.
On Debian 12, replace every 8.3 in this guide with 8.2.
Step 1: Update the system and enable the firewall
Log in as a sudo user and run:
sudo apt update && sudo apt full-upgrade -y
Then enable UFW, allowing SSH first:
sudo apt install ufw
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status
If SSH listens on a port other than 22, use that number. Enabling UFW without the SSH rule is the fastest way to lock yourself out; only your provider's console can save you then. For the rest of a new server's hardening, see how to secure an Ubuntu VPS.
Step 2: Install nginx
sudo apt install nginx
sudo systemctl enable --now nginx
curl -I http://localhost
HTTP/1.1 200 OK means nginx is running. Opening http://your-server-ip in a browser shows the default welcome page.
Step 3: Install and secure MariaDB
sudo apt install mariadb-server
sudo mariadb-secure-installation
The script asks a few questions. On Ubuntu and Debian, MariaDB's root user authenticates with unix_socket by default: only the operating system's root (through sudo mariadb) can log in, with no password. That is a safe setup; keep it. Answer Y to removing anonymous users, disallowing remote root login and removing the test database.
MariaDB listens on 127.0.0.1 by default. Do not open port 3306 to the internet; for remote access, use an SSH tunnel or allow only your own IP.
Create a database and user for the site (one user per site):
sudo mariadb -e "CREATE DATABASE example_wp CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'example_wp'@'localhost' IDENTIFIED BY 'change-to-a-strong-password';
GRANT ALL PRIVILEGES ON example_wp.* TO 'example_wp'@'localhost';"
Step 4: Install PHP-FPM and the usual extensions
sudo apt install php8.3-fpm php8.3-mysql php8.3-curl php8.3-gd \
php8.3-mbstring php8.3-xml php8.3-zip php8.3-intl
sudo systemctl status php8.3-fpm
These extensions cover WordPress and most popular PHP applications. OPcache comes with PHP-FPM. Do not install the bare php8.3 package: on some systems it pulls in Apache and libapache2-mod-php, and Apache then fights nginx for port 80.
Adjust a few values in /etc/php/8.3/fpm/php.ini if your application needs them:
upload_max_filesize = 64M
post_max_size = 64M
memory_limit = 256M
Then sudo systemctl reload php8.3-fpm.
Step 5: Create an nginx server block
Create the site's folder and configuration file:
sudo mkdir -p /var/www/example.com/public
sudo nano /etc/nginx/sites-available/example.com
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com/public;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
location ~ /\.(?!well-known) {
deny all;
}
}
snippets/fastcgi-php.conf ships with the Ubuntu and Debian nginx packages and sets up the FastCGI parameters. The last block denies hidden files such as .env and .git while leaving .well-known available for Let's Encrypt.
Enable it and test:
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx
Always run nginx -t before reloading. One missing semicolon can stop nginx from restarting, taking every site on the server with it. The nginx documentation covers every directive used here.
Step 6: Check that PHP works
echo '<?php phpinfo();' | sudo tee /var/www/example.com/public/info.php
curl -s http://localhost/info.php -H 'Host: example.com' | grep -m1 'PHP Version'
sudo rm /var/www/example.com/public/info.php
Delete info.php right after the test: it exposes your server configuration to anyone.
Step 7: Free SSL with Let's Encrypt
Point the A records of example.com and www.example.com at the VPS, then:
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
sudo certbot renew --dry-run
Certbot adds HTTPS to the server block, redirects HTTP to HTTPS and sets up a renewal timer. See Certbot's instructions and our guide to free SSL with Let's Encrypt.
Step 8: Isolate sites when you host more than one
The configuration above runs PHP for every site as the same www-data user. For one site that is fine. For several, it is the biggest weakness of a hand-built LEMP server: a hacked plugin on site A can read site B's wp-config.php, because they share a user.
The fix is one Linux user and one PHP-FPM pool per site:
sudo adduser --system --group --no-create-home --home /var/www/example.com example
sudo chown -R example:example /var/www/example.com/public
sudo cp /etc/php/8.3/fpm/pool.d/www.conf /etc/php/8.3/fpm/pool.d/example.conf
In example.conf, change [www] to [example], set user = example, group = example and listen = /run/php/php8.3-fpm-example.sock, and keep listen.owner = www-data and listen.group = www-data so nginx can connect. Point the server block's fastcgi_pass at the new socket, then reload PHP-FPM and nginx. Set directory permissions to 750 and consider open_basedir per pool.
Doing this for ten sites is manageable. Doing it for a hundred, along with CPU and memory limits, disk quotas, backups and certificate renewals, is exactly what a control panel is for.
Build it yourself or use a control panel?
| Criterion | Hand-built LEMP | Control panel (e.g. ZoPanel) |
|---|---|---|
| Understanding of the system | Excellent | You rarely need to look |
| Time to add a site | 10–20 minutes | About a minute |
| Isolation between sites | Per pool, per user, by hand | Built in for every account |
| Several PHP versions | Add a repository, manage it yourself | Pick from a list |
| Backups, SSL, monitoring | Your own scripts and cron jobs | In the interface |
| Best for | 1–3 sites, people who want full control | Agencies, hosting providers, many sites |
ZoPanel installs this same stack: nginx, MariaDB, and PHP from Ondřej Surý's repositories (PHP 7.4 to 8.4), plus UFW, Fail2ban and chrooted SFTP. MariaDB's buffer pool is set to 20% of RAM. Every hosting account is its own Linux user with its own PHP-FPM pool and open_basedir, running in its own systemd slice with CPU, memory and process limits from its package. Pools of idle sites go to sleep to save memory. It installs on Ubuntu 22.04/24.04 or Debian 12/13 with one command:
curl -fsSL https://get.zopanel.net | sudo bash
The installer expects a fresh server with no other web server or panel, so do not run it on the machine you just built by hand. See the installation guide, optional components and websites and PHP versions.
Common LEMP problems
- 502 Bad Gateway: nginx cannot reach PHP-FPM. Check that
fastcgi_passnames the right socket (ls /run/php/) and that PHP-FPM is running. - The browser downloads .php files: the
location ~ \.php$block is missing, or nginx was not reloaded. - 404 on every page except the home page (WordPress, Laravel):
try_files ... /index.php?$argsis missing. - Access denied for user: wrong database password, or the user was created for a host other than
localhost. - Apache holds port 80: the bare
php8.3package was installed. Remove Apache withsudo apt purge apache2(after checking nothing else uses it). - Certbot fails validation: DNS does not point to the server yet, or your provider's firewall blocks port 80.
Conclusion
Installing LEMP on Ubuntu 24.04 is straightforward: the official repositories already carry nginx 1.24, PHP 8.3 and MariaDB 10.11, which cover most PHP applications today. The most valuable part of building it yourself is understanding how nginx, PHP-FPM and MariaDB fit together. The most expensive part is keeping many sites isolated, backed up and on valid certificates. When you reach that point, try ZoPanel's Free plan (3 hosting accounts, 10 websites, 10 databases, no time limit, multiple PHP versions included) on a fresh VPS, and see pricing when you need more.
Frequently asked questions
Which PHP version does Ubuntu 24.04 ship?
Ubuntu 24.04's official repositories ship PHP 8.3, as the php8.3-fpm package. Debian 12 ships PHP 8.2. For other versions you need a third-party repository such as Ondřej Surý's PPA.
Should I use MariaDB or MySQL in a LEMP stack?
Both run WordPress and most PHP applications well. MariaDB is in the Ubuntu and Debian repositories and is the default MySQL-compatible server on Debian, so it is simpler to install and update. Choose MySQL only when an application needs a MySQL-specific feature.
Why does nginx return 502 Bad Gateway after installing PHP?
Usually fastcgi_pass points at the wrong socket or PHP-FPM is not running. Run ls /run/php/ to see the real socket name and systemctl status php8.3-fpm to check the service, then fix the server block and reload nginx.
Do I need to open MariaDB's port 3306?
No, not when the site and database share a server. An open 3306 attracts constant password guessing. For remote access, use an SSH tunnel or allow only your own IP in the firewall.
Can I install a control panel on top of a hand-built LEMP server?
Most panels, ZoPanel included, expect a fresh server and check that no other web server is present, because the configurations would clash. Install the panel on a new VPS and move your sites over.
Install ZoPanel free
One command on a fresh Ubuntu or Debian server. Up and running in minutes.
Install in 5 minutesKeep reading

Deploy Laravel to a VPS: queues, scheduler and zero-downtime releases
How to deploy Laravel to a VPS on Ubuntu 24.04: PHP-FPM, nginx, .env, queue workers, the scheduler and zero-downtime releases, by hand or with ZoPanel.

Multiple PHP versions on one server: a PHP-FPM guide
Run multiple PHP versions on one server with PHP-FPM on Ubuntu or Debian: install side by side, set a version per site, handle CLI, cron and pitfalls.

Point domain to VPS: A records, nameservers and DNS checks
Point domain to VPS step by step: A, AAAA and www records, changing nameservers, keeping email working, checking with dig, and getting SSL right after.