How to Set Up a LEMP Stack on Ubuntu 24.04

Serdar Tekin
Serdar Tekin
Co-Founder & CTO
BeginnerUpdated Oct 27, 202616 min read~30 minutes total
LEMPUbuntu 24.04NginxMariaDBPHP-FPMLinux VMWeb Server
Last reviewed:
Tutorial
How to Set Up a LEMP Stack on Ubuntu 24.04
rafftechnologies.com/learn
On this page

Prerequisites

  • A Raff Linux VM running Ubuntu 24.04 with at least 2 GB RAM, SSH access, a non-root user with sudo privileges, and an optional domain pointed to the VM

Don’t have a server yet?

A LEMP stack combines Linux, Nginx, MariaDB or MySQL, and PHP-FPM to run PHP websites and applications. On Ubuntu 24.04, you can build the stack from the standard Ubuntu repositories without adding third-party package sources. This tutorial sets up a production-oriented LEMP stack on a Raff Linux VM, creates a dedicated local database user, connects Nginx to PHP-FPM, verifies PHP execution, adds a basic firewall and HTTPS path, and finishes with end-to-end checks and cleanup instructions.

The current Ubuntu 24.04 package line uses PHP 8.3, and Ubuntu's Noble package repository provides MariaDB 10.11. You can verify those package baselines in the official Ubuntu PHP package listing and Ubuntu MariaDB package listing. This guide uses MariaDB, but the same LEMP architecture can use MySQL when your application requires it.

You need an Ubuntu 24.04 server with at least 2 GB RAM, SSH access, and a non-root user with sudo privileges. A domain is optional for the initial stack test but required for the HTTPS step.

Step 1 — Update Ubuntu and Confirm the Server Baseline

Update package metadata and install available updates:

Bash
sudo apt update sudo apt upgrade -y

Check the operating system release:

Bash
cat /etc/os-release

If the upgrade reports that a reboot is required, reboot and reconnect before continuing:

Bash
sudo reboot

Verify: /etc/os-release should identify Ubuntu 24.04, and sudo apt update should complete without repository errors.

Step 2 — Install and Start Nginx

Install Nginx:

Bash
sudo apt install -y nginx

Enable it at boot and start it now:

Bash
sudo systemctl enable --now nginx

Check the service and installed version:

Bash
systemctl is-active nginx nginx -v

If UFW is already active, allow HTTP and HTTPS traffic only after confirming that your SSH rule is present:

Bash
sudo ufw status sudo ufw allow OpenSSH sudo ufw allow 'Nginx Full'

Verify: systemctl is-active nginx should return active, and curl -I http://127.0.0.1 should return an HTTP response from Nginx.

Step 3 — Install and Secure MariaDB

Install MariaDB from the Ubuntu repositories:

Bash
sudo apt install -y mariadb-server

Enable and start the service:

Bash
sudo systemctl enable --now mariadb

Run MariaDB's security helper:

Bash
sudo mariadb-secure-installation

For a single-server LEMP stack, keep local administrative access protected, remove anonymous users and the test database, and avoid unnecessary remote root access. Applications should never connect using the MariaDB root account.

Check the database version:

Bash
sudo mariadb -e "SELECT VERSION();"

Do not expose MariaDB port 3306 publicly for this architecture.

Verify: systemctl is-active mariadb should return active, and the version query should return a MariaDB 10.11 release from the Ubuntu 24.04 package line.

Step 4 — Install PHP-FPM and Common PHP Extensions

Install PHP-FPM, the CLI, MySQL/MariaDB support, and common PHP modules:

Bash
sudo apt install -y \ php-fpm \ php-cli \ php-mysql \ php-curl \ php-gd \ php-intl \ php-mbstring \ php-xml \ php-zip \ php-bcmath

Ubuntu 24.04's standard PHP branch is 8.3. Check the CLI version and PHP-FPM service:

Bash
php -v systemctl is-active php8.3-fpm

Confirm the PHP-FPM Unix socket exists:

Bash
ls -l /run/php/php8.3-fpm.sock

If you deliberately install another PHP branch later, update both the service name and Nginx socket path consistently.

Verify: php -v should report PHP 8.3.x on the standard Ubuntu 24.04 package line, php8.3-fpm should be active, and /run/php/php8.3-fpm.sock should exist.

Step 5 — Create a Dedicated Database and Application User

Generate a strong password and save it in a password manager:

Bash
openssl rand -hex 24

Open MariaDB:

Bash
sudo mariadb

Create an application database and local-only user. Replace use_a_unique_password with the generated password:

Sql
CREATE DATABASE example_app CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'example_user'@'localhost' IDENTIFIED BY 'use_a_unique_password'; GRANT ALL PRIVILEGES ON example_app.* TO 'example_user'@'localhost'; FLUSH PRIVILEGES; EXIT;

Test the application account:

Bash
mariadb -u example_user -p example_app

Then run:

Sql
SELECT DATABASE(), CURRENT_USER(); EXIT;

Verify: The query should show example_app and example_user@localhost. Keep port 3306 closed to the public internet.

Step 6 — Create the Website Directory

This tutorial uses example.com. Replace it with your domain if you have one.

Create the document root:

Bash
sudo mkdir -p /var/www/example.com/public sudo chown -R "$USER":www-data /var/www/example.com sudo find /var/www/example.com -type d -exec chmod 750 {} \; sudo find /var/www/example.com -type f -exec chmod 640 {} \;

Create a basic HTML page:

Bash
cat <<'EOF' | sudo tee /var/www/example.com/public/index.html >/dev/null <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>LEMP stack ready</title> </head> <body> <h1>Nginx is serving the LEMP site</h1> </body> </html> EOF

Verify: Run ls -ld /var/www/example.com/public and confirm that the directory exists, then confirm index.html is present with ls -l /var/www/example.com/public/index.html.

Step 7 — Configure the Nginx Server Block for PHP-FPM

Create the site configuration:

Bash
sudo nano /etc/nginx/sites-available/example.com

Add:

Nginx
server { listen 80; listen [::]:80; server_name example.com www.example.com; root /var/www/example.com/public; index index.php index.html; access_log /var/log/nginx/example.com.access.log; error_log /var/log/nginx/example.com.error.log; location / { try_files $uri $uri/ =404; } location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php8.3-fpm.sock; } location ~ /\.(?!well-known).* { deny all; } }

Enable the site and remove the default site symlink:

Bash
sudo ln -s /etc/nginx/sites-available/example.com \ /etc/nginx/sites-enabled/example.com sudo rm -f /etc/nginx/sites-enabled/default

Test before reloading:

Bash
sudo nginx -t sudo systemctl reload nginx

Never reload Nginx after a failed nginx -t check.

Verify: sudo nginx -t should report successful syntax, and this request should return the site response:

Bash
curl -I http://127.0.0.1 -H 'Host: example.com'

Step 8 — Verify PHP Processing Without Leaving phpinfo() Exposed

Create a temporary PHP test file:

Bash
cat <<'EOF' | sudo tee /var/www/example.com/public/health.php >/dev/null <?php header('Content-Type: text/plain'); echo "php-fpm-ok\n"; EOF

Test it locally through Nginx:

Bash
curl http://127.0.0.1/health.php -H 'Host: example.com'

You should see:

Text
php-fpm-ok

Delete the temporary PHP file immediately after verification:

Bash
sudo rm -f /var/www/example.com/public/health.php

This avoids leaving a phpinfo() page or other diagnostic endpoint exposed.

Verify: The PHP request should return php-fpm-ok, and test ! -f /var/www/example.com/public/health.php && echo removed should print removed after cleanup.

Step 9 — Verify PHP Can Reach MariaDB

Create a temporary database connectivity test outside the public document root. Use mktemp so the file is created with restrictive permissions instead of leaving a database password in a normally world-readable temporary file:

Bash
DB_TEST="$(mktemp /tmp/lemp-db-test.XXXXXX.php)" chmod 600 "$DB_TEST" cat > "$DB_TEST" <<'PHP' <?php $pdo = new PDO( 'mysql:host=localhost;dbname=example_app;charset=utf8mb4', 'example_user', 'use_a_unique_password', [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION] ); echo "database-ok\n"; PHP

Replace use_a_unique_password with the application database password, then verify the file permissions before running it:

Bash
stat -c '%a %U:%G %n' "$DB_TEST" php "$DB_TEST"

The file mode should be 600, and PHP should print:

Text
database-ok

Remove the temporary file immediately:

Bash
rm -f "$DB_TEST" unset DB_TEST

Because the test runs outside the public web root and the temporary file is restricted to its owner, the database credential is not intentionally exposed over HTTP or to unrelated local users.

Verify: PHP prints database-ok, the temporary file was mode 600 while it existed, and the file is removed after the test.

Step 10 — Add HTTPS When the Domain Points to the VM

If you have a public domain, verify every hostname you plan to include in the certificate before running Certbot:

Bash
dig +short A example.com dig +short A www.example.com dig +short AAAA example.com dig +short AAAA www.example.com

Only keep hostnames that resolve to this VM. If you do not use www.example.com, remove it from the Nginx server_name and from the Certbot request.

Install Certbot and its Nginx plugin:

Bash
sudo apt install -y certbot python3-certbot-nginx

Request a certificate and redirect HTTP to HTTPS for the hostnames that resolve correctly:

Bash
sudo certbot --nginx --redirect \ -d example.com \ -d www.example.com

If you use only the apex hostname, request only -d example.com. Certbot validation must succeed for every hostname in the request.

Check the certificate and renewal process:

Bash
sudo certbot certificates sudo certbot renew --dry-run

For certificate troubleshooting, use How to Secure Nginx with Let's Encrypt on Ubuntu 24.04.

