How to Set Up a LEMP Stack on Ubuntu 24.04

Beginner
Updated Mar 24, 202615 min read~30 minutes total
Ubuntu
Nginx
MariaDB
PHP
Web Server
LEMP

On This Page

Prerequisites

A Raff VM running Ubuntu 24.04 with at least 2 vCPU and 4 GB RAM (CPU-Optimized Tier 3 or higher), SSH access configured, a non-root user with sudo privileges (initial server setup)

Don't have a server yet? Deploy a Raff VM in 60 seconds.

Deploy a VM

Introduction

A LEMP stack is one of the most popular open-source web server configurations for hosting dynamic websites and web applications. LEMP stands for Linux, Engine-X (Nginx), MariaDB (or MySQL), and PHP — four components that work together to serve, process, and store web content.

Nginx handles incoming HTTP requests and serves static files with exceptional speed. When a request requires dynamic processing — like loading a WordPress page or running a Laravel application — Nginx forwards it to PHP-FPM, which executes the PHP code. PHP communicates with MariaDB to read and write data, then returns the generated HTML to Nginx, which delivers it to the visitor's browser.

In this tutorial, you will install and configure each component of the LEMP stack on your Raff Ubuntu 24.04 VM. You will set up Nginx as the web server, install MariaDB with a secure initial configuration, install PHP-FPM with common extensions, and verify the complete stack by serving a PHP info page. By the end, you will have a production-ready foundation for hosting PHP-based applications.

Step 1 — Updating the System and Installing Nginx

Start by updating the package index and upgrading installed packages to their latest versions. This ensures you get the most recent software versions and security patches.

bashsudo apt update && sudo apt upgrade -y

Install Nginx from the default Ubuntu repositories:

bashsudo apt install -y nginx

Nginx starts automatically after installation. Verify it is running:

bashsudo systemctl status nginx

You should see active (running) in the output. If you have configured UFW on your server, allow HTTP and HTTPS traffic:

bashsudo ufw allow 'Nginx Full'

Open your server's IP address in a browser:

http://your_server_ip

You should see the default Nginx welcome page. This confirms Nginx is installed, running, and accessible from the internet.

Check the installed Nginx version:

bashnginx -v

Ubuntu 24.04 ships with Nginx 1.24.x or later from its default repositories.

Step 2 — Installing MariaDB

MariaDB is a community-developed fork of MySQL that offers improved performance and additional features while maintaining full MySQL compatibility. Most PHP applications that support MySQL also support MariaDB without any configuration changes.

Install MariaDB server:

bashsudo apt install -y mariadb-server

Verify MariaDB is running:

bashsudo systemctl status mariadb

Run the security script to remove default insecure settings:

bashsudo mysql_secure_installation

The script will prompt you through several security questions. Here are the recommended answers:

  • Enter current password for root: Press Enter (no password is set by default)
  • Switch to unix_socket authentication: Type Y — this is more secure than password-based root login
  • Change the root password: Type Y and set a strong password
  • Remove anonymous users: Type Y
  • Disallow root login remotely: Type Y
  • Remove test database and access to it: Type Y
  • Reload privilege tables now: Type Y

Verify you can connect to MariaDB:

bashsudo mariadb

You should see the MariaDB monitor prompt. Check the version:

sqlSELECT VERSION();

Expected output will show MariaDB 10.11.x or later. Type EXIT; to leave the MariaDB shell.

Step 3 — Installing PHP-FPM and Common Extensions

PHP-FPM (FastCGI Process Manager) is the recommended way to run PHP with Nginx. Unlike Apache's mod_php, PHP-FPM runs as a separate process pool that Nginx communicates with via a socket or TCP connection. This architecture is more efficient and allows you to fine-tune PHP process management independently.

Install PHP-FPM and the extensions most commonly required by PHP applications:

bashsudo apt install -y php-fpm php-mysql php-curl php-gd php-intl php-mbstring php-xml php-zip php-bcmath php-cli php-common

Here is what each extension provides:

  • php-fpm — The FastCGI Process Manager
  • php-mysql — MariaDB/MySQL database connectivity
  • php-curl — HTTP client for API calls
  • php-gd — Image processing (thumbnails, resizing)
  • php-intl — Internationalization functions
  • php-mbstring — Multi-byte string handling (UTF-8 support)
  • php-xml — XML parsing and generation
  • php-zip — ZIP archive handling
  • php-bcmath — Arbitrary precision mathematics

Check the installed PHP version:

bashphp -v

Ubuntu 24.04 ships with PHP 8.3.x. Verify PHP-FPM is running:

bashsudo systemctl status php8.3-fpm

Note

If your PHP version differs (e.g., 8.4), adjust the version number in all subsequent commands and configuration paths. Check with php -v to confirm your exact version.

Step 4 — Configuring Nginx to Process PHP

Create an Nginx server block configuration for your site. This example uses example.com as the domain — replace it with your actual domain name or your server's IP address.

Create the web root directory:

bashsudo mkdir -p /var/www/example.com
sudo chown -R www-data:www-data /var/www/example.com
sudo chmod -R 755 /var/www/example.com

Create the Nginx server block:

bashsudo nano /etc/nginx/sites-available/example.com

Paste the following configuration:

nginxserver {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;
    root /var/www/example.com;
    index index.php index.html index.htm;

    location / {
        try_files $uri $uri/ =404;
    }

    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;
    }

    location ~ /\.ht {
        deny all;
    }

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    # Logging
    access_log /var/log/nginx/example.com.access.log;
    error_log /var/log/nginx/example.com.error.log;
}

Key configuration details:

  • fastcgi_pass unix:/run/php/php8.3-fpm.sock — Connects Nginx to PHP-FPM via a Unix socket, which is faster than TCP for same-server communication
  • location ~ /\.ht — Blocks access to .htaccess files, which are Apache-specific and should not be publicly accessible
  • The security headers protect against common web vulnerabilities like clickjacking and content-type sniffing

Enable the server block by creating a symbolic link:

bashsudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/

Optionally remove the default server block to avoid conflicts:

bashsudo rm /etc/nginx/sites-enabled/default

Test the Nginx configuration for syntax errors:

bashsudo nginx -t

If the test passes, reload Nginx:

bashsudo systemctl reload nginx

Step 5 — Creating a Test Database and Database User

Production applications should never use the MariaDB root account. Create a dedicated database and user for your application.

Log in to MariaDB:

bashsudo mariadb

Create a database:

sqlCREATE DATABASE example_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

The utf8mb4 character set supports the full Unicode range, including emojis. Always use utf8mb4 instead of utf8 for new databases.

Create a user with a strong password and grant permissions:

sqlCREATE USER 'example_user'@'localhost' IDENTIFIED BY 'your_strong_password_here';
GRANT ALL PRIVILEGES ON example_db.* TO 'example_user'@'localhost';
FLUSH PRIVILEGES;

The @'localhost' restriction means this user can only connect from the local machine, adding a layer of security.

Verify the user can connect:

sqlEXIT;
bashmariadb -u example_user -p example_db

Enter the password when prompted. If you see the MariaDB prompt, the user and database are configured correctly. Type EXIT; to leave.

Step 6 — Testing PHP Processing and Database Connectivity

Create a PHP test file to verify that Nginx correctly passes requests to PHP-FPM:

bashsudo nano /var/www/example.com/info.php

Add the following content:

php<?php
phpinfo();

Open http://your_server_ip/info.php in your browser. You should see the PHP information page displaying your PHP version, loaded modules, and configuration details.

Verify the following on the PHP info page:

  • Server API should show FPM/FastCGI
  • mysqli and pdo_mysql should appear in the loaded modules
  • mbstring, curl, gd, and xml should be listed

Now test database connectivity. Create a PHP file that connects to MariaDB:

bashsudo nano /var/www/example.com/db_test.php

Add the following content:

php<?php
$host = 'localhost';
$db   = 'example_db';
$user = 'example_user';
$pass = 'your_strong_password_here';

try {
    $pdo = new PDO("mysql:host=$host;dbname=$db;charset=utf8mb4", $user, $pass);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    echo "Database connection successful. MariaDB version: " . $pdo->query('SELECT VERSION()')->fetchColumn();
} catch (PDOException $e) {
    echo "Connection failed: " . $e->getMessage();
}

Open http://your_server_ip/db_test.php in your browser. You should see a success message with the MariaDB version number.

bashsudo rm /var/www/example.com/info.php /var/www/example.com/db_test.php

Step 7 — Tuning PHP-FPM for Production

The default PHP-FPM configuration is conservative. For production workloads on a Raff VM with 4 GB RAM, adjust the process manager settings.

Open the PHP-FPM pool configuration:

bashsudo nano /etc/php/8.3/fpm/pool.d/www.conf

Find and modify these directives:

inipm = dynamic
pm.max_children = 20
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 10
pm.max_requests = 500
  • pm.max_children — Maximum number of PHP worker processes. Each process uses approximately 30-50 MB of RAM. With 4 GB total and other services running, 20 is a reasonable limit.
  • pm.max_requests — Recycles worker processes after 500 requests to prevent memory leaks from accumulating.

Also adjust PHP settings for better performance:

bashsudo nano /etc/php/8.3/fpm/php.ini

Modify these values:

iniupload_max_filesize = 64M
post_max_size = 64M
memory_limit = 256M
max_execution_time = 300
max_input_vars = 5000

Restart PHP-FPM to apply the changes:

bashsudo systemctl restart php8.3-fpm

Verify PHP-FPM restarted without errors:

bashsudo systemctl status php8.3-fpm

Conclusion

You have set up a complete LEMP stack on your Raff Ubuntu 24.04 VM with Nginx serving web content, MariaDB managing databases with a secure configuration, PHP 8.3 with FPM processing dynamic requests, and production-ready tuning for both PHP-FPM and Nginx.

This stack is the foundation for hosting PHP applications including WordPress, Laravel, Drupal, Magento, and many others. From here, you can:

  • Install WordPress on top of your LEMP stack
  • Secure your site with HTTPS using Let's Encrypt
  • Add Redis caching for improved application performance

Raff's NVMe SSD storage gives your LEMP stack fast disk I/O for database queries and file serving, while AMD EPYC processors handle PHP processing efficiently. With hourly billing, you can test your configuration and scale up when traffic grows.

Get notified when we publish new tutorials

Cloud tips, step-by-step guides, and infrastructure insights — straight to your inbox.

Frequently Asked Questions

Ready to get started?

Deploy an Ubuntu 24.04 VM and follow along in under 60 seconds.

Deploy a VM Now