Hardware FixRecommendedDevice not working? Your driver may be the problemCheck updates for common hardware issues.Fix DriversOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsPC HealthRecommendedCrashes, freezes, slowdowns? Check your PC nowSpot repairable issues before they interrupt work.Check PC×
Skip to content
VGSources
Blog

How to Set Up a LEMP Stack With Nginx on an Ubuntu or Debian Linux VPS

A practical, release-aware walkthrough for installing Nginx, PHP-FPM, MariaDB or MySQL, and UFW on a Linux VPS, then configuring a PHP site, HTTPS, permissions, backups, and diagnostics.
Length11 min Posted Quest giverVGSources Team
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A LEMP stack combines Linux, Nginx, a MySQL-compatible database such as MariaDB or MySQL, and PHP running through PHP-FPM. On a fresh Ubuntu or Debian VPS, the reliable setup is: create a sudo-enabled account, install the distribution packages, verify the PHP-FPM socket, build an Nginx server block, permit only the required ports, harden the database, test each layer, and then add HTTPS.

This guide uses commands that fit current Debian-family systems, but package names, service names, PHP versions, and socket paths vary by release. Check the values installed on your server instead of copying a historical path such as php7.2-fpm.sock.

As an Amazon Associate I earn from qualifying purchases.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What a LEMP stack does

LEMP is shorthand for Linux, Nginx, MySQL or MariaDB, and PHP. Nginx handles connections and static files, then forwards requests for PHP scripts to PHP-FPM using FastCGI. PHP executes application code and uses the database for persistent data. A typical request travels through these layers:

  1. A browser resolves your domain to the VPS address and connects on TCP port 80 or 443.
  2. Nginx selects the matching server block by the requested host name.
  3. Nginx serves images, CSS, JavaScript, and other static files directly.
  4. A request ending in .php is passed to the local PHP-FPM Unix socket.
  5. The PHP application connects to MariaDB or MySQL through a local database account and returns a response.

Keeping Nginx, PHP-FPM, and the database on one VPS is simple and inexpensive for a small site, but it also means one machine carries every role. Plan backups, monitoring, and recovery before putting an important application online.

Prerequisites and decisions

Server access and DNS

  • Use a fresh Ubuntu or Debian VPS with SSH access.
  • Log in with an SSH key and a non-root account that has sudo privileges. Keep a root console or provider recovery method available while changing SSH and firewall settings.
  • Point a domain or fully qualified domain name, such as example.com, at the VPS public IPv4 address. Add an IPv6 record only when the VPS is configured to accept IPv6 traffic.
  • Know which provider-level firewall controls exist. Both that network firewall and the VPS’s host firewall must allow the ports you intend to use.

Choose a database and package source

MariaDB and MySQL implement the SQL interfaces used by most PHP applications, but they are separate projects with different release policies and administrative preferences. Pick one, use its matching service name, and test the application’s compatibility before deployment. Distribution packages are the simplest starting point because dependencies and security updates follow Ubuntu or Debian. An official Nginx repository can provide a different release cadence, but it adds another package source to maintain.

Ubuntu or Debian?

Both are suitable. Ubuntu releases often provide newer packages in a given support window, while Debian emphasizes a conservative, stable base. The exact PHP major version, package names, and FPM socket are release-specific, so the commands below deliberately discover local values rather than assuming a version.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Install Nginx, the database, PHP-FPM, and UFW

1. Connect and update the operating system

ssh [email protected]
sudo apt update
sudo apt full-upgrade -y

Reboot if the package manager installs a kernel or another component that requires it, then reconnect over SSH. Do not continue with a stale package index.

2. Install the web, database, PHP, and firewall packages

For a MariaDB-based installation, the package set is:

sudo apt install -y ufw nginx mariadb-server php-fpm php-mysql

php-mysql supplies the PHP extensions commonly used to connect to MySQL-compatible servers. If you selected MySQL instead, install the distribution’s MySQL server package and retain the same PHP extension unless your application specifies another driver.

3. Enable and start services

Some packages start automatically. Explicitly enabling them makes the desired boot behavior clear:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
sudo systemctl enable --now nginx
sudo systemctl enable --now mariadb
sudo systemctl enable --now php*-fpm

The shell wildcard may match the installed PHP-FPM unit on some distributions and may be rejected on others. If it does not work, list the units and enable the exact one:

systemctl list-unit-files | grep -E 'php.*fpm|mariadb|mysql|nginx'
sudo systemctl enable --now php8.3-fpm

Replace php8.3-fpm with the unit actually installed. Check every service:

systemctl status nginx --no-pager
systemctl status mariadb --no-pager
systemctl status php8.3-fpm --no-pager

Find the real PHP-FPM socket

Nginx must send FastCGI requests to the socket created by your installed PHP-FPM package. Never infer the path from an old tutorial. Discover it locally:

ls -l /run/php/
systemctl status php8.3-fpm --no-pager
grep -R "^listen =" /etc/php/*/fpm/pool.d/www.conf

Typical output is a Unix socket such as /run/php/php8.3-fpm.sock, but the version and directory can differ. Copy the exact path into the Nginx configuration later. If the service status shows a different pool file or a TCP listener, use that locally reported endpoint instead.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Configure UFW without locking yourself out

Allow SSH first

Permit the SSH profile before enabling UFW. Otherwise an active SSH session can survive while every new connection is blocked:

sudo ufw allow 'OpenSSH'
sudo ufw status

If SSH runs on a nonstandard port, allow that port explicitly, for example sudo ufw allow 2222/tcp, and verify the provider firewall permits it too.

Open web traffic

sudo ufw allow 'Nginx HTTP'
sudo ufw allow 'Nginx HTTPS'
sudo ufw enable
sudo ufw status verbose

Use the HTTPS rule when you plan to serve TLS. If your release does not provide the named profiles, allow 80/tcp and 443/tcp directly. Keep the default policy restrictive and do not expose the database port to the public internet unless remote database access is an explicit architectural requirement.

Harden MariaDB or MySQL

Run the vendor hardening assistant

Run the utility supplied by your database package:

sudo mysql_secure_installation

Follow its prompts to remove anonymous accounts, restrict unnecessary remote access, remove test data, and apply the administrator authentication choice appropriate to your release. Prompt wording differs between MariaDB and MySQL versions; read each question rather than blindly accepting a historical answer.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Create an application database and user

Do not configure a web application with the database administrator account. Create a dedicated database and account with only the privileges the application needs. The exact SQL varies with your database version and application schema, so run the vendor-supported commands for your chosen server and store the credentials outside the web root. Keep the database listening locally by default. If a separate application server must connect remotely, restrict the listener and firewall rule to that server’s address and require encrypted transport.

Create the site directory and permissions

Create a document root for the domain. The example below uses /var/www/example.com:

sudo mkdir -p /var/www/example.com
sudo chown -R "$USER":www-data /var/www/example.com
sudo find /var/www/example.com -type d -exec chmod 755 {} ;
sudo find /var/www/example.com -type f -exec chmod 644 {} ;

These are a starting point, not a universal permission policy. The web worker should be able to read application files, while writable directories should be limited to the specific paths an application documents. Do not make the entire site writable by the web worker and do not place database dumps, private keys, or environment files in a publicly served directory.

Build an Nginx server block for PHP

Write the configuration

Create /etc/nginx/sites-available/example.com:

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

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

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

    location ~ /.ht {
        deny all;
    }
}

Replace example.com, the document root, and the socket with your own values. The distribution’s snippets/fastcgi-php.conf file supplies standard FastCGI parameters and commonly protects against malformed script paths. If your distribution stores the snippet elsewhere, locate the package-provided FastCGI configuration and use that path.

Understand the important directives

  • listen binds the site to HTTP on IPv4 and, when configured, IPv6.
  • server_name determines which host name selects this block.
  • root defines the directory from which static files and scripts are served.
  • index makes PHP or HTML index files candidates for a directory request.
  • try_files $uri $uri/ =404; serves an existing file or directory and returns a 404 instead of forwarding arbitrary paths to PHP.
  • The PHP location includes the distribution FastCGI settings and sends requests to the locally verified Unix socket.
  • The deny rule prevents legacy .htaccess files from being downloaded if they exist.

Enable the site and test syntax

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

Only reload after nginx -t reports successful syntax and configuration checks:

sudo systemctl reload nginx

Disable the default site when it would answer requests intended for your domain:

sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx

On a multi-site VPS, keep one server block per domain, give each site a distinct document root, and ensure every server_name is unique. A single site can use the same pattern without the organizational overhead.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Verify each layer before deploying an application

Test Nginx and static content

Create a simple file and request headers locally or through the domain:

echo 'LEMP is running' | sudo tee /var/www/example.com/index.html
curl -I http://example.com

Expect an HTTP response from Nginx. A timeout usually indicates DNS, the provider firewall, UFW, or a routing problem rather than PHP.

Test PHP-FPM

Create a temporary diagnostic page:

printf '%sn' '<?php phpinfo();' | sudo tee /var/www/example.com/info.php

Open http://example.com/info.php. A PHP information page proves that Nginx reached PHP-FPM, but it also reveals versions and environment details. Delete it immediately after testing:

sudo rm /var/www/example.com/info.php

If the browser downloads the PHP source instead of executing it, the PHP location block, FastCGI include, or socket directive is missing or not selected for that request.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Test the database connection

Use a small application script or the application’s own installer to connect with the dedicated database account. Confirm that it can create and read the tables it needs, then remove any diagnostic script and credentials from the document root. A successful command-line login alone does not prove that PHP has the required extension or that the web user can reach the database.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Add HTTPS after DNS resolves

Wait until the domain resolves to the VPS and port 80 is reachable. Install and run a certificate-authority client such as Certbot using the package and Nginx integration supported by your distribution. The client should obtain a certificate, update the Nginx configuration, and optionally create an HTTP-to-HTTPS redirect. Ensure UFW and the provider firewall allow TCP 443.

After issuance, test both the HTTPS site and the renewal process. A certificate that works today is not sufficient if automatic renewal cannot complete. Keep the HTTP listener available for the challenge method you select or use the renewal method documented by your certificate client.

Troubleshoot common failures

Symptom Likely cause Checks and fix
Nginx returns 502 Bad Gateway PHP-FPM is stopped or Nginx points to the wrong socket. Run systemctl status php*-fpm, inspect /run/php/, compare the actual socket with fastcgi_pass, then run nginx -t and reload.
PHP appears as downloadable text The PHP location block is absent, malformed, or not matched. Confirm the location ~ .php$ block, the FastCGI include, and the socket directive. Reload only after a successful syntax test.
Site times out DNS, provider firewall, UFW, or IPv6 routing is blocking traffic. Check the domain’s A and AAAA records, sudo ufw status verbose, provider rules, and whether Nginx listens on the address family advertised by DNS.
Nginx will not start or reload Syntax error, duplicate listener, or another service owns port 80. Run sudo nginx -t and inspect systemctl status nginx. Check listeners with sudo ss -ltnp | grep ':80'; stop or reconfigure Apache or another conflicting service.
Domain shows the wrong site The default server block or an overlapping server_name is selected. Review files in /etc/nginx/sites-enabled/, disable the conflicting default block, and make host names distinct.
Database connection is refused The database service is stopped, credentials are wrong, or the application expects a different socket or host. Check the database service status, test the dedicated account locally, verify the PHP database extension, and keep the host set to the local endpoint unless remote access is intentional.

Production checks after the first successful request

Access, patching, and recovery

  • Use SSH keys and a non-root sudo account; keep root login and password authentication disabled according to your access policy after confirming key-based access.
  • Apply operating-system and package security updates regularly.
  • Back up both the database and application files. Test restoring them on a separate system; an untested backup is not a recovery plan.
  • Document the domain, DNS records, firewall rules, package versions, service names, socket path, and certificate renewal method.

Logs and resource monitoring

Review Nginx access and error logs, PHP-FPM logs, and database logs when diagnosing an incident. Monitor disk space, memory, CPU, and swap. A full disk can stop database writes and certificate renewal even when the web process still responds.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Performance changes should follow measurement

PHP-FPM worker counts, database buffers, opcode caching, and Nginx caching all depend on traffic, request duration, available memory, and the application. Change one setting at a time, measure the result, and retain a rollback plan. There is no universal throughput number for a generic LEMP stack.

Operational choices compared

Choice Advantages Trade-offs
Ubuntu package repositories Familiar documentation, integrated dependencies, and a release-specific security update path. PHP and Nginx versions are tied to the Ubuntu release and may differ from current upstream versions.
Debian package repositories Conservative, stable base with predictable package maintenance. Some packages may be older than those in a newer Ubuntu release; verify application compatibility.
MariaDB MySQL-compatible server available directly from many distributions. Compatibility is high but not absolute; test vendor-specific SQL, authentication, and backup behavior.
MySQL Useful when an application or team standardizes on MySQL-specific features and tooling. Package names, service names, and administrative prompts differ from MariaDB.
Distribution Nginx packages Simple installation and dependency management through the operating system. Release timing follows the distribution rather than the latest Nginx packages.
Official Nginx repository May provide a different Nginx release cadence and feature set. Adds another trusted package source and its update policy to maintain.
One site per VPS Few server blocks and straightforward ownership and deployment. Less efficient when several low-traffic domains could share the machine.
Multiple server blocks Separate domains, roots, logs, and deployment settings on one Nginx instance. Requires careful host-name matching, permissions, certificates, and resource isolation.

A repeatable deployment checklist

  1. Confirm SSH-key access, a sudo account, DNS, and provider firewall control.
  2. Run apt update and upgrade the system.
  3. Install UFW, Nginx, MariaDB or MySQL, PHP-FPM, and php-mysql.
  4. Enable and inspect the Nginx, database, and PHP-FPM services.
  5. Record the actual PHP-FPM socket path and service name.
  6. Allow SSH in UFW, then HTTP and HTTPS, and enable the firewall.
  7. Run the database hardening assistant and create a separate application database user.
  8. Create the document root with deliberate ownership and permissions.
  9. Write and enable the Nginx server block, including the FastCGI snippet and verified socket.
  10. Run nginx -t before every reload.
  11. Test static files, PHP execution, and a real database connection; remove diagnostic files.
  12. Issue an HTTPS certificate after DNS works, allow port 443, redirect HTTP, and verify renewal.
  13. Set up patching, backups, log review, resource monitoring, and a tested restore procedure.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More quests from Patch Notes

  1. How to Set Up a RedM (RDR2) Server in 2026: License Key, txAdmin and server.cfg, Step by StepBlog11min
  2. Best RedM (RDR2) Server Hosting in 2026: Comparing Four Hosts on Slots, Memory and PriceBlog10min
  3. How to Host a Mindustry Server in 2026: server-release.jar, Port 6567 and the Commands That MatterBlog7min
Recommended PC Tool
Recommended PC Tool
Outdated Drivers Are Slowing You DownFree scan - exact matches
PC Slower Than It Used to Be?Free scan - under a minute

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.