Verify: curl -I https://example.com should complete over HTTPS, and curl -I http://example.com should redirect to HTTPS after Certbot updates the Nginx configuration.

Step 11 — Run the Final End-to-End LEMP Verification

Check all three services:

Bash
systemctl is-active nginx mariadb php8.3-fpm

Test the site locally through the configured hostname:

Bash
curl -I http://127.0.0.1 -H 'Host: example.com'

Check recent service and site errors:

Bash
sudo journalctl -u nginx -n 30 --no-pager sudo journalctl -u mariadb -n 30 --no-pager sudo journalctl -u php8.3-fpm -n 30 --no-pager sudo tail -n 30 /var/log/nginx/example.com.error.log

Confirm MariaDB is not intentionally exposed through UFW:

Bash
sudo ufw status

For production workloads, measure actual PHP-FPM worker memory and database usage before tuning process limits. A fixed pm.max_children value is not portable across WordPress, Laravel, Drupal, custom PHP applications, and different plugin or request profiles.

Verify: Nginx, MariaDB, and PHP-FPM should all be active; the configured site should return an HTTP response; logs should not show a repeating fatal configuration error; and port 3306 should not have a public firewall rule.

Step 12 — Clean Up or Roll Back the LEMP Test Site

If you are keeping the stack, there is nothing else to remove after the temporary PHP and database test files from Steps 8 and 9 are deleted.

If this was only a test and you want to remove the example site, first disable the Nginx site:

Bash
sudo rm -f /etc/nginx/sites-enabled/example.com sudo nginx -t sudo systemctl reload nginx

Remove the example files only if they are no longer needed:

Bash
sudo rm -rf /var/www/example.com sudo rm -f /etc/nginx/sites-available/example.com

Drop the test database and user:

Bash
sudo mariadb

Then run:

Sql
DROP DATABASE example_app; DROP USER 'example_user'@'localhost'; EXIT;

If you issued a Let's Encrypt certificate specifically for this retired test domain, inspect certificates before deleting anything:

Bash
sudo certbot certificates

Then delete only the certificate that belongs to the retired hostname:

Bash
sudo certbot delete --cert-name example.com

Verify: After a full rollback, the Nginx site symlink and document root should be gone, and SHOW DATABASES LIKE 'example_app'; should return no test database.

Conclusion

You now have a complete LEMP stack on Ubuntu 24.04 with Nginx serving the application, PHP-FPM handling PHP requests, and MariaDB using a dedicated local application account. The stack has explicit Nginx, PHP, database, firewall, HTTPS, end-to-end verification, and rollback paths rather than stopping after package installation.

Use the Linux VM product linked in the introduction and current Raff pricing to choose resources for this self-managed stack. A 2 GB VM can be an initial small-test reference; production needs depend on PHP-FPM concurrency, MariaDB memory, background jobs, plugins, and traffic. To decide whether this operating model suits your workload, read VPS for Web Hosting: What Small Teams Should Know. This tutorial runs MariaDB on the VM; managed versus self-hosted databases explains a separate operating model, not an automatically available managed MariaDB SKU. For a complete PHP application example, continue with WordPress on Ubuntu 24.04 with Nginx.

Deploy a Raff Ubuntu Linux VM to run Nginx, PHP-FPM and MariaDB under your own administration. Size the VM for application traffic and database load, then configure HTTPS and a tested backup path.

Was this article helpful?
Serdar Tekin
Serdar Tekin
Co-Founder & CTO

Co-Founder & CTO at Raff, focused on cloud infrastructure, systems architecture, security, reliability, and production operations.

Put it into practice

Spin up a Raff server, follow the steps above on real infrastructure, and you are live. Linux or Windows, backed by a 14-day money-back guarantee.

Frequently asked questions

What does LEMP stand for?
LEMP stands for Linux, Engine-X (Nginx), MariaDB or MySQL, and PHP. Together they provide the operating system, web server, database, and PHP runtime for dynamic web applications.
How much RAM does a LEMP stack need?
A small LEMP workload can start with 2 GB RAM. Use more memory as PHP concurrency, MariaDB activity, background jobs, frameworks, plugins, or traffic increase.
What PHP version does Ubuntu 24.04 use for a standard LEMP stack?
Ubuntu 24.04's standard PHP package line is PHP 8.3. If you deliberately install another PHP branch, update the PHP-FPM service name and Nginx Unix socket path consistently.
Should MariaDB port 3306 be open publicly?
No for a single-server LEMP stack. Keep MariaDB local and open remote database access only when a deliberately designed private or otherwise secured architecture requires it.
Should applications connect as the MariaDB root user?
No. Create a dedicated database user for each application and grant privileges only to the database that application needs.
How much does a Raff VM for a LEMP stack cost?
Raff VM pricing can change, so check the Raff pricing page for current Linux VM sizes and billing terms. For Nginx, PHP-FPM, and MariaDB on one VM, start with at least 2 GB RAM and size for the application's real concurrency, database load, and traffic.