How to deploy an Angular/Node App on AWS EC2 (Linux 2 AMI) with SSL (CertBot)

Muhammad Ali Zia
5 min readFeb 14, 2022

In this tutorial, We will deploy a project based on Angular and Node.js on Amazon EC2 Linux 2 AMI. We will also install free SSL certificates using Lets Encrypt and Certbot. Furthermore, we will install PHPMyAdmin and configure access to it. So grab yourself a coffee because we are going live!

  • Run sudo yum update
  • Run sudo yum -y install httpd mod_ssl
  • Run sudo rpm -ivh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
  • Run sudo yum install -y certbot
  • Run sudo certbot certonly --standalone -d your_domain.com. Enter your email address to receive updates regarding your certificate. Enter y for all the prompts. This will generate a free certificate for your Frontend.
  • Run sudo certbot certonly --standalone -d backend.your_domain.com. Enter your email address to receive updates regarding your certificate. Enter y for all the prompts. This will generate a free certificate for your Backend. We are deploying the backend on a subdomain.
  • Run sudo systemctl start httpd
  • Create a separate directory for the Frontend by running sudo mkdir -p /var/www/html/your_domain/public_html
  • Create a separate directory for the Backend by running sudo mkdir -p /var/www/html/your_domain_backend/public_html
  • Set folder permissions by running sudo chmod -R 755 /var/www
  • Create a subdirectory to add server config by running sudo mkdir /etc/httpd/sites-available & sudo mkdir /etc/httpd/sites-enabled
  • Create a new virtual host file by running sudo nano /etc/httpd/sites-available/your_domain.conf
  • Paste the following code inside the your_domain.conf file. You can create separate virtual host files for Frontend & Backend, But I like to keep one virtual host file for the entire project.
Listen 8080

<VirtualHost *:8080>
ServerName ec2_public_ip
ServerAlias ec2_public_ip
ServerAdmin contact@your_domain.com

DocumentRoot /var/www/html/phpMyAdmin

<Directory /var/www/html/phpMyAdmin>
Options Indexes FollowSymLinks MultiViews
AllowOverride All
Require all granted
</Directory>
</VirtualHost>




<VirtualHost *:80>
ServerName your_domain.com
ServerAlias www.your_domain.com
ServerAdmin contact@your_domain.com

DocumentRoot /var/www/html/your_domain/public_html/

<Directory /var/www/html/your_domain/public_html>
Options Indexes FollowSymLinks MultiViews
AllowOverride All
Require all granted
</Directory>
Redirect permanent / https://your_domain.com/

</VirtualHost>
<VirtualHost *:443>
SSLEngine On
SSLCertificateFile /etc/letsencrypt/live/your_domain.com/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/live/your_domain.com/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/your_domain.com/fullchain.pem

ServerName your_domain.com
ServerAlias www.your_domain.com
ServerAdmin contact@your_domain.com

DocumentRoot /var/www/html/your_domain/public_html/

<Directory /var/www/html/your_domain/public_html>
Options Indexes FollowSymLinks MultiViews
AllowOverride All
Require all granted
</Directory>
</VirtualHost>

<VirtualHost *:80>
ServerName backend.your_domain.com
ServerAlias backend.your_domain.com
ServerAdmin contact@backend.your_domain.com

ProxyPreserveHost On
ProxyRequests Off
ProxyPass / http://127.0.0.1:3000/
ProxyPassReverse / http://127.0.0.1:3000/
Redirect permanent / https://backend.your_domain.com/
</VirtualHost>

<VirtualHost *:443>
SSLEngine On
SSLCertificateFile /etc/letsencrypt/live/backend.your_domain.com/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/live/backend.your_domain.com/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/backend.your_domain.com/fullchain.pem

ServerAdmin admin@backend.your_domain.com
ServerName backend.your_domain.com
ServerAlias www.backend.your_domain.com

ProxyPreserveHost On
ProxyRequests Off
ProxyPass / http://127.0.0.1:3000/ retry=1 acquire=3000 timeout=600 Keepalive=On
ProxyPassReverse / http://127.0.0.1:3000/
</VirtualHost>
  • Enable new virtual host file sudo ln -s /etc/httpd/sites-available/your_domain.conf /etc/httpd/sites-enabled/your_domain.conf
  • Goto cd /etc/httpd/conf and type sudo nano httpd.conf and at the end of the file add IncludeOptional sites-enabled/*.conf. Press ctrl+x -> y -> Enter to save
  • Restart server sudo systemctl restart httpd

Installing phpMyAdmin

  • Run sudo amazon-linux-extras install -y lamp-mariadb10.2-php7.2 php7.2
  • Run sudo systemctl enable httpd
  • Run sudo yum install php-mbstring php-xml -y
  • Run sudo systemctl restart httpd
  • Run sudo systemctl restart php-fpm
  • Run cd /var/www/html
  • Run sudo wget https://www.phpmyadmin.net/downloads/phpMyAdmin-latest-all-languages.tar.gz
  • Run sudo mkdir phpMyAdmin
  • Run sudo tar -xvzf phpMyAdmin-latest-all-languages.tar.gz -C phpMyAdmin --strip-components 1
  • Run sudo rm phpMyAdmin-latest-all-languages.tar.gz
  • Run sudo yum install mariadb-server -y
  • Run sudo systemctl start mariadb
  • Run sudo mysql_secure_installation
  • When prompted, type a password for the root account
  • Type the current root password. By default, the root account does not have a password set. Press Enter
  • Type Y to set a password, and type a secure password twice
  • Type Y to remove the anonymous user accounts
  • Type Y to disable the remote root login
  • Type Y to remove the test database
  • Type Y to reload the privilege tables and save your changes
  • Run sudo systemctl enable mariadb

Install utilities

  • Run sudo yum install git htop tmux -y
  • Run curl -sL https://rpm.nodesource.com/setup_16.x | sudo bash
  • Run sudo sudo yum install nodejs

Setup Backend

  • Goto home directory by running cd ~
  • Clone your Backend project by running cd your_project_name
  • Change directory by running cd your_project_directory
  • Copy your contents from the current directory to the public_html directory by running the following command sudo cp -a /home/ec2-user/your_project_backend_dir/* /var/www/html/your_domain_backend/public_html/
  • Run cd /var/www/html/your_domain_backend/public_html/
  • Install dependencies by running npm i
  • Now you need to set up your database and run migrations. For MySQL, use root as username, 127.0.0.1 as host, and the same password you used while installing phpMyAdmin
  • Run tmux to start a new session
  • Now start the project by running npm start
  • Press ctrl+b and then d to detach from the current session and keep it running in the background. You can connect to the session by running tmux attach -t session_id. Session id should be 0 if you have no other sessions running

Setup Frontend

  • Goto home directory by running cd ~
  • Clone your Frontend project by running git clone your_project.git
  • Change directory by running cd your_project_directory
  • Copy your contents from dist directory to the public_html directory by running the following command sudo cp -a /home/ec2-user/your_project_frontend_dir/dist/name_of_project/* /var/www/html/your_domain/public_html/
  • If you have 404 not found errors for routes then probably your .htaccess file is missing. You can create one and paste the following code into it
RewriteEngine on

# Don't rewrite files or directories
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]

# Rewrite everything else to index.html to allow html5 state links
RewriteRule ^ index.html [L]


# Enable Compression
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/vnd.ms-fontobject
AddOutputFilterByType DEFLATE application/x-font
AddOutputFilterByType DEFLATE application/x-font-opentype
AddOutputFilterByType DEFLATE application/x-font-otf
AddOutputFilterByType DEFLATE application/x-font-truetype
AddOutputFilterByType DEFLATE application/x-font-ttf
AddOutputFilterByType DEFLATE application/x-javascript
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE font/opentype
AddOutputFilterByType DEFLATE font/otf
AddOutputFilterByType DEFLATE font/ttf
AddOutputFilterByType DEFLATE image/svg+xml
AddOutputFilterByType DEFLATE image/x-icon
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/javascript
AddOutputFilterByType DEFLATE text/plain
</IfModule>
<IfModule mod_gzip.c>
mod_gzip_on Yes
mod_gzip_dechunk Yes
mod_gzip_item_include file .(html?|txt|css|js|php|pl)$
mod_gzip_item_include handler ^cgi-script$
mod_gzip_item_include mime ^text/.*
mod_gzip_item_include mime ^application/x-javascript.*
mod_gzip_item_exclude mime ^image/.*
mod_gzip_item_exclude rspheader ^Content-Encoding:.*gzip.*
</IfModule>

# Leverage Browser Caching
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/jpg "access 1 year"
ExpiresByType image/jpeg "access 1 year"
ExpiresByType image/gif "access 1 year"
ExpiresByType image/png "access 1 year"
ExpiresByType text/css "access 1 month"
ExpiresByType text/html "access 1 month"
ExpiresByType application/pdf "access 1 month"
ExpiresByType text/x-javascript "access 1 month"
ExpiresByType application/x-shockwave-flash "access 1 month"
ExpiresByType image/x-icon "access 1 year"
ExpiresDefault "access 1 month"
</IfModule>
<IfModule mod_headers.c>
<filesmatch "\.(ico|flv|jpg|jpeg|png|gif|css|swf)$">
Header set Cache-Control "max-age=2678400, public"
</filesmatch>
<filesmatch "\.(html|htm)$">
Header set Cache-Control "max-age=7200, private, must-revalidate"
</filesmatch>
<filesmatch "\.(pdf)$">
Header set Cache-Control "max-age=86400, public"
</filesmatch>
<filesmatch "\.(js)$">
Header set Cache-Control "max-age=2678400, private"
</filesmatch>
</IfModule>

It is LIVE!

Now you can access the Frontend at your_domain.com and phpMyAdmin at ec2_public_ip:8080. To monitor hardware usage, you can type htop to view the task manager (sorta task manager). Now pat yourself in the back because you just deployed a project. Happy coding! :)

--

--