Install a LEMP Stack on Ubuntu 20.04 LTS

Updated on September 25, 2020
Install a LEMP Stack on Ubuntu 20.04 LTS header image

Introduction

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.

1. Deploy Ubuntu Server

Change to your sudo user for the remaining steps.

2. Install Nginx and MariaDB

Update sources and install Nginx and MariaDB.

$ sudo apt update && sudo apt install -y nginx mariadb-server

3. Configure MariaDB

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

4. Install PHP and Certbot

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

5. Configure Nginx

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

6. Test

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.

About the Nginx Config

The security headers added to the Nginx config are

  • X-XSS-Protection "1; mode=block"
    • Prevents cross-site scripting, which stops attackers from injecting code onto the website that other users could see. With mode=block, the browser will not render the page at all if an attack is detected.
  • Content-Security-Policy "default-src 'self'; script-src 'self';"
    • Also prevents cross-site scripting by only allowing scripts to be loaded from the same domain the website is hosted on.
  • Referrer-Policy "no-referrer"
    • No referrer information will be added to the headers. It is mostly for the privacy of the user.
  • X-Frame-Options "SAMEORIGIN" always
    • The webpage will only be displayed on the same origin (domain) as itself. It will attempt to prevent browsers from rendering the webpage on a remote website, thereby making phishing attacks, and IP theft a lot harder. Not all browsers are compatible though.

It will also redirect all HTTP requests to HTTPS, making it almost impossible for MITM attacks to occur.

Troubleshooting

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	

Conclusion

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: