LEMP (Linux, Nginx, MySQL, PHP) is a variation of the LAMP stack (Linux, Apache, MySQL, PHP). The only difference being LEMP uses Nginx, where LAMP uses Apache. Nginx is much faster, and generally more secure than Apache. In this guide, we will be configuring it to work with Let's Encrypt, which is a global Certificate Authority. They provide a free service for SSL/TLS certificates, as they are nonprofit. If you use Vultr DNS, we have a version of this article with a helper script that automates many steps.
Deploy a new Ubuntu 20.04 Vultr VPS instance.
Follow our best practices guides:
Change to your sudo user for the remaining steps.
Update sources and install Nginx and MariaDB.
$ sudo apt update && sudo apt install -y nginx mariadb-server
Run the first-time setup for the MySQL installation. By default, it will ask for a root password, which is unset, so press enter.
Then it will ask if you want to set a root password, press N, and ENTER. For the rest of the prompts, press enter to accept the defaults.
$ sudo mysql_secure_installation
Connect to the MariaDB monitor.
$ sudo mariadb
Create a new test database.
CREATE DATABASE example_db;
Grant privileges for a non-root user. Replace the username and password with your current username, and a secure password of your choosing.
GRANT ALL ON example_db.* TO 'username'@'localhost' IDENTIFIED BY 'password' WITH GRANT OPTION;
Flush privileges and exit.
FLUSH PRIVILEGES;
exit
Now connect again with the username you just created. It will prompt for the password.
$ mariadb -u username -p
Make sure the example_db is accessible.
SHOW DATABASES;
Then set it as the current database.
USE example_db;
Create a table for later testing and exit.
CREATE TABLE table1(column1 varchar(255));
INSERT INTO table1 VALUES("Database connection established successfully");
exit
Install php-fpm and php-mysql.
$ sudo apt install -y php-fpm php-mysql
Install snap dependencies.
$ sudo apt install -y snap
Install Certbot with Snap. This ensures the correct version is installed.
$ sudo snap install --classic certbot
Allow HTTP and HTTPS traffic through the UFW firewall.
$ sudo ufw allow http
$ sudo ufw allow https
Most of the steps below use the domain example.com, which you need to replace with your domain name.
Make a new directory for the website.
$ sudo mkdir -p /var/www/example.com
Give the correct user permissions to the website directory.
$ sudo chown -R $USER:$USER /var/www/example.com
Open a new file in the Nginx sites-available
directory.
$ sudo nano /etc/nginx/sites-available/example.com
Add the following code snippet, save, and exit.
server {
listen 80;
listen [::]:80;
server_name www.example.com;
server_name example.com;
root /var/www/example.com;
location / {
index index.html;
}
}
Restart Nginx.
$ sudo systemctl restart nginx.service
Run a certbot dry run. It will prompt you for some information.
$ sudo certbot certonly --dry-run --nginx -d example.com,www.example.com
It should report the dry run ran successfully. Now obtain the LetsEncrypt certificate.
$ sudo certbot certonly --nginx -d example.com,www.example.com
Open the Nginx config file again.
$ sudo nano /etc/nginx/sites-available/example.com
Replace everything with the following code snippet, save, and exit.
server {
listen 80;
listen [::]:80;
server_name www.example.com;
server_name example.com;
root /var/www/example.com;
location / {
return 301 https://example.com$request_uri;
}
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
root /var/www/example.com;
index index.php index.html index.htm index.nginx-debian.html;
add_header X-XSS-Protection "1; mode=block";
add_header Content-Security-Policy "default-src 'self'; script-src 'self';";
add_header Referrer-Policy "no-referrer";
add_header X-Frame-Options "SAMEORIGIN" always;
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php7.4-fpm.sock;
}
server_name www.example.com;
server_name example.com;
access_log /var/log/example.com.log;
error_log /var/log/example.com.error.log;
}
Link the config file to the nginx sites-enabled directory.
$ sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
Restart nginx.
$ sudo systemctl restart nginx.service
Open a new file in your web directory.
$ nano /var/www/example.com/testdb.php
Add the following code snippet, save, and exit. Change the username and password below to the ones you set earlier in the MariaDB monitor.
<?php
$mysqli = new mysqli("localhost", "username", "password", "example_db");
if (mysqli_connect_errno()) {
printf("Connection failed: %s\n", mysqli_connect_error());
exit();
}
$query = "SELECT column1 FROM table1";
if($result = $mysqli->query($query)) {
while($row = $result->fetch_row()){
printf("%s\n", $row[0]);
}
$result->close();
}
$mysqli->close();
?>
To verify the server is running with LetsEncrypt, and can access the database correctly, navigate to https://example.com/testdb.php
, substituting example.com with your domain.
You should see "Database connection established successfully". This verifies the LEMP stack is functioning properly.
For testing LetsEncrypt, you can use ssllabs, which should report an "A" rank for your domain.
The security headers added to the Nginx config are
X-XSS-Protection "1; mode=block"
Content-Security-Policy "default-src 'self'; script-src 'self';"
Referrer-Policy "no-referrer"
X-Frame-Options "SAMEORIGIN" always
It will also redirect all HTTP requests to HTTPS, making it almost impossible for MITM attacks to occur.
In some cases, you may find that Snap needs an update. Vultr users have reported these steps will work to resolve the issue:
$ sudo apt-get remove snap
$ sudo apt install snapd
$ sudo snap install core
$ sudo ln -s /snap/bin/certbot /usr/bin/certbot
You have successfully installed a LEMP stack on your Ubuntu 20.04 LTS VPS. For more information about LEMP, and further guides, see the official documentation: