You decide which services we may enable for analytics, personalization, and external content.
We use cookies and similar technologies on our website to personalize content, embed external media, and better understand how individual areas are used.
Necessary cookies
These entries support essential functions such as security, stability, and form processing.
Optional sections
AnalyticsMarketingPersonalizationExternal media
Your data may be processed with your consent or on the basis of legitimate interests. In the cookie settings, you can reject or explicitly allow each optional category.
Some services process personal data in third countries. By consenting, you also agree to this transfer. You can find more information in our Privacy Policy.
You can reopen and change your selection at any time. · 1.0.2
Set Up a LEMP Stack on a Linux Server – Deploy High-Performance Web Applications
Learn how to set up a LEMP stack for hosting dynamic PHP websites on Linux VPS servers → Find out more
10 November 20268 min read1 Views0 helpful
Introduction
The LEMP stack is a popular collection of open-source software that is set up together to make hosting dynamic websites easy—with a particular focus on PHP websites and apps. The acronym stands for: Linux as the operating system, "Engine x" (nginx) as the web server, MySQL as the database, and finally PHP for processing. In this guide, we’ll show you how to set up a LEMP stack on a Linux VPS, with detailed step-by-step instructions and an example of a to-do list website.
FAQ
Frequently asked questions
Linux is the operating system, Engine x (nginx) is the web server, MySQL is the database, and PHP handles processing—a popular software stack for hosting dynamic PHP websites.
Ubuntu. The commands are identical for Debian and should be similar for other distributions, although the syntax may vary slightly.
Connect to your server via SSH and update all packages using the appropriate command, such as sudo apt update on Ubuntu or Debian.
Run sudo apt install nginx. Then make sure the appropriate firewall rules are in place so the web server is accessible from the internet.
Feedback
Was this article helpful?
Your feedback helps us prioritize and improve content.
0 of 0 Users found this article helpful.
Personal support
Need more help than the guide provides?
Our ZAP support team knows your product — briefly describe your issue and we'll help you personally.
Navigation
VPS
Preparation
First, connect to your server via SSH. If you don’t know how, please see our First Access (SSH) guide.
This guide uses Ubuntu as the Linux distribution. The commands are identical for Debian and should be similar for other distributions, although command syntax may vary slightly. Make sure an operating system is installed and that you’re connected via SSH.
As always, update all packages to the latest version before installing anything by running the following command:
The installation can be divided into the LEMP stack’s core components: starting with the Nginx web server, followed by the MySQL database, and finally PHP. During the installation, we’ll set up a test website written in PHP that accesses the MySQL database. Each web request will then be processed and served by the Nginx web server.
Set up Nginx
Nginx is the web server that processes incoming web requests and serves responses. Install it with the following command:
Code
sudo apt install nginx
After installation, make sure the appropriate firewall rules are in place so the web server is accessible from the internet. In this example, we’re using the UFW firewall, as Nginx has a registered application profile for it.
If you use a different firewall, make sure port 80 (HTTP) is open. For more information about Linux firewalls, see our Managing the Firewall guide.
Enable the UFW firewall and create an SSH rule:
Code
# Regel für SSH erstellen
sudo ufw allow OpenSSH
# UFW Firewall aktivieren
sudo ufw enable
Warning
Make sure you have an SSH rule set up if you use UFW! Without one, you won’t be able to connect to your server via SSH if your current session disconnects!
Now create the rule for Nginx and check that the rules are in place:
Code
# Regel für Nginx erstellen
sudo ufw allow in "Nginx Full"
# UFW Firewall Regeln anzeigen
sudo ufw status
Tip
You can use the ufw app list command to see which profiles are available. In the example above, Nginx Full allows both HTTP (port 80) and HTTPS (port 443).
You should see the Nginx and Nginx (v6) rules with the status ALLOW, confirming that the firewall is configured correctly. You’ll also see any other rules you may have already set up, including the SSH rule.
Once the firewall is open for Nginx, test whether Nginx is working. Open your browser and enter your IP address, for example: http://[your_ip_address]
If everything is working, you should see a default welcome page. If not, check the service status with: systemctl status nginx
Set up MySQL
Next, you’ll install and configure a MySQL server to store data persistently in a relational database. Install it with:
Code
sudo apt install mysql-server
After installation, we recommend running the security script to secure your MySQL server. This is optional, but highly recommended. Run it with:
Code
sudo mysql_secure_installation
The script will guide you through the setup interactively. First, you’ll be asked about password validation. We recommend choosing Y so that only secure passwords are allowed in the future, and then selecting either MEDIUM with 1 or STRONG with 2.
You’ll then be asked whether to remove the anonymous user and disable remote root login. For security reasons, we recommend confirming both with Y. This removes the test user and makes root access available only locally via SSH, minimizing risk.
Finally, you’ll be asked whether to delete the test database and reload the privilege tables. We recommend choosing Y here as well, since the test database isn’t needed and the privilege tables need to be reloaded for the changes to take effect.
Now check whether the MySQL database is running by logging in with the following command: sudo mysql -u root. If everything works, you’ll see a welcome message. Enter quit to exit the MySQL shell.
Set up PHP
The final LEMP component is PHP. With Nginx, the external program php-fpm (PHP FastCGI Process Manager) is used. Nginx is configured to forward requests to php-fpm before they’re processed and served.
Install the latest version of php-fpm and the PHP-MySQL plugin with:
Code
sudo apt install php-fpm php-mysql
Check whether the installation was successful by checking the PHP version. If a version is displayed, PHP is working correctly:
Code
php -v
Tip
PHP extensions
For more advanced use cases, you may need additional PHP extensions. You can view a list with apt search php- | less.
Use the arrow keys to scroll and Q to exit. To install an extension, simply run:
Code
sudo apt install [php_erweiterung] [...]
You can install multiple extensions at once by separating them with spaces.
Create a test website
Now that all LEMP components are installed, let’s create a test website to show how the stack works together to serve a dynamic website. This is optional, but helpful for understanding how to set up your own websites.
In this example, we’ll build a small to-do list in PHP that retrieves and displays entries from a MySQL database. The website will be served through Nginx.
We’ll use zapdocs.example.com as the example domain, since you’ll probably want to use a domain in practice. You must create an A DNS record for the domain that points to your server’s IP address. See our Domain Records guide for help.
Info
You can also proceed without a domain and replace [your_domain] with any name. You’ll then access the website using the IP address. However, remember to remove the server_name line from the server block file later.
Configure Nginx
Web servers usually store website files in the /var/www directory. By default, Nginx has an html folder there containing a default page. To keep things organized, especially when hosting multiple websites, we recommend creating a separate folder for each website.
Create a new folder under /var/www/[your_domain]. In our example, this is /var/www/zapdocs.example.com.
Code
sudo mkdir /var/www/[your_domain]
Now create a new Nginx server block configuration file for this domain in the sites-available directory:
PHP version
Important: Replace [your_phpversion] with the PHP version you currently have installed. Check it with php -v, for example: PHP 8.3.6 (cli) (built: Mar 19 2025 10:08:38) (NTS).
In this example, it would be 8.3, so the line would be: fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
This server block file handles requests on port 80 (HTTP) and checks whether the request matches the specified server_name, meaning your domain. It also points to the /var/www/[your_domain] folder you created earlier, where the files are served from.
Save the file and exit Nano by pressing CTRL + X, then Y to confirm, and ENTER.
Enable the configuration by creating a symbolic link in the sites-enabled directory:
Not using a domain
If you’re not using a domain, remove or comment out the server_name line (by adding # in front of it). You should also disable the default server block with:
Code
sudo unlink /etc/nginx/sites-enabled/default
We recommend checking the syntax with the following command:
Code
sudo nginx -t
Restart Nginx to apply the changes:
Code
sudo systemctl reload nginx
Create the website
Now that Nginx is configured, let’s create the website that will be served. The folder is currently empty, so nothing will be displayed yet. We’ll build a small to-do list, as described above.
Prepare the database
Log in to your MySQL server:
Code
sudo mysql -u root
Create a new database named todowebsite and a table named todoitems inside it:
Code
# Datenbank erstellen
CREATE DATABASE todowebsite;
# Datenbank auswählen
USE todowebsite;
# Tabelle erstellen
CREATE TABLE todoitems (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
is_completed BOOLEAN DEFAULT FALSE,
creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Add a few sample entries:
Code
INSERT INTO todoitems (name, is_completed) VALUES ('Create ZAP-Docs Guide', 0);
INSERT INTO todoitems (name, is_completed) VALUES ('Buy a ZAP-Hosting Server', 1);
INSERT INTO todoitems (name, is_completed) VALUES ('Join ZAP-Hosting Discord', 0);
INSERT INTO todoitems (name, is_completed) VALUES ('Have a great day!', 0);
Create a dedicated todo user for the website:
Code
# Benutzer erstellen
# Ersetze [your_password] durch dein eigenes Passwort
CREATE USER todo@localhost IDENTIFIED BY '[your_password]';
# Rechte vergeben (alles in einer Zeile kopieren)
GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,DROP,ALTER
ON todowebsite.*
TO todo@localhost;
# Rechte neu laden
FLUSH PRIVILEGES;
Exit the MySQL shell with quit.
PHP website files
Now let’s create the PHP file for the to-do page. Open the Nano editor:
Code
sudo nano /var/www/[your_domain]/index.php
Add the following code. The first PHP block establishes the MySQL connection.
Important
Replace [your_password] with the password you set for the todo user.
The HTML section displays the to-do list by retrieving data from the database.
Code
<?php
// MySQL-Verbindung vorbereiten
$servername = "localhost";
$username = "todo";
$password = "[your_password]";
$dbname = "todowebsite";
// Verbindung erstellen
$conn = new mysqli($servername, $username, $password, $dbname);
// Verbindung prüfen, bei Fehler abbrechen
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
// SQL-Abfrage ausführen und Ergebnis speichern
$sql = "SELECT id, name, is_completed, creation_date FROM todoitems ORDER BY creation_date DESC";
$result = $conn->query($sql);
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>To-Do Liste</title>
</head>
<body>
<h1>Coole To-Do Liste :D</h1>
<p>Für unseren tollen ZAP-Hosting Guide: <a href="https://zap-hosting.com/guides/docs/vserver-linux-lemp-stack">https://zap-hosting.com/guides/docs/vserver-linux-lemp-stack</a></p>
<ul>
<?php
// Prüfen, ob Ergebnisse vorhanden sind
if ($result->num_rows > 0) {
// Durch alle Einträge iterieren
foreach ($result as $entry) {
echo "<li>";
// Name anzeigen, htmlspecialchars schützt vor XSS
echo htmlspecialchars($entry["name"]);
// Status anzeigen
if ($entry["is_completed"]) {
echo " <strong>(Abgeschlossen)</strong>";
} else {
echo " <strong>(Offen)</strong>";
}
// Erstellungsdatum anzeigen
echo " - Erstellungsdatum: " . htmlspecialchars($entry['creation_date']);
echo "</li>";
}
} else {
// Falls keine Einträge vorhanden sind
echo "<li>Keine To-Do Einträge gefunden.</li>";
}
?>
</ul>
</body>
</html>
<?php
// Verbindung schließen
$conn->close();
?>
Save the file and exit Nano by pressing CTRL + X, then Y and ENTER.
Test the website
You’ve successfully set up a test to-do website that uses all the components of the LEMP stack!
You should now be able to access the website using the domain (port 80 / http) you defined in the server block—in our example, zapdocs.example.com. It should look like this:
Conclusion
Congratulations, you’ve successfully installed and configured the LEMP stack! As a next step, we strongly recommend setting up a domain and an SSL certificate to ensure your data is transmitted securely. See our Certbot guide, which focuses on the Nginx plugin, and follow the interactive setup to quickly and easily get a certificate for your domain.
If you have any questions or run into any issues, our support team is available every day—don’t hesitate to contact us! 🙂
Server hosting from €2.49 / month, online instantly, DDoS protection included.