Install Magento 2.3.2 on AWS Ubuntu 18.04 full SSL with Nginx Redis Varnish and Cloudfront

Vincent Teyssier
Aug 14 · 11 min read

Magento open source is a fantastic platform, but is usually a hassle to get good performance and security. In this tutorial, I will try to achieve a mono instance installation fully https with session and full page cache and different speed optimization which will give you a A grade on webpagetest. Still some improvement to have to reach 100 on google pagespeed which will be the topic of a separate tutorial.

Description of the requests flow:

We want our site to be fully HTTPS, have full page cache and session cache. However Vanish does not handle HTTPS caching. To address this issue, we will route data the following way:

All calls on port 80 will be forwarded to port 443 by Nginx

Nginx will then proxypass (forward) calls arriving on port 443 (HTTPS) to Varnish port 6081.

Varnish will return the hit or miss to nginx on port 8080 where the configuration parameters will be applied (headers…)

user -> nginx:80 -> nginx:443 -> varnish:6081 -> nginx:8080

Installation preparation:

Go to your AWS console and pop up an EC2 instance (t2 medium strongly suggested as it has 4Gb RAM. Will cost you around 30usd/month for spot instance 24/7). I assumed you already know how to create your instance and ssh to it.

Assign an elastic IP to it and forward your domain name by adding an A record with your elastic IP address as value. Finally make sure that your security groups assigned to your instance allow SSH for your IP only and all inbound HTTP/HTTPS calls:

Inbound:
SSH Port 22 My IP
HTTP Port 80 0.0.0.0
HTTPS Port 443 0.0.0.0

SSH to your instance and let’s update and install dependencies, plus Maria DB (better perf than MySQL) and Nginx:

sudo apt update && sudo apt upgrade
sudo apt install unzip certbot
sudo apt-get -y install nginx
sudo apt install mariadb-server

Then we want to secure the DB:

sudo mysql_secure_installation

Follow instructions and enter a password for root.

Login to your database by typing mysql in command line and execute the following queries:

CREATE DATABASE magentodb;GRANT ALL PRIVILEGES ON magentodb.* TO ‘magentouser’@’localhost’ IDENTIFIED BY ‘putyourpasswordhere’;FLUSH PRIVILEGES;exit;

Next we want to create the magento user, group, folder and give correct permissions:

sudo useradd -m -U -r -d /opt/magento magento
sudo usermod -a -G magento www-data
sudo chmod 750 /opt/magento

Now let’s install php dependencies:

sudo apt install php7.2-common php7.2-cli php7.2-fpm php7.2-opcache php7.2-gd php7.2-mysql php7.2-curl php7.2-intl php7.2-xsl php7.2-mbstring php7.2-zip php7.2-bcmath php7.2-soap

And configure php parameters to fit Magento requirements:

sudo sed -i “s/memory_limit = .*/memory_limit = 2048M/” /etc/php/7.2/fpm/php.inisudo sed -i “s/upload_max_filesize = .*/upload_max_filesize = 256M/” /etc/php/7.2/fpm/php.inisudo sed -i “s/zlib.output_compression = .*/zlib.output_compression = on/” /etc/php/7.2/fpm/php.inisudo sed -i “s/max_execution_time = .*/max_execution_time = 18000/” /etc/php/7.2/fpm/php.inisudo sed -i “s/;date.timezone.*/date.timezone = UTC/” /etc/php/7.2/fpm/php.inisudo sed -i “s/;opcache.save_comments.*/opcache.save_comments = 1/” /etc/php/7.2/fpm/php.ini

Let’s now create a magento php configuration file :

sudo nano /etc/php/7.2/fpm/pool.d/magento.conf

and fill it with the following config:

[magento]
user = magento
group = www-data
listen.owner = magento
listen.group = www-data
listen = /var/run/php/php7.2-fpm-magento.sock
pm = ondemand
pm.max_children = 50
pm.process_idle_timeout = 10s
pm.max_requests = 500
chdir = /

And restart PHP FPM to reload the config:

sudo systemctl restart php7.2-fpm

Finally let’s get composer and install it locally:

curl -sS https://getcomposer.org/installer | sudo php — — install-dir=/usr/local/bin — filename=composer

Installation — SSL certificate:

If you already have an ssl certificate you can skip this part. Otherwise we will use certbot to request a Let’s Encrypt certificate.

First let’s stop nginx:

sudo systemctl stop nginx

For better security, we want to generate a Diffie-Hellman parameters strong enough:

sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048

Now let’s switch to root user and create the let’s encrypt folder and give it the correct group and permissions:

sudo su
mkdir -p /var/lib/letsencrypt/.well-known
chgrp www-data /var/lib/letsencrypt
chmod g+s /var/lib/letsencrypt

We will need to integrate this with nginx, so let’s create a snippet that we will use later in our nginx config file:

sudo nano /etc/nginx/snippets/letsencrypt.conf

Fill it with:

location ^~ /.well-known/acme-challenge/ {
allow all;
root /var/lib/letsencrypt/;
default_type “text/plain”;
try_files $uri =404;
}

Now let’s create our nginx configuration file to be able to answer the challenges to the EFF certificate server:

sudo nano /etc/nginx/sites-available/mywebsite.com

And fill it in with:

upstream fastcgi_backend {
server unix:/run/php/php7.2-fpm.sock;
}
server {
server_name mywebsite.com;
listen 80;
include snippets/letsencrypt.conf;
}

Create a symlink to enable the configuration:

sudo ln -s /etc/nginx/sites-available/mywebsite.com /etc/nginx/sites-enabled/mywebsite.com

Remove default config files:

sudo rm -f /etc/nginx/sites-enabled/default
sudo rm -f /etc/nginx/sites-available/default

And restart Nginx:

sudo service nginx start

Finally let’s generate the certificate (you will need to enter your email address and follow instructions):

sudo certbot certonly — agree-tos — email myemail@gmail.com — webroot -w /var/lib/letsencrypt/ -d mywebsite.com

The generated certificate has a validity of 90 days but is renewable. No need to set an alarm, let’s create a cron job to automatically do it for us:

sudo nano /etc/cron.d/certbot

Add the following in it:

0 */12 * * * root test -x /usr/bin/certbot -a \! -d /run/systemd/system && perl -e ‘sleep int(rand(3600))’ && certbot -q renew — renew-hook “systemctl reload nginx”

Installation — Magento:

Now we have all pre requisite and a valid ssl certificate, let’s install Magento via the composer method. We need to switch to the magento user to perform the install.

sudo su — magentocomposer create-project — repository-url=https://repo.magento.com/ magento/project-community-edition /opt/magento/public_htmlcd ~/public_html

And here is the main command that will magically install Magento:

php bin/magento setup:install — base-url=https://www.mywebsite.com/ \
— base-url-secure=https://www.mywebsite.com/ \
— admin-firstname=”FirstName” \
— admin-lastname=”LastName” \
— admin-email=”myemail@gmail.com” \
— admin-user=12345 \
— admin-password=”123456” \
— db-host=localhost \
— db-name=magentodb \
— db-user=magentouser \
— db-password=putyourpasswordhere\
— currency=USD \
— timezone=America/Chicago \
— use-rewrites=1

I won’t go through the list of parameters, I believe this is self-explanatory. Basically you fill in admin details and database (configured earlier) details.

Let’s now setup cron jobs:

php ~/public_html/bin/magento cron:install

Once installed, the last step before having a working magento is to modify the nginx configuration file to forward http calls to https and serve the magento site over https.

Please see below a classic configuration file:

sudo nano /etc/nginx/sites-available/mywebsite.comupstream fastcgi_backend {
server unix:/var/run/php/php7.2-fpm-magento.sock;
}
server {
listen 80;
server_name mywebsite.com;
include snippets/letsencrypt.conf;
return 301 https://mywebsite.com$request_uri;
}
server {
listen 443 ssl http2;
server_name mywebsite.com;
ssl_certificate /etc/letsencrypt/live/mywebsite.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mywebsite.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/mywebsite.com/chain.pem;
ssl_dhparam /etc/ssl/certs/dhparam.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ‘ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS’; ssl_prefer_server_ciphers on;
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 30s;
keepalive_timeout 300s;
add_header Strict-Transport-Security “max-age=15768000; includeSubdomains; preload”;
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
include snippets/letsencrypt.conf; set $MAGE_ROOT /opt/magento/public_html;
set $MAGE_MODE developer; # or production
access_log /var/log/nginx/magento-test.summerraingroup.com-access.log;
error_log /var/log/nginx/magento-test.summerraingroup.com-error.log;
include /opt/magento/public_html/nginx.conf.sample;
}

First test that your config file is bug free by running

nginx -t

And finally reload your nginx config (or restart or both to be sure):

sudo service nginx reload
sudo service nginx restart

You now have a working Magento installation. Try access it via browser and also try to access the admin panel. (you saved the admin link, right?)

Installing and configuring a secure Redis for session caching

Sudo apt-get install redis-server

Let’s add it to the start services:

sudo update-rc.d redis-server defaults

Now let’s secure it:

sudo nano /etc/redis/redis.conf

After opening the config file make sure the binding line is uncommented and uncomment the password line adding a secure password as below:

...
bind 127.0.0.1 ::1
...
...
# requirepass foobared
requirepass replacewithyourpassword

You can generate a strong and secure password using the following command:

openssl rand 60 | openssl base64 -A

This is important since this password can be brute forced.
Finally restart the service:

sudo systemctl restart redis.service

Now that we have installed redis and secured it we need to enable it in Magento. Please do it the following way in command line. And don’t forget to input your redis password in the command otherwise you will get an error 500:

bin/magento setup:config:set — session-save=redis — session-save-redis-host=127.0.0.1 — session-save-redis-log-level=3 — session-save-redis-db=2 — session-save-redis-password replacewithyourpassword

Here you go. Now your sessions are cached with Redis.

Installing and configuring Varnish for full page caching

sudo apt-get install varnish

Then go to your magento admin panel and get the Varnish configuration file:

Click STORES > Settings > Configuration > ADVANCED > System > Full Page Cache.

Enable Varnish and in Varnish settings select Export VCL for Varnish 5:

Edit the file and add the following line at the beginning of it:

import std;

Now let’s replace the content of the following file with the content of your newly generated VCL file:

sudo nano -f /etc/varnish/default.vcl

Finally we need to edit the Nginx config file to tell it to route calls coming on port 443(https) to Varnish port 6081.

The begining of your configuration file will be the same, redirecting port 80 (http) to 443 (https). Please see the modified part below:

server {
listen 443 ssl http2;
server_name mywebsite.com;
ssl_certificate /etc/letsencrypt/live/magento-test.summerraingroup.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/magento-test.summerraingroup.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/magento-test.summerraingroup.com/chain.pem;
ssl_dhparam /etc/ssl/certs/dhparam.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ‘ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS’; ssl_prefer_server_ciphers on;
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 30s;
keepalive_timeout 300s;
location / {
proxy_pass http://127.0.0.1:6081;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Ssl-Offloaded “1”;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Port 443;
#proxy_hide_header X-Varnish;
#proxy_hide_header Via;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
server_name mywebsite.com;
listen 8080;
add_header Strict-Transport-Security “max-age=15768000; includeSubdomains; preload”; add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
include snippets/letsencrypt.conf; set $MAGE_ROOT /opt/magento/public_html;
set $MAGE_MODE production; # or production
access_log /var/log/nginx/magento-test.summerraingroup.com-access.log;
error_log /var/log/nginx/magento-test.summerraingroup.com-error.log;
include /opt/magento/public_html/nginx.conf.sample;
}

You will notice that we include the Magento nginx template configuration file at the end of the routing chain.

Let’s give a few parameters more to our Varnish by editing the following file:

Inside   /etc/default/varnish edit as following:DAEMON_OPTS=”-a :6081 \
-T localhost:6082 \
-f /etc/varnish/default.vcl \
-S /etc/varnish/secret \
-p http_resp_hdr_len=65536 \
-p http_resp_size=98304 \
-s malloc,256m”

Now it is time to restart all your services, flush the cache and we are done:

nginx -tsudo service restart varnishsudo service restart nginxservice httpd restart# go to magento folder and flush the cache
bin/magento cache:flush

Configuring Cloudfront CDN to serve media and static files

This part would need a full tutorial by itself. I basically followed one tutorial to set up my domain set records and cloudfront distributions. Please edit your nginx configuration file before following that tutorial.

https://www.atwix.com/magento-2/improve-performance-with-aws-cloudfront/

However the tricky part is that most of the tutorials you find online are providing configurations to address CORS for Apache but not Nginx.

To do that in our case we need to allow cross origin resources for static and media folders in our public folder.

Let’s first create a little snippet which will allow CORS by adding the right headers:

sudo nano /etc/nginx/magento2-cors.conf# content below:
add_header 'Access-Control-Allow-Origin' '*' 'always';

if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*' 'always';
add_header 'Access-Control-Allow-Headers' 'x-requested-with' 'always';
add_header 'Access-Control-Max-Age' 86400 'always';
add_header 'Content-Length' 0 'always';
return 204;
}

