A LEMP stack combines Linux, Nginx (pronounced "engine-x"), MySQL and PHP. Unlike Apache with mod_php, Nginx does not run PHP itself: it passes PHP requests to PHP-FPM over a Unix socket, which keeps memory use low and scales well under load. In this tutorial you will install Nginx, MySQL 8.0 and PHP-FPM 8.3 on Ubuntu 24.04, configure a server block for your domain and verify that PHP can query the database.

Prerequisites

To follow this tutorial you need:

  • A server running Ubuntu 24.04 LTS, for example a CubePath VPS, with at least 1 GB of RAM.
  • A non-root user with sudo privileges.
  • Optionally, a domain name with an A record pointing to your server's public IP. This tutorial uses your_domain as a placeholder. Without a domain, use your_server_ip instead.

Step 1 - Installing Nginx and allowing web traffic

Refresh the package index and install Nginx from Ubuntu's repositories:

sudo apt update
sudo apt install nginx

Nginx starts automatically after installation. Check that it is running:

sudo systemctl status nginx --no-pager
● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled)
     Active: active (running) since ...

If UFW is enabled, allow SSH and the Nginx Full profile, which opens ports 80 and 443:

sudo ufw allow OpenSSH
sudo ufw allow "Nginx Full"
sudo ufw enable

Verify the rules:

sudo ufw status
Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
Nginx Full                 ALLOW       Anywhere
...

Open http://your_server_ip in a browser. You should see the "Welcome to nginx!" page. From the server itself:

curl -sI http://localhost | head -n 1
HTTP/1.1 200 OK

Step 2 - Installing MySQL

Install MySQL server. Ubuntu 24.04 ships MySQL 8.0:

sudo apt install mysql-server

Confirm the service is active:

sudo systemctl is-active mysql
active

Run the security script to remove anonymous users, disallow remote root login and drop the test database:

sudo mysql_secure_installation

Answer Y to those questions. You can also enable the password validation component, which rejects weak passwords for new users. On Ubuntu the MySQL root account uses the auth_socket plugin, so the script skips setting a root password: you log in as MySQL root with sudo mysql and no password.

Check that the console opens:

sudo mysql -e "SELECT VERSION();"
+-------------------------+
| VERSION()               |
+-------------------------+
| 8.0.x-0ubuntu0.24.04.x  |
+-------------------------+

Step 3 - Installing PHP-FPM

Install PHP-FPM and the MySQL extension. Note that you install php-fpm rather than the php meta package: on Ubuntu, php pulls in Apache and mod_php, which you don't want on an Nginx server.

sudo apt install php-fpm php-mysql

Add the extensions that most applications expect:

sudo apt install php-curl php-gd php-mbstring php-xml php-zip php-intl

Ubuntu 24.04 installs PHP 8.3. Check the CLI version and the FPM service:

php -v
sudo systemctl status php8.3-fpm --no-pager
PHP 8.3.6 (cli) (built: ...) (NTS)
...
● php8.3-fpm.service - The PHP 8.3 FastCGI Process Manager
     Loaded: loaded (/usr/lib/systemd/system/php8.3-fpm.service; enabled; preset: enabled)
     Active: active (running) since ...

PHP-FPM listens on a Unix socket that Nginx will use. Confirm it exists:

ls -l /run/php/
-rw-r--r-- 1 root     root      ... php8.3-fpm.pid
srw-rw---- 1 www-data www-data  0 ... php8.3-fpm.sock
lrwxrwxrwx 1 root     root     ... php-fpm.sock -> /etc/alternatives/php-fpm.sock

The socket is owned by www-data, the same user Nginx runs as, so Nginx can connect to it without further changes.

Step 4 - Configuring an Nginx server block

Create a document root for your site and give your user ownership of it:

sudo mkdir -p /var/www/your_domain
sudo chown -R $USER:$USER /var/www/your_domain

Create a new server block file:

sudo nano /etc/nginx/sites-available/your_domain

Add this configuration, replacing your_domain:

server {
    listen 80;
    listen [::]:80;
    server_name your_domain www.your_domain;

    root /var/www/your_domain;
    index index.php index.html;

    access_log /var/log/nginx/your_domain.access.log;
    error_log /var/log/nginx/your_domain.error.log;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    }

    location ~ /\.ht {
        deny all;
    }
}

