Skip to main content

Installing a LEMP Stack on VDS (Ubuntu)

VDS / VPS · 18.02.2025 · 5 min read
Illustration for “Installing a LEMP Stack on VDS (Ubuntu)”

LEMP is the stack of Linux, Nginx (pronounced "engine-x", hence the E), MySQL, and PHP. Unlike LAMP with Apache, Nginx serves static files and proxies dynamic requests to PHP-FPM over a separate socket — that means lower memory use under many concurrent connections, and it requires explicitly configuring the bridge between Nginx and PHP.

Installing LEMP on Ubuntu 22.04

The order only matters formally — the components are independent, but it is convenient to install the web server first so you can immediately see that port 80 responds.

1. Installing Nginx

apt update
apt install nginx -y
systemctl enable nginx
systemctl start nginx

# Check: should return a Server: nginx header
curl -I http://localhost

2. Installing MySQL

apt install mysql-server -y
mysql_secure_installation

The mysql_secure_installation wizard sets a root password, disables anonymous users, blocks remote root login, and removes the test database — all four steps are worth confirming on a production server.

3. Installing PHP

apt install php8.2-fpm php8.2-mysql php8.2-curl php8.2-gd php8.2-mbstring php8.2-xml php8.2-zip -y

# Confirm the FPM daemon is running and listening on its socket
systemctl status php8.2-fpm
ls -l /run/php/php8.2-fpm.sock

Modules are chosen based on the task: php8.2-mysql is needed to connect to the database, php8.2-mbstring for correct UTF-8 handling in CMSs like WordPress, php8.2-gd for image processing. The exact module list for popular applications is usually listed in their documentation.

4. Configuring Nginx for PHP

Edit /etc/nginx/sites-available/default or create a separate config for the site:

server {
    listen 80;
    server_name yourdomain.com;
    root /var/www/yourdomain.com;
    index index.php index.html;

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

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }

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

The fastcgi_pass directive points to the PHP-FPM unix socket — the same path shown by the ls command in the previous step. If the socket has a different name (for example, a TCP port 127.0.0.1:9000 is set in the FPM pool), you need to specify that instead.

5. Restart Nginx

nginx -t && systemctl reload nginx

Checking the syntax with nginx -t before reload is not a formality: it tells apart a typo in the config from a real problem after a restart, when the site suddenly does not open.

Common Errors Explained

502 Bad Gateway — Nginx accepted the request but could not get a response from PHP-FPM. There are usually two causes: the socket in fastcgi_pass does not match the FPM socket's actual path, or the php8.2-fpm process itself is not running. Check with systemctl status php8.2-fpm and the log at /var/log/nginx/error.log, which will show a line like connect() to unix:/run/php/php8.2-fpm.sock failed (2: No such file or directory).

Downloading a PHP file instead of executing it in the browser means the location ~ \.php$ block did not fire — usually because of a typo in the regular expression or a missing fastcgi_params include.

Comparing LEMP and LAMP

ParameterLEMP (Nginx)LAMP (Apache)
Request handling modelEvent-driven, one process for thousands of connectionsProcesses or threads per connection (mod_php), or via FPM
Memory under many connectionsLower at high concurrencyHigher with mod_php, comparable when paired with FPM
.htaccessNot supported, rules move into the site's configSupported out of the box
Learning curveSteeper, needs explicit setup of the PHP bridgeGentler, mod_php works right away

When LEMP Does Not Fit

  • The application or its hosting instructions depend heavily on .htaccess for overriding rules at the directory level — Nginx has no such mechanism, all rules are centralized in the site's config.
  • The server already runs a control panel that generates its own Apache configuration (for example, a familiar cPanel setup) — mixing two web servers on the same port without explicit proxying is not a good idea.
  • The team is used to debugging through .htaccess and has no access to reload Nginx (for example on shared hosting) — LEMP assumes control over the server config, not just over the site's files.

Verifying the Result

  1. Create a test file info.php with the content <?php phpinfo(); in the site root and open it in a browser — it should display the PHP configuration page, not its source code.
  2. Delete info.php right after the check — this page reveals software versions and server paths, and must never stay in production.
  3. Check the PHP-to-MySQL connection: mysql -u root -p -e "SELECT VERSION();" and a matching query through a test PHP script using mysqli or PDO.
  4. Confirm all three services come back up automatically after a reboot: systemctl is-enabled nginx mysql php8.2-fpm should return enabled for each.

Further tuning of the virtual host, gzip, and static caching is covered in the article on configuring Nginx for a site on a VDS, and attaching a domain to TLS is covered in the article on installing SSL with Certbot.

Was this article helpful?
← Back to Knowledge Base Ask Support