Linux, nginx, MySQL 5.7, and PHP 7 (LEMP) on AWS with free SSL

A stack is a group of software that creates a foundation to build upon. The LEMP stack is a web software stack which allows for delivering web applications. It is one of the most common of the web stacks to deliver a PHP application. LEMP uses a Linux kernel, Nginx for the webserver, MySQL or MariaDB for the database, and PHP for the scripting language.

Nearly all distributions of GNU/Linux will have the same instructions to install the required packages. There is also little change between versions of Ubuntu to warrant a special blog post entitled “LEMP on Ubuntu 14.04” and “LEMP on Ubuntu 16.04” as they will contain the same exact instructions. There are a few oddities on the Amazon Linux AMI to get Let’s Encrypt working on the t2.nano (1 CPU, 512 RAM) instance which I will cover here.

Once you launch your instance, the packages you need to install are:

sudo yum localinstall http://rpms.famillecollet.com/enterprise/remi-release-6.rpm
sudo yum localinstall https://dev.mysql.com/get/mysql57-community-release-el6-8.noarch.rpm
sudo yum install nginx.x86_64 php70-php-fpm php70-php-cli php70-php-mysqlnd mysql-community-server --enablerepo=epel

Now enable the services to run at boot:

sudo chkconfig nginx on
sudo chkconfig mysqld on
sudo chkconfig php70-php-fpm on
sudo service nginx start
sudo service mysqld start
sudo service php70-php-fpm start

Congratulations, you installed a LEMP stack! Now let us go a step further and set up SSL and a WordPress website. Type the following commands to set up our document root and create our virtual host:

sudo -i
cat << EOF > /etc/nginx/conf.d/wordpress.conf
# Upstream to abstract backend connection(s) for php
upstream php {
        server unix:/tmp/php-cgi.socket;
        server 127.0.0.1:9000 backup;
}

# comment the below if not using SSL
server {
        listen 80 default_server;
       listen [::]:80 default_server;
        server_name _;
        return 301 https://$host$request_uri;
}

server {
        # comment the next line when not using SSL. Don't forget to comment out the above server block and uncomment the listen 80 line below.
        listen 443 ssl default_server;
        #listen 80 default_server;
        server_name example.com www.example.com;
        ## Your only path reference.
        root /var/www/html/wordpress/;

        # comment if not using SSL
        ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
        
        index maint.php index.php;

        # Disables serving gzip content to IE 6 or below
        gzip_disable        "MSIE [1-6]\.";
        gzip_static     on;
 
        # Sets the default type to text/html so that gzipped content is served
        # as html, instead of raw uninterpreted data.
        default_type text/html;

        location /.well-known/acme-challenge {
           root /var/www/html/letsencrypt/wordpress/;
        }

        location = /favicon.ico {
                log_not_found off;
                access_log off;
        }

        location = /robots.txt {
                allow all;
                log_not_found off;
                access_log off;
        }

        # Use cached or actual file if they exists, otherwise pass request to WordPress
        location / {
                try_files $uri $uri/ /index.php?$args ;
        }    

        location ~ \.php$ {
                #NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini
                include fastcgi.conf;
                fastcgi_intercept_errors on;
                fastcgi_pass php;
        }

        location ~* \.(ico|jpg|webp|jpeg|gif|css|png|js|ico|bmp|zip|woff|woff2)$ {
                access_log off;
                log_not_found off;
                add_header Pragma public;
                add_header Cache-Control "public";
                expires max;
        }

        location ~* \.(php|html)$ {
                access_log on;
                log_not_found on;
                add_header Pragma public;
                add_header Cache-Control "public";
                expires 3d;
        }

#including this file which some caching plugins use
include /var/www/html/wordpress/nginx.conf;

# We don't want to allow the browsers to see .hidden linux/unix files or our own custom file
location ~ /\.ht { deny  all; access_log off; log_not_found off; }
location ~ /nginx.conf { deny all; }

}
EOF

The above commands are intended to be copied and pasted into a terminal. If you edited the file and pasted the contents in, remove the backslashes ( \ ) from the try_files line. Next let’s verify our php-fpm configuration to see how it is listening for requests.
sudo grep -in ^listen /etc/opt/remi/php70/php-fpm.d/www.conf

We can either edit the listen directive in /etc/opt/remi/php70/php-fpm.d/www.conf to match our unix socket in our wordpress.conf nginx virtual host file or leave it as is. We have already made nginx look at the unix socket first (which is faster) and revert to the IP address as a backup option.

Now let’s make our document root, download WordPress, and extract it to our specified directory.

sudo mkdir -p /var/www/html/
cd /var/www/html
wget https://wordpress.org/latest.tar.gz
tar xvf latest.tar.gz

Our next step is to set the appropriate permissions on the files and directories.

sudo chown nginx:apache -R /var/www/html/wordpress
sudo find /var/www/html/wordpress -type d -exec chmod 755 {} \;
sudo find /var/www/html/wordpress -type f -exec chmod 644 {} \;

Now let’s create a database user and secure our database. If you remember from a previous post mysql generates a temporary password. Let us grab that and run mysql_secure_installation. Answer all questions and provide a secure root password.

sudo grep 'temporary password' /var/log/mysqld.log 
sudo mysql_secure_installation

New we need to set up a database for WordPress.

mysql -u root -p
mysql> CREATE DATABASE word_press;
mysql> GRANT ALL PRIVILEGES ON word_press.* TO "word_press_user"@"localhost" IDENTIFIED BY "S3cur3_WordPress_DB!Password";
mysql> FLUSH PRIVILEGES;
mysql> EXIT;

Now let’s set up our SSL certificate with Let’s Encrypt. To install the client, we need additional software and a swap file.

sudo yum install make automake gcc gcc-c++ kernel-devel git-core python27-devel
# change to python 2.7 if you have an old Amazon AMI
sudo alternatives --config python
sudo easy_install pip
sudo pip install pip virtualenv --upgrade
git clone https://github.com/letsencrypt/letsencrypt /home/ec2-user/letsencrypt
cd /home/ec2-user/letsencrypt
# create a 2GB RAM swap for machines with less than 2GB RAM
sudo dd if=/dev/zero of=/swapfile bs=16M count=128
sudo mkswap /swapfile
sudo chmod 0600 /swapfile
sudo swapon /swapfile

# now to build letsencrypt
./letsencrypt-auto --help --debug

# Optional: Disable swapfile
sudo swapoff /swapfile

# Request certificates
sudo mkdir -p /var/www/html/letsencrypt/wordpress/
sudo chown apache:nginx /var/www/html/letsencrypt/wordpress/
sudo service nginx reload
./letsencrypt-auto certonly --webroot -w /var/www/html/letsencrypt/wordpress/ -d example.com -d www.example.com

Fill out the prompted information to get a certificate (we have already filled out the pertinent information in our nginx conf). All that is left to do is to make sure we get automatic renewals. To do that set up your crontab (sudo crontab -e) to include this line:

@monthly /home/ec2-user/letsencrypt/letsencrypt-auto renew && /sbin/service nginx reload

We are now all set to continue with the installation of WordPress using the built in installer. This can be accessed by visiting your website at https://example.com/

After installing WordPress, I recommend installing the jetpack module. Not only does it allow for posting to social media, but it has a setting to keep your WordPress installation on the latest version (a huge security plus).

3 comments

    1. you will also then need to press y 3 times during the installation of the sudo yum install nginx.x86_64 php70-php-fpm php70-php-cli php70-php-mysqlnd mysql-community-server –enablerepo=epel sequence of commands i guess you could also add -y to the end of the sequence

Comments are closed.