The LEMP stack combines Linux, Nginx, MariaDB, and PHP to create a high-performance foundation for hosting dynamic web applications. Whether you need to run WordPress, Laravel, Drupal, or custom PHP projects, this combination provides the speed and flexibility required for production workloads.
This guide walks you through installing and configuring each component on Debian. By the end, you will have Nginx serving web requests, MariaDB handling database storage, and PHP-FPM processing dynamic content. Additionally, you will secure your setup with a free SSL certificate from Letโs Encrypt and configure firewall rules to protect your server.
Understanding LEMP Stack Components
Before diving into the installation, however, understanding each component helps you troubleshoot issues and optimize performance:
- Nginx: A high-performance HTTP server and reverse proxy designed for efficiency. Unlike Apache, Nginx uses an event-driven architecture that handles thousands of concurrent connections with minimal memory overhead. This makes it ideal for high-traffic websites and API endpoints.
- MariaDB: A community-developed fork of MySQL that maintains compatibility while adding features like improved replication and storage engines. Debian includes MariaDB in its default repositories as the primary MySQL-compatible database server.
- PHP-FPM: The FastCGI Process Manager for PHP enables Nginx to execute PHP scripts efficiently. PHP-FPM manages worker processes that handle PHP requests, offering features like adaptive process spawning and separate pool configurations for different sites.
Default Versions by Debian Release
Debian includes specific versions of each LEMP component in its default repositories. The following table shows what you get with each supported release:
| Component | Debian 13 (Trixie) | Debian 12 (Bookworm) | Debian 11 (Bullseye) |
|---|---|---|---|
| Nginx | 1.26.x | 1.22.x | 1.18.x |
| MariaDB | 11.8.x | 10.11.x | 10.5.x |
| PHP | 8.4.x | 8.2.x | 7.4.x |
Most commands in this guide work identically across Debian 11, 12, and 13. Where differences exist (primarily PHP-FPM service names and socket paths), the guide provides version-specific instructions. Version numbers shown in expected outputs are examples; your output will reflect the actual version included in your Debian release.
LEMP Part 1: Install Nginx on Debian
Step 1: Update Your Debian System
Before installing any components, update your package lists and upgrade installed packages to their latest versions. This ensures you install the most recent stable versions of all LEMP components and prevents dependency conflicts:
sudo apt update && sudo apt upgrade
Specifically, apt update refreshes the package index from configured repositories, while apt upgrade installs available updates for all installed packages.
Step 2: Install Nginx
Subsequently, install Nginx from Debianโs default repositories:
sudo apt install nginx
Once installation completes, Nginx starts automatically. As a result, you can confirm the service is running by checking its status:
sudo systemctl status nginx
Typically, a healthy installation displays output similar to the following:
โ nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/lib/systemd/system/nginx.service; enabled; preset: enabled)
Active: active (running) since Mon 2025-01-01 00:00:00 UTC; 1min ago
Docs: man:nginx(8)
Main PID: 1236 (nginx)
Tasks: 2 (limit: 4915)
Memory: 3.5M
CPU: 25ms
CGroup: /system.slice/nginx.service
โโ1236 "nginx: master process /usr/sbin/nginx -g daemon on; master_process on;"
โโ1237 "nginx: worker process"
However, if Nginx is not running, start the service and enable it to launch at boot:
sudo systemctl enable nginx --now
Step 3: Verify Nginx Installation
Consequently, to confirm Nginx is serving pages correctly, check the installed version:
nginx -v
This command displays the version, which varies depending on your Debian release (1.18.x on Debian 11, 1.22.x on Debian 12, or 1.26.x on Debian 13):
nginx version: nginx/1.x.x
Additionally, you can open your serverโs IP address in a web browser. You should see the default Nginx welcome page confirming the web server is accessible.
For users who need the latest Nginx features or security patches, refer to our guide on installing Nginx Mainline on Debian, which covers adding the official Nginx repository. For configuration details, see the official Nginx documentation.
LEMP Part 2: Install MariaDB on Debian
Now that Nginx is running, the next component is MariaDB, which provides the database layer for your LEMP stack. Because Debian includes MariaDB in its default repositories, installation is straightforward.
If you need a specific MariaDB version or features from the official MariaDB repository, see our dedicated guide on installing MariaDB on Debian.
Step 1: Install MariaDB Server
Initially, install both the MariaDB server and client packages:
sudo apt install mariadb-server mariadb-client
Step 2: Verify MariaDB Service Status
Following the installation, verify that MariaDB is running:
sudo systemctl status mariadb
As expected, the status output confirms whether MariaDB is active:
โ mariadb.service - MariaDB x.x.x database server
Loaded: loaded (/lib/systemd/system/mariadb.service; enabled; preset: enabled)
Active: active (running) since Sat 2025-01-01 00:00:00 UTC; 30s ago
Docs: man:mariadbd(8)
https://mariadb.com/kb/en/library/systemd/
Main PID: 2345 (mariadbd)
Status: "Taking your SQL requests now..."
Tasks: 9 (limit: 4915)
Memory: 80.0M
CPU: 500ms
CGroup: /system.slice/mariadb.service
โโ2345 /usr/sbin/mariadbd
Should MariaDB not be running, start and enable it with this command:
sudo systemctl enable mariadb --now
Step 3: Secure the MariaDB Installation
MariaDB includes a security script that hardens the default configuration by removing test databases, anonymous users, and restricting root access. Therefore, run this script immediately after installation:
sudo mysql_secure_installation
The script prompts you for several security options. Follow these recommendations:
- Enter current password for root: Press Enter on a fresh installation (no password is set by default).
- Switch to unix_socket authentication: Type
Yand press Enter. This ensures only system users with appropriate permissions can access the MariaDB root account. - Change the root password: Type
Yand enter a strong password if you want password-based access in addition to socket authentication. - Remove anonymous users: Type
Yand press Enter. Anonymous users can pose security risks in production environments. - Disallow root login remotely: Type
Yand press Enter. This prevents unauthorized remote access attempts targeting the root account. - Remove test database: Type
Yand press Enter. The test database is accessible to all users and serves no purpose in production. - Reload privilege tables: Type
Yand press Enter to apply all changes immediately.
Step 4: Verify MariaDB Version
To conclude the MariaDB setup, confirm the installation by checking the version:
mariadb --version
This displays version information that varies by Debian release (10.5.x on Debian 11, 10.11.x on Debian 12, or 11.8.x on Debian 13):
mariadb from x.x.x-MariaDB, client 15.2 for debian-linux-gnu (x86_64) using EditLine wrapper
For a graphical interface to manage your databases, see our guide on installing phpMyAdmin with Nginx on Debian. For advanced configuration, refer to the official MariaDB documentation.
LEMP Part 3: Install PHP on Debian
At this point, PHP processes dynamic content and connects Nginx to MariaDB. You will install PHP-FPM (FastCGI Process Manager) along with common extensions required by most web applications.
Debian includes a specific PHP version in each release. If you need a different PHP version, see our guide on installing PHP on Debian for instructions on adding the Sury repository.
Step 1: Install PHP-FPM and Extensions
To begin the process, install PHP-FPM and commonly required extensions:
sudo apt install php-fpm php php-cli php-mysql php-curl php-gd php-mbstring php-xml php-zip
This command installs the PHP-FPM meta-package, which automatically pulls in the correct PHP version for your Debian release (PHP 7.4 on Debian 11, PHP 8.2 on Debian 12, or PHP 8.4 on Debian 13). The additional extensions provide essential functionality:
php-gd: Image processing for thumbnails and uploadsphp-mbstring: Multibyte string handling for international character setsphp-xml: XML parsing required by most CMS platformsphp-zip: Archive handling for plugin and theme installations
WordPress, Drupal, and Laravel may require additional extensions like
php-intl(internationalization) orphp-imagick(advanced image processing). Install them as needed withsudo apt install php-intl php-imagick.
Step 2: Verify PHP-FPM Service Status
Importantly, PHP-FPM runs as a version-specific service. To proceed, first identify your installed PHP version:
php -v
The output shows your installed PHP version, which will match your Debian release:
PHP 8.x.x (cli) (built: Mon DD YYYY HH:MM:SS) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.x.x, Copyright (c) Zend Technologies
with Zend OPcache v8.x.x, Copyright (c), by Zend Technologies
Debian 11 ships PHP 7.4, Debian 12 ships PHP 8.2, and Debian 13 ships PHP 8.4. Your output will show the version matching your release.
Next, check the PHP-FPM service status using your version number. Replace 8.4 with your actual PHP version:
For Debian 13 (PHP 8.4):
sudo systemctl status php8.4-fpm
On Debian 12 (PHP 8.2):
sudo systemctl status php8.2-fpm
With Debian 11 (PHP 7.4):
sudo systemctl status php7.4-fpm
Consequently, a running service displays status information similar to this (service name reflects your PHP version):
โ phpX.X-fpm.service - The PHP X.X FastCGI Process Manager
Loaded: loaded (/lib/systemd/system/phpX.X-fpm.service; enabled; preset: enabled)
Active: active (running) since Sat 2025-01-01 00:00:00 UTC; 1min ago
Docs: man:php-fpmX.X(8)
Main PID: 3450 (php-fpmX.X)
Status: "Processes active: 0, idle: 2, Requests: 0, slow: 0, Traffic: 0req/sec"
Tasks: 3 (limit: 4915)
Memory: 8.0M
CPU: 50ms
CGroup: /system.slice/phpX.X-fpm.service
โโ3450 "php-fpm: master process (/etc/php/X.X/fpm/php-fpm.conf)"
โโ3451 "php-fpm: pool www"
โโ3452 "php-fpm: pool www"
Step 3: Enable PHP-FPM Service
If PHP-FPM is not running, you need to enable and start it. Therefore, choose the command matching your PHP version:
For Debian 13 (PHP 8.4):
sudo systemctl enable php8.4-fpm --now
On Debian 12 (PHP 8.2):
sudo systemctl enable php8.2-fpm --now
With Debian 11 (PHP 7.4):
sudo systemctl enable php7.4-fpm --now
Configure Nginx to Process PHP Files
By default, however, Nginx does not process PHP files. You must configure a server block that passes PHP requests to PHP-FPM via a Unix socket.
Step 1: Create a Server Block Configuration
To initiate the configuration, create a new server block configuration file for your domain. Replace example.com with your actual domain:
sudo nano /etc/nginx/sites-available/example.com.conf
Next, add the following configuration. Adjust the PHP socket path to match your PHP version (the example uses PHP 8.4 for Debian 13):
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/html/example.com;
index index.php index.html index.htm;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
# PHP 8.4 socket path for Debian 13
# Debian 12: /run/php/php8.2-fpm.sock
# Debian 11: /run/php/php7.4-fpm.sock
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_buffer_size 128k;
fastcgi_buffers 4 128k;
fastcgi_intercept_errors on;
}
location ~ /\.ht {
deny all;
}
}
The
fastcgi_passdirective must point to the correct PHP-FPM socket for your Debian version. Use/run/php/php8.4-fpm.sockfor Debian 13,/run/php/php8.2-fpm.sockfor Debian 12, or/run/php/php7.4-fpm.sockfor Debian 11.
Save the file by pressing Ctrl+O, then exit with Ctrl+X.
Step 2: Create the Web Root Directory
First and foremost, create the document root directory specified in your server block:
sudo mkdir -p /var/www/html/example.com
Subsequently, set appropriate ownership so your user account can manage files:
sudo chown -R $USER:$USER /var/www/html/example.com
Thereafter, set permissions to allow Nginx to read files:
sudo chmod -R 755 /var/www/html/example.com
Step 3: Enable the Server Block
At this stage, create a symbolic link from sites-available to sites-enabled to activate your configuration:
sudo ln -s /etc/nginx/sites-available/example.com.conf /etc/nginx/sites-enabled/
Step 4: Test and Reload Nginx
Prior to applying changes, always test the Nginx configuration for syntax errors:
sudo nginx -t
If successful, the test produces this output:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful
If the test passes, reload Nginx to apply your changes:
sudo systemctl reload nginx
Create a PHP Test Page
With all components configured, you should now verify that PHP is processing correctly by creating a test page that displays PHP configuration information.
Step 1: Create a PHP Info File
First, use the following command to create a test PHP file in your document root:
echo "<?php phpinfo(); ?>" | sudo tee /var/www/html/example.com/info.php
Step 2: Test PHP Processing
Then, open your browser and navigate to http://example.com/info.php (or http://your-server-ip/info.php if you have not configured DNS). You should see a detailed PHP information page showing your PHP version, loaded modules, and configuration settings.
Step 3: Remove the Test File
After confirming PHP works correctly, remove the info.php file. Leaving it accessible exposes sensitive server configuration details:
sudo rm /var/www/html/example.com/info.php
For a complete WordPress installation using your new LEMP stack, see our guide on installing WordPress with Nginx on Debian.
Verify Complete LEMP Stack
Before proceeding to SSL configuration, you should verify that all LEMP components are running and properly configured. This quick check confirms everything works together.
Check All Services
First, verify that Nginx, MariaDB, and PHP-FPM are all running. Use the PHP-FPM service name matching your Debian version:
For Debian 13:
sudo systemctl is-active nginx mariadb php8.4-fpm
On Debian 12:
sudo systemctl is-active nginx mariadb php8.2-fpm
With Debian 11:
sudo systemctl is-active nginx mariadb php7.4-fpm
Importantly, a healthy stack returns active for all three services:
active active active
Test PHP and MariaDB Connection
To test the PHP-to-MariaDB connection, first create a temporary database user that PHP can use. Connect to MariaDB as root:
sudo mariadb
At the MariaDB prompt, create a test user and grant minimal privileges:
CREATE USER 'testuser'@'localhost' IDENTIFIED BY 'testpass';
GRANT USAGE ON *.* TO 'testuser'@'localhost';
FLUSH PRIVILEGES;
EXIT;
Now create a PHP file that tests the connection:
sudo tee /var/www/html/example.com/test-db.php <<'EOF'
<?php
$conn = new mysqli("localhost", "testuser", "testpass");
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
echo "Connected to MariaDB successfully. Server version: " . $conn->server_info;
$conn->close();
?>
EOF
Visit http://example.com/test-db.php in your browser. A successful connection displays the MariaDB version. After testing, remove the file and test user:
sudo rm /var/www/html/example.com/test-db.php
sudo mariadb -e "DROP USER 'testuser'@'localhost';"
With unix_socket authentication enabled (recommended in this guide), the MariaDB root account can only be accessed by system users with root privileges. PHP running as www-data cannot connect as MariaDB root, which is why this test uses a dedicated test user.
Secure Your LEMP Stack with Letโs Encrypt SSL
Implementing an SSL certificate encrypts traffic between your server and visitors. This improves security and is required for modern browsers to display your site without security warnings. Letโs Encrypt provides free, automated SSL certificates.
For advanced SSL configuration options, wildcard certificates, and troubleshooting, see our detailed guide on securing Nginx with Letโs Encrypt on Debian.
Step 1: Install Certbot
As a first step, install Certbot and its Nginx plugin:
sudo apt install python3-certbot-nginx -y
Step 2: Obtain and Install SSL Certificate
Afterward, run Certbot with the Nginx plugin. Replace the email address and domain with your own:
sudo certbot --nginx --agree-tos --redirect --hsts --staple-ocsp --email you@example.com -d example.com -d www.example.com
In particular, this command performs several security-enhancing actions automatically:
--nginx: Uses the Nginx plugin to automatically configure your server block for SSL.--agree-tos: Accepts Letโs Encryptโs terms of service.--redirect: Adds a 301 redirect from HTTP to HTTPS, ensuring all visitors use the encrypted connection.--hsts: Enables HTTP Strict Transport Security, instructing browsers to only connect via HTTPS.--staple-ocsp: Enables OCSP stapling, which speeds up SSL verification by having your server provide certificate validity proof directly.
Once Certbot completes, your site is accessible via https://example.com. Additionally, Certbot automatically renews certificates before expiration.
Configure Firewall Rules
Assuming your server runs a firewall, you must allow HTTP and HTTPS traffic. The following examples use UFW (Uncomplicated Firewall), which is commonly used on Debian systems.
Step 1: Install and Enable UFW
In the event that UFW is not installed, install it:
sudo apt install ufw
Important: If you are connected via SSH, allow SSH access before enabling UFW to prevent locking yourself out of your server.
sudo ufw allow ssh
Step 2: Allow HTTP and HTTPS Traffic
Conveniently, Nginx registers application profiles with UFW. Allow both HTTP and HTTPS:
sudo ufw allow 'Nginx Full'
Alternatively, however, you can allow ports individually:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
Step 3: Enable UFW
Finally, enable the firewall:
sudo ufw enable
Then verify the firewall status:
sudo ufw status
The firewall status displays your configured rules:
Status: active To Action From -- ------ ---- 22/tcp ALLOW Anywhere Nginx Full ALLOW Anywhere 22/tcp (v6) ALLOW Anywhere (v6) Nginx Full (v6) ALLOW Anywhere (v6)
Troubleshooting Common LEMP Issues
When troubleshooting LEMP issues, log files are your primary diagnostic tool. As such, each component writes logs to specific locations:
- Nginx logs:
/var/log/nginx/error.logand/var/log/nginx/access.log - PHP-FPM logs:
/var/log/php8.4-fpm.log(replace8.4with your PHP version) - MariaDB logs:
/var/log/mysql/error.log
For example, view recent errors with sudo tail -50 /var/log/nginx/error.log to quickly identify issues.
Nginx Fails to Start Due to Port Conflict
If Nginx fails to start, another service may be using port 80. To diagnose this, install lsof if it is not already available (minimal Debian installations often omit it):
sudo apt install lsof
With the tool installed, check for port conflicts:
sudo lsof -i :80
For example, if Apache is using port 80, the output looks like this:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME apache2 1234 root 4u IPv6 12345 0t0 TCP *:http (LISTEN)
To resolve this conflict, first stop the conflicting service, then start Nginx:
sudo systemctl stop apache2
sudo systemctl start nginx
PHP Files Download Instead of Executing
When your browser downloads PHP files instead of displaying them, the PHP location block is likely missing or misconfigured. In this case, verify your server block includes the PHP processing section and that the socket path matches your PHP version.
In addition to this, check that the PHP-FPM socket exists:
ls -la /run/php/
When correctly configured, the installation shows a socket file matching your PHP version:
srw-rw---- 1 www-data www-data 0 Jan 1 00:00 phpX.X-fpm.sock
However, if no socket file exists, PHP-FPM is not running. In that case, start it with your version-specific service name.
502 Bad Gateway Error
A 502 error typically indicates PHP-FPM is not running or the socket path is incorrect. Therefore, check PHP-FPM status using your version-specific service name:
For Debian 13:
sudo systemctl status php8.4-fpm
On Debian 12:
sudo systemctl status php8.2-fpm
With Debian 11:
sudo systemctl status php7.4-fpm
If the service shows as inactive or failed, start it:
sudo systemctl start php8.4-fpm # Adjust version as needed
Additionally, verify that the socket path in your Nginx configuration matches the actual socket in /run/php/. A mismatch between the configured and actual socket path is a common cause of 502 errors.
MariaDB Connection Refused
When PHP cannot connect to MariaDB, start by verifying the service is running:
sudo systemctl status mariadb
Then, test the connection from the command line:
sudo mariadb -e "SELECT VERSION();"
If this command succeeds while PHP still cannot connect, verify that your application uses the correct database credentials and socket path. MariaDB on Debian uses /var/run/mysqld/mysqld.sock for MySQL compatibility, which is the path most PHP applications expect.
Remove LEMP Stack Components
If you decide to remove the LEMP stack, follow these steps for each component. This section covers removing packages installed via APT and cleaning up configuration files.
Remove Nginx
sudo systemctl stop nginx
sudo apt remove --purge nginx nginx-common -y
sudo apt autoremove -y
Optionally, you may also remove configuration files and site data:
Warning: The following command permanently deletes all Nginx configuration files and logs. Back up any custom configurations before proceeding.
sudo rm -rf /etc/nginx /var/log/nginx
Remove MariaDB
sudo systemctl stop mariadb
sudo apt remove --purge mariadb-server mariadb-client mariadb-common -y
sudo apt autoremove -y
Similarly, remove databases and configuration files:
Warning: The following command permanently deletes all databases, user data, and MariaDB configuration. Export any databases you need to keep before proceeding.
sudo rm -rf /var/lib/mysql /etc/mysql /var/log/mysql
Remove PHP
Next, remove PHP and all installed extensions. The command varies by PHP version:
For Debian 13 (PHP 8.4):
sudo apt remove --purge php8.4* -y
sudo apt autoremove -y
On Debian 12 (PHP 8.2):
sudo apt remove --purge php8.2* -y
sudo apt autoremove -y
With Debian 11 (PHP 7.4):
sudo apt remove --purge php7.4* -y
sudo apt autoremove -y
Optionally, remove PHP configuration directories:
Warning: The following command permanently deletes all PHP configuration files. Back up any custom PHP configurations before proceeding.
sudo rm -rf /etc/php
Verify Removal
Finally, confirm all packages have been removed:
dpkg -l | grep -E "nginx|mariadb|php"
If successful, the command produces no output, confirming all LEMP packages have been successfully removed. Any remaining entries indicate packages that still need removal.
Additionally, to verify services are no longer listening on web ports:
sudo ss -tlnp | grep -E ':80|:443'
Similarly, no output confirms that nothing is listening on web ports after removal.
Conclusion
At this point, your Debian server runs a complete LEMP stack with Nginx handling web requests, MariaDB managing databases, and PHP-FPM processing dynamic content. As a result, the SSL certificate encrypts all traffic, and the firewall rules protect your server from unauthorized access.
Moving forward, consider deploying a content management system with our WordPress installation guide, or set up phpMyAdmin for database management. For production environments, implement regular backups, configure log rotation, and consider adding a caching layer like Redis or Memcached to improve performance.