Go to your Magento folder and edit the file nginx.conf.sample . Scroll down until you find the following section. I put in bold the additions I made, which is basically referencing the snippet above in your static and media locations:

Static:

location /static/ {
# Uncomment the following line in production mode
# expires max;
# Remove signature of the static files that is used to overcome the browser cache
location ~ ^/static/version {
rewrite ^/static/(version\d*/)?(.*)$ /static/$2 last;
}
location ~* \.(ico|jpg|jpeg|png|gif|svg|js|css|swf|eot|ttf|otf|woff|woff2|json)$ {
add_header Cache-Control “public”;
add_header X-Frame-Options “SAMEORIGIN”;
include /etc/nginx/magento2-cors.conf;
expires +1y;
if (!-f $request_filename) {
rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$2 last;
}
}

location ~* \.(zip|gz|gzip|bz2|csv|xml)$ {
add_header Cache-Control “no-store”;
add_header X-Frame-Options “SAMEORIGIN”;
include /etc/nginx/magento2-cors.conf;
expires off;
if (!-f $request_filename) {
rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$2 last;
}
}

if (!-f $request_filename) {
rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$2 last;
}

add_header X-Frame-Options “SAMEORIGIN”;
include /etc/nginx/magento2-cors.conf;
}

Media:

location /media/ {
try_files $uri $uri/ /get.php$is_args$args;
location ~ ^/media/theme_customization/.*\.xml {
deny all;
}
location ~* \.(ico|jpg|jpeg|png|gif|svg|js|css|swf|eot|ttf|otf|woff|woff2)$ {
add_header Cache-Control “public”;
add_header X-Frame-Options “SAMEORIGIN”;
include /etc/nginx/magento2-cors.conf;
expires +1y;
try_files $uri $uri/ /get.php$is_args$args;
}
location ~* \.(zip|gz|gzip|bz2|csv|xml)$ {
add_header Cache-Control “no-store”;
add_header X-Frame-Options “SAMEORIGIN”;
include /etc/nginx/magento2-cors.conf;
expires off;
try_files $uri $uri/ /get.php$is_args$args;
}
add_header X-Frame-Options “SAMEORIGIN”;
include /etc/nginx/magento2-cors.conf;
}

You can now test and restart nginx, flush your cache and reset your varnish:

cd /opt/magento/public_html
bin/magento cache:flush
nginx -t
sudo service varnish restart
sudo service nginx restart

And finally go to Cloudfront>Distributions, and for both distributions, go to invalidation and enter /* then invalidate. This will make sure that your CDN served static assets gets refreshed with the one having the CORS header.


This is it. We have successfully installed Magento with an ssl certificate, http redirect to https, session cache with redis and full page cache with Varnish. We are delivering static and media files via CDN and fixed the CORS issue.

There are still many optimisation that you can do to speed up your magento, such as flat catalogs and products, font-display swap, etc… but you are already on a good start with this installation and will outperform a major part of the shops out there.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade