
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!

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 |
System | Linux ip-172-31-85-143 5.15.0-1011-aws #14-Ubuntu SMP Wed Jun 1 20:54:22 UTC 2022 x86_64 |
Build Date | Aug 17 2022 13:08:39 |
Build System | Linux |
Server API | FPM/FastCGI |
Virtual Directory Support | disabled |
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 API | 20210902 |
PHP Extension | 20210902 |
Zend Extension | 420210902 |
Zend Extension Build | API420210902,NTS |
PHP Extension Build | API20210902,NTS |
Debug Build | no |
Thread Safety | disabled |
Zend Signal Handling | enabled |
Zend Memory Manager | enabled |
Zend Multibyte Support | disabled |
IPv6 Support | enabled |
DTrace Support | available, disabled |
Registered PHP Streams | https, ftps, compress.zlib, php, file, glob, data, http, ftp, phar |
Registered Stream Socket Transports | tcp, udp, unix, udg, ssl, tls, tlsv1.0, tlsv1.1, tlsv1.2, tlsv1.3 |
Registered Stream Filters | zlib.*, 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.
- AWS Marketplace: LEMP stack (aws.amazon.com)
- AWS LEMP Stacks and EFS Issues – Lance Cleveland (lance.bio)
- Tutorial: Install a LAMP web server on the Amazon Linux (docs.aws.amazon.com)
- LEMP ready to run AMI for Amazon Web Services (AWS) (www.intuz.com)
- WEB STACK IMPLEMENTATION (LAMP STACK) IN AWS – dare (www.darey.io)
- How to Setup LEMP Stack on AWS – edoos.asia (edoos.asia)
- Home | Free Web Stack – Web Engineering (www.freewebstack.com)
- AWS: Highly Available & Resilient architecture using LAMP stack (www.linkedin.com)
- oayanda/LEMP-STACK-IMPLEMENTATION-ON-AWS – github.com (github.com)
- Drupal and CiviCRM installation with AWS RDS, LEMP stack (medium.com)
- Deploy LAMP Stack on AWS | Fit-DevOps (fitdevops.in)
- What is LEMP Stack? – GeeksforGeeks (www.geeksforgeeks.org)
- LEMP (container stack) | TurnKey GNU/Linux (www.turnkeylinux.org)