What each part does:

  • try_files serves a file or directory if it exists and otherwise hands the request to index.php, the front controller pattern used by WordPress, Laravel and Symfony.
  • snippets/fastcgi-php.conf ships with Ubuntu's Nginx package. It sets SCRIPT_FILENAME and returns 404 for PHP files that don't exist, so arbitrary paths are never sent to PHP.
  • fastcgi_pass points to the PHP-FPM socket from Step 3.
  • The last block hides any .htaccess files left over from Apache-oriented projects.

Enable the site by linking it into sites-enabled, and remove the default site so it does not answer for your domain:

sudo ln -s /etc/nginx/sites-available/your_domain /etc/nginx/sites-enabled/
sudo unlink /etc/nginx/sites-enabled/default

Test the configuration and reload Nginx:

sudo nginx -t
sudo systemctl reload nginx
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Step 5 - Testing PHP with Nginx

Create a test file in the document root:

nano /var/www/your_domain/info.php

Add:

<?php phpinfo();

Open http://your_domain/info.php. The page should show Server API: FPM/FastCGI. From the command line:

curl -s http://your_domain/info.php | grep -o 'FPM/FastCGI' | head -n 1
FPM/FastCGI

Delete the file afterwards, because it reveals details about your server:

rm /var/www/your_domain/info.php

Step 6 - Testing the database connection from PHP

Create a database and a dedicated user. Open the MySQL console:

sudo mysql

Run the following statements, replacing your_strong_password with a strong password of your own:

CREATE DATABASE example_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'example_user'@'localhost' IDENTIFIED BY 'your_strong_password';
GRANT ALL PRIVILEGES ON example_db.* TO 'example_user'@'localhost';
USE example_db;
CREATE TABLE todo_list (id INT AUTO_INCREMENT PRIMARY KEY, content VARCHAR(255));
INSERT INTO todo_list (content) VALUES ('Install Nginx'), ('Install MySQL'), ('Install PHP-FPM');
EXIT;

Create a PHP script that reads the table:

nano /var/www/your_domain/todo.php

Add the following code with the same password:

<?php
$pdo = new PDO(
    'mysql:host=localhost;dbname=example_db;charset=utf8mb4',
    'example_user',
    'your_strong_password',
    [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);

foreach ($pdo->query('SELECT id, content FROM todo_list') as $row) {
    echo $row['id'] . '. ' . $row['content'] . PHP_EOL;
}

Request the page:

curl http://your_domain/todo.php
1. Install Nginx
2. Install MySQL
3. Install PHP-FPM

The stack is working end to end. Remove the script, which contains a password:

rm /var/www/your_domain/todo.php

Step 7 - Enabling HTTPS with Let's Encrypt (optional)

With your domain pointing to the server, install Certbot and its Nginx plugin:

sudo apt install certbot python3-certbot-nginx

Request a certificate. Certbot updates the server block with the certificate paths and an HTTP to HTTPS redirect:

sudo certbot --nginx -d your_domain -d www.your_domain

Renewal runs automatically from a systemd timer. Confirm it works with a dry run:

sudo certbot renew --dry-run

Troubleshooting

  • 502 Bad Gateway on PHP pages. Nginx cannot reach PHP-FPM. Check that php8.3-fpm is running (sudo systemctl status php8.3-fpm) and that the socket path in fastcgi_pass matches the file in /run/php/. The exact error appears in /var/log/nginx/your_domain.error.log.
  • PHP files are downloaded instead of executed. The request did not match the location ~ \.php$ block. Make sure the block is inside the right server and that you reloaded Nginx.
  • nginx -t reports a duplicate default server. Two server blocks use default_server. Remove the default site link as shown in Step 4.
  • Access denied for user from PDO. The database credentials in the script don't match the user you created. Reset the password from sudo mysql with ALTER USER.

Conclusion

You installed Nginx, MySQL 8.0 and PHP-FPM 8.3 on Ubuntu 24.04, configured a server block that passes PHP requests to PHP-FPM and confirmed that PHP can read from the database. Next, you can tune PHP-FPM pools for your workload, install an application such as WordPress with WP-CLI, or run several PHP versions side by side for different sites.