How To Install The LEMP Stack On AWS

In this tutorial we are going to install the Linux, Nginx, MySQL, and PHP (LEMP) stack on an AWS EC2 instance. The LEMP stack is a very popular solution in the web hosting industry. It is a combination of Linux, Nginx, MySQL, and PHP. The LEMP stack is a great alternative to the LAMP stack. The LEMP stack is perfect for high-performance web applications. There are a few steps involved in getting this set up so let’s get right to it.

Select Your Instance

The foundation of this solution is the Linux portion of LEMP. In this case, that will be Ubuntu. If you don’t yet have an Ubuntu server running and are not sure how to proceed, you are in luck because we recently published How To Create An Ubuntu Server On AWS EC2 as well as How To SSH To Ubuntu Server On EC2. So the first step is to follow both of those guides and when you are ready we can move on.

Installing the Nginx Web Server

Nginx which is pronounced like Engine-X is a high-performance web server that has gained a lot of popularity in recent years. Nginx is used to display web pages to visitors of your server in the AWS cloud. In Ubuntu, the APT package manager is the tool of choice for locating and installing software. We can start by updating the package index on the server. The command for this is apt update.

apt update
Reading package lists... Done
E: Could not open lock file /var/lib/apt/lists/lock - open (13: Permission denied)
E: Unable to lock directory /var/lib/apt/lists/
W: Problem unlinking the file /var/cache/apt/pkgcache.bin - RemoveCaches (13: Permission denied)
W: Problem unlinking the file /var/cache/apt/srcpkgcache.bin - RemoveCaches (13: Permission denied)

Well, that did not go as planned! In order to run the apt update command, you need to make use of the sudo construct. Sudo is an acronym for superuser do or substitute user do, is a command that runs an elevated prompt without a need to change your identity. So let’s try that again.

sudo apt update
...
...
Fetched 23.0 MB in 4s (6444 kB/s)               
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
79 packages can be upgraded. Run 'apt list --upgradable' to see them.

If things go smoothly, you’ll see some output similar to the above. Ok, we’re ready to install Nginx! To install Nginx we can use the command sudo apt install nginx.

sudo apt install nginx
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following additional packages will be installed:
  fontconfig-config fonts-dejavu-core libdeflate0 libfontconfig1 libgd3 libjbig0 libjpeg-turbo8
  libjpeg8 libnginx-mod-http-geoip2 libnginx-mod-http-image-filter libnginx-mod-http-xslt-filter
  libnginx-mod-mail libnginx-mod-stream libnginx-mod-stream-geoip2 libtiff5 libwebp7 libxpm4
  nginx-common nginx-core
Suggested packages:
  libgd-tools fcgiwrap nginx-doc ssl-cert
The following NEW packages will be installed:
  fontconfig-config fonts-dejavu-core libdeflate0 libfontconfig1 libgd3 libjbig0 libjpeg-turbo8
  libjpeg8 libnginx-mod-http-geoip2 libnginx-mod-http-image-filter libnginx-mod-http-xslt-filter
  libnginx-mod-mail libnginx-mod-stream libnginx-mod-stream-geoip2 libtiff5 libwebp7 libxpm4 nginx
  nginx-common nginx-core
0 upgraded, 20 newly installed, 0 to remove and 79 not upgraded.
Need to get 2689 kB of archives.
After this operation, 8333 kB of additional disk space will be used.
Do you want to continue? [Y/n] Y

Notice just above that as the installation is working, it will pause and ask you if you want to continue since installing Nginx will take up some disk space. We of course choose Y for Yes and move forward. At the completion of this step, there will be a live web server powered by Nginx running on this Ubuntu server. Cool!

If you would like to verify this, check your EC2 instances dashboard and view the details for this particular Ubuntu server. Locate the Public IPv4 address and then type that address into the web browser of your choice. If you were successful, you’ll see the familiar “Welcome to nginx!” splash screen for a new Nginx web server.

MySQL Installation

Ok, Nginx is down, and MySQL is up next. Step 2 of the LEMP stack is getting MySQL running on our Ubuntu server. To do this, let’s use the command sudo apt install mysql-server.

sudo apt install mysql-server
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following additional packages will be installed:
  libcgi-fast-perl libcgi-pm-perl libclone-perl libencode-locale-perl libevent-pthreads-2.1-7
  libfcgi-bin libfcgi-perl libfcgi0ldbl libhtml-parser-perl libhtml-tagset-perl
  libhtml-template-perl libhttp-date-perl libhttp-message-perl libio-html-perl
  liblwp-mediatypes-perl libmecab2 libprotobuf-lite23 libtimedate-perl liburi-perl mecab-ipadic
  mecab-ipadic-utf8 mecab-utils mysql-client-8.0 mysql-client-core-8.0 mysql-common
  mysql-server-8.0 mysql-server-core-8.0
Suggested packages:
  libdata-dump-perl libipc-sharedcache-perl libbusiness-isbn-perl libwww-perl mailx tinyca
The following NEW packages will be installed:
  libcgi-fast-perl libcgi-pm-perl libclone-perl libencode-locale-perl libevent-pthreads-2.1-7
  libfcgi-bin libfcgi-perl libfcgi0ldbl libhtml-parser-perl libhtml-tagset-perl
  libhtml-template-perl libhttp-date-perl libhttp-message-perl libio-html-perl
  liblwp-mediatypes-perl libmecab2 libprotobuf-lite23 libtimedate-perl liburi-perl mecab-ipadic
  mecab-ipadic-utf8 mecab-utils mysql-client-8.0 mysql-client-core-8.0 mysql-common mysql-server
  mysql-server-8.0 mysql-server-core-8.0
0 upgraded, 28 newly installed, 0 to remove and 79 not upgraded.
Need to get 29.3 MB of archives.
After this operation, 242 MB of additional disk space will be used.
Do you want to continue? [Y/n] Y

Notice that as we did with the Nginx install, we choose Y for Yes when installing the MySQL software. We should now have both services running and we can check that with service –status-all

service --status-all
 [ + ]  acpid
 [ + ]  apparmor
 [ + ]  apport
 [ + ]  chrony
 [ - ]  console-setup.sh
 [ + ]  cron
 [ - ]  cryptdisks
 [ - ]  cryptdisks-early
 [ + ]  dbus
 [ - ]  grub-common
 [ - ]  hwclock.sh
 [ - ]  irqbalance
 [ - ]  iscsid
 [ - ]  keyboard-setup.sh
 [ + ]  kmod
 [ - ]  lvm2
 [ - ]  lvm2-lvmpolld
 [ + ]  multipath-tools
 [ + ]  mysql
 [ + ]  nginx
 [ - ]  open-iscsi
 [ - ]  open-vm-tools
 [ + ]  plymouth
 [ + ]  plymouth-log
 [ + ]  procps
 [ - ]  rsync
 [ - ]  screen-cleanup
 [ + ]  ssh
 [ + ]  udev
 [ + ]  ufw
 [ + ]  unattended-upgrades
 [ - ]  uuidd

Any service with a + sign before it means it is installed and running. We can see both Nginx and MySQL are now running nicely.

Logging In To MySQL

To verify the MySQL installation, let’s login using the sudo mysql command.

sudo mysql
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.30-0ubuntu0.22.04.1 (Ubuntu)

Copyright (c) 2000, 2022, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
4 rows in set (0.00 sec)
mysql> exit
Bye

In the steps above we did not need to use a password since MySql on Ubuntu makes use of auth_socket for authentication instead of a password. We’ll see how to add a user and configure PHP and MySql to work together shortly.

Install PHP

PHP has advanced greatly since its early beginnings and now has blazing performance and modern syntax for demanding developers. To install PHP we can type sudo apt install php8.1-fpm php-mysql at the Ubuntu terminal like so.

sudo apt install php8.1-fpm php-mysql
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following additional packages will be installed:
  bzip2 mailcap mime-support php-common php8.1-cli php8.1-common php8.1-mysql php8.1-opcache
  php8.1-readline
Suggested packages:
  bzip2-doc php-pear
The following NEW packages will be installed:
  bzip2 mailcap mime-support php-common php-mysql php8.1-cli php8.1-common php8.1-fpm php8.1-mysql
  php8.1-opcache php8.1-readline
0 upgraded, 11 newly installed, 0 to remove and 79 not upgraded.
Need to get 5382 kB of archives.
After this operation, 22.2 MB of additional disk space will be used.
Do you want to continue? [Y/n] Y

Once that completes, you will now have PHP running on the server. Excellent!

Make PHP and Nginx Work Together

Nginx uses server blocks to hold information about the website you would like to serve over the internet. By making use of server blocks, you can actually serve more than one domain from your LEMP server if you so choose. Typically there is a default folder at /var/www/html which serves up a website. Let’s configure a new site using exampledomain (replace with your own).

Set up a root directory for exampledomain like so:

sudo mkdir /var/www/exampledomain

Now, we can leverage the $USER variable to assign ownership to the current user.

sudo chown -R $USER:$USER /var/www/exampledomain

Now we need to create a configuration file for our exampledomain in the sites-available directory. To do this we can use nano, a simple text editor built into most Linux operating systems. The command will be sudo nano /etc/nginx/sites-available/exampledomain

sudo nano /etc/nginx/sites-available/exampledomain

You will see an empty screen but you’ll want to fill in the details as shown here.

server {
    listen 80;
    server_name exampledomain www.exampledomain;
    root /var/www/exampledomain;

    index index.html index.htm index.php;

    location / {
        try_files $uri $uri/ =404;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
     }

    location ~ /\.ht {
        deny all;
    }

}

With all of this in place you now need to save this file. To do so in nano, you use the ctrl+X key. When you press this key combination you will see the message “Save modified buffer?”. Do not be alarmed. Press the Y key.

It will then say “File Name to Write: /etc/nginx/sites-available/exampledomain”. Simply hit the Enter key here and the file will be saved.

Activate Configuration

To activate the configuration we need to use a symlink. The command to create the symlink is shown here.

sudo ln -s /etc/nginx/sites-available/exampledomain /etc/nginx/sites-enabled/

Making the symlink above is not enough to activate the new server block. You must also unlink the default configuration by using the unlink command as well.

sudo unlink /etc/nginx/sites-enabled/default

Verify Nginx syntax

To make sure the configuration is correct in the Nginx configuration file, we can use the command sudo nginx -t which will display errors if there are any. If everything is good, we’ll see that the syntax is ok and the test is successful.

sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

In order for these changes to take effect, we need to reload Nginx using sudo systemctl reload nginx.

sudo systemctl reload nginx

Testing the new server block

Now that we have restarted the nginx service, let’s go ahead and visit the IP address or domain of our server in a web browser. Oh no, we see a 403 Forbidden nginx/1.18.0 (Ubuntu) error.

What is this all about? Well, we created a directory to hold the files for our exampledomain configuration, but we never did put any HTML files in there. Let’s fix that now by placing a simply index.html file with a simple message. Use the command below to create a new file.

nano /var/www/exampledomain/index.html

Place this markup in the file.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Hello LEMP!</title>
</head>
<body>
    <h1>Hello LEMP!</h1>
</body>
</html>

Use the ctrl+X command to save this file.

With a valid index.html file in our chosen directory, we should see a new successful page display in our browser, and sure enough, that is what we get!

LEMP is working

What about PHP?

To see if PHP is working, we can create a new PHP file using this command.

nano /var/www/exampledomain/hellophp.php

In this file place this simple PHP script.

<?php
phpinfo();

After you save this file, you can visit it in your browser using http://server_domain_or_IP/hellophp.php

You should see some output similar to below. Nice! PHP is also now working.

PHP Version 8.1.2
SystemLinux ip-172-31-85-143 5.15.0-1011-aws #14-Ubuntu SMP Wed Jun 1 20:54:22 UTC 2022 x86_64
Build DateAug 17 2022 13:08:39
Build SystemLinux
Server APIFPM/FastCGI
Virtual Directory Supportdisabled
Configuration File (php.ini) Path/etc/php/8.1/fpm
Loaded Configuration File/etc/php/8.1/fpm/php.ini
Scan this dir for additional .ini files/etc/php/8.1/fpm/conf.d
Additional .ini files parsed/etc/php/8.1/fpm/conf.d/10-mysqlnd.ini, /etc/php/8.1/fpm/conf.d/10-opcache.ini, /etc/php/8.1/fpm/conf.d/10-pdo.ini, /etc/php/8.1/fpm/conf.d/20-calendar.ini, /etc/php/8.1/fpm/conf.d/20-ctype.ini, /etc/php/8.1/fpm/conf.d/20-exif.ini, /etc/php/8.1/fpm/conf.d/20-ffi.ini, /etc/php/8.1/fpm/conf.d/20-fileinfo.ini, /etc/php/8.1/fpm/conf.d/20-ftp.ini, /etc/php/8.1/fpm/conf.d/20-gettext.ini, /etc/php/8.1/fpm/conf.d/20-iconv.ini, /etc/php/8.1/fpm/conf.d/20-mysqli.ini, /etc/php/8.1/fpm/conf.d/20-pdo_mysql.ini, /etc/php/8.1/fpm/conf.d/20-phar.ini, /etc/php/8.1/fpm/conf.d/20-posix.ini, /etc/php/8.1/fpm/conf.d/20-readline.ini, /etc/php/8.1/fpm/conf.d/20-shmop.ini, /etc/php/8.1/fpm/conf.d/20-sockets.ini, /etc/php/8.1/fpm/conf.d/20-sysvmsg.ini, /etc/php/8.1/fpm/conf.d/20-sysvsem.ini, /etc/php/8.1/fpm/conf.d/20-sysvshm.ini, /etc/php/8.1/fpm/conf.d/20-tokenizer.ini
PHP API20210902
PHP Extension20210902
Zend Extension420210902
Zend Extension BuildAPI420210902,NTS
PHP Extension BuildAPI20210902,NTS
Debug Buildno
Thread Safetydisabled
Zend Signal Handlingenabled
Zend Memory Managerenabled
Zend Multibyte Supportdisabled
IPv6 Supportenabled
DTrace Supportavailable, disabled
Registered PHP Streamshttps, ftps, compress.zlib, php, file, glob, data, http, ftp, phar
Registered Stream Socket Transportstcp, udp, unix, udg, ssl, tls, tlsv1.0, tlsv1.1, tlsv1.2, tlsv1.3
Registered Stream Filterszlib.*, string.rot13, string.toupper, string.tolower, convert.*, consumed, dechunk, convert.iconv.*
This program makes use of the Zend Scripting Language Engine:
Zend Engine v4.1.2, Copyright (c) Zend Technologies
    with Zend OPcache v8.1.2, Copyright (c), by Zend Technologies

How To Install The LEMP Stack On AWS Summary

Congratulations! You now have a fully operational Linux, Nginx, MySQL, and PHP (LEMP) stack running on an AWS EC2 instance. Let’s have a look at the services running one more time to confirm.

service --status-all
 [ + ]  acpid
 [ + ]  apparmor
 [ + ]  apport
 [ + ]  chrony
 [ - ]  console-setup.sh
 [ + ]  cron
 [ - ]  cryptdisks
 [ - ]  cryptdisks-early
 [ + ]  dbus
 [ - ]  grub-common
 [ - ]  hwclock.sh
 [ - ]  irqbalance
 [ - ]  iscsid
 [ - ]  keyboard-setup.sh
 [ + ]  kmod
 [ - ]  lvm2
 [ - ]  lvm2-lvmpolld
 [ + ]  multipath-tools
 [ + ]  mysql
 [ + ]  nginx
 [ - ]  open-iscsi
 [ - ]  open-vm-tools
 [ + ]  php8.1-fpm
 [ + ]  plymouth
 [ + ]  plymouth-log
 [ + ]  procps
 [ - ]  rsync
 [ - ]  screen-cleanup
 [ + ]  ssh
 [ + ]  udev
 [ + ]  ufw
 [ + ]  unattended-upgrades
 [ - ]  uuidd

To learn more about LEMP on AWS check out these articles.