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.
Antihost offers Linux VPS plans with full root access, unlimited traffic on a 1 Gbps port and a 14-day free trial.
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.
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:
- A browser resolves your domain to the VPS address and connects on TCP port 80 or 443.
- Nginx selects the matching server block by the requested host name.
- Nginx serves images, CSS, JavaScript, and other static files directly.
- A request ending in
.phpis passed to the local PHP-FPM Unix socket. - 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.
#1 Best Overall
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
sudoprivileges. 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.
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:
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minutePC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Rank #2
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.
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.
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.
Rank #4
Understand the important directives
listenbinds the site to HTTP on IPv4 and, when configured, IPv6.server_namedetermines which host name selects this block.rootdefines the directory from which static files and scripts are served.indexmakes 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
.htaccessfiles 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.
Recommended Free Tools
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:
Best Value
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.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →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.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.
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.
Quick Recap
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
- Confirm SSH-key access, a sudo account, DNS, and provider firewall control.
- Run
apt updateand upgrade the system. - Install UFW, Nginx, MariaDB or MySQL, PHP-FPM, and
php-mysql. - Enable and inspect the Nginx, database, and PHP-FPM services.
- Record the actual PHP-FPM socket path and service name.
- Allow SSH in UFW, then HTTP and HTTPS, and enable the firewall.
- Run the database hardening assistant and create a separate application database user.
- Create the document root with deliberate ownership and permissions.
- Write and enable the Nginx server block, including the FastCGI snippet and verified socket.
- Run
nginx -tbefore every reload. - Test static files, PHP execution, and a real database connection; remove diagnostic files.
- Issue an HTTPS certificate after DNS works, allow port 443, redirect HTTP, and verify renewal.
- 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.




