Your LAMP stack on AWS EC2

deploying EC2 instances on a VPC

Sun, 26 Jan 2020

In Terraforming your fist AWS VPC we built our fist VPC, now let’s deploy a compute unit inside our brand new Virtual Private Cloud!


Disclaimer: to follow this tutorial make sure you’ve already completed Terraforming your fist AWS VPC!

Creating a security group

When we deal with instances inside an AWS VPC there’s a tool for securing and controlling our network. But what is an SG?

A security group acts as a virtual firewall for your instance to control inbound and outbound traffic.

While Network Access list Control (NACL) does control your inbound and outbound network at subnet level, Security Groups apply at instance level.

This allows fine-grained security rules for traffic and communication in your private cloud instances and services.

For our instance, we will define an SG that will allow us to talk with our instance via SSH and allow both HTTP and Secure TLS (https) transport for accessing a web-server exposed in our instance.

resource "aws_security_group" "sgPublicEC2Instances" {
  vpc_id = // this vpc id references the vpc we are working with
  tags = {
    Name = "PublicEc2Sg"
  ingress {
    description = "allow TLS inbound traffic"
    from_port   = 443
    to_port     = 443
    protocol = "tcp"
    cidr_blocks = [""]
  ingress {
    description = "allow regular http inbound traffic"
    from_port = 80
    protocol = "tcp"
    to_port = 80
    cidr_blocks = [""]
  ingress {
    description = "allow ssh from my network"
    from_port = 22
    protocol = "tcp"
    to_port = 22
    cidr_blocks = ["XX.XX.XX.XX/32"] // replace XX.XX.XX.XX with your ip, or for any ip
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = [""]

Replace ingress on port 22 “allow ssh from my network” cidrblocks with your IP addresses, to allow access via SSH only from your IP._

Defining an EC2 instance

First of all, we need to choose an AMI for launching new instances.

An Amazon Machine Image (AMI) is a special type of virtual appliance that is used to create a virtual machine within the Amazon Elastic Compute Cloud (“EC2”). It serves as the basic unit of deployment for services delivered using EC2

For our tutorial, we will use Amazon Linux 2 that provides an optimized system for running EC2 instances. Amazon Linux AMI’s comes with pre-installed AWS agents and CLI, and some supported third party packages from amazon-linux-extras. For most generic workflow it’s a good start point, but for some projects you might want to look at other AMI’s from both official listing and Marketplace.

From the AWS FAQ:

The Amazon Linux AMI is a supported and maintained Linux image provided by Amazon Web Services for use on Amazon Elastic Compute Cloud (Amazon EC2).

To find the correct ami, browse the EC2 listing and copy the AMI id.

amazon ami listing

Before getting into the instance definition we need to create a key pair for ssh into our instance to be referenced later on in terraform. Visit EC2 in AWS console and at key pairs, create a new one:

key pair

Choose the appropriate key for you, either openSSH or putty. If you’re on Windows 10, you can still use openSSH because it’s now available. (try use ssh command in your CMD or PowerShell).

Download the generated key pair to your local computer, we will use it later on.

Now let’s define our instance in our terraform file. We will create a t2.micro that is eligible for free tier. The instance will be associated to the public subnet publicSubnet1a we created earlier and attached to the Security Group defined above.

Make sure to reference your downloaded key name in the instance definition below.

resource "aws_instance" "Ec2Public1" {
  ami = "ami-07cda0db070313c52"
  instance_type = "t2.micro"
  subnet_id =
  vpc_security_group_ids = []
  tags = {
    Name = "Ec2Public1"
  associate_public_ip_address = true
  key_name = "ec2 tutorial 1" // this is your key pair name

Installing apache server

For installing new packages and services on an AMI, we can use something called “User data”. User data are script that will be launched right after instance initialization and usually is where you programmatically define system dependencies, attach volumes or install required software packages.

First create the script file:

cd /project_files

Now we write our User data script where we will install php7.2 from amazon-linux-extras and httpd. We will also chmod /var/wwwand make ec2-user, the ec2 instance user, the owner of /var/www to allow writes. (default is root)

## install dependencies
sudo yum update -y
sudo amazon-linux-extras install -y php7.2
sudo yum install -y httpd

## start apache
sudo systemctl start httpd
sudo systemctl enable httpd
sudo systemctl is-enabled httpd

## add ec2-user permissions to /var/www and apache group
sudo usermod -a -G apache ec2-user
sudo chown -R ec2-user:apache /var/www
sudo chmod 2775 /var/www && find /var/www -type d -exec sudo chmod 2775 {} \;
find /var/www -type f -exec sudo chmod 0664 {} \;

There are several way to add userdata to our terraform file. For simple stuff, you can just write a string and use <<EOF EOF syntax to write multi-line strings. Better, use a built-in function like file()

user_data = "${file("")}"

or creating a template data, where you can also pass environment variables to be marshalled.

data "template_file" "user_data" {
  template = file("${path.module}/") // rename your file to .tpl
  vars = {

To use template in your instance declaration, use the ‘rendered’ attribute.

user_data = data.template_file.user_data.rendered

Let’s test it out!

in your shell, run

terraform apply

Since we have associated an Elastic IP ( associate_public_ip_address = true), we can check the resulting ip address from the AWS console. (from console menu: EC2 -> Elastic IP)


If it’s all right you will see apache default page at the provisioned IP address directly from your browser:


Of course your Elastic IP will be different, and if you don’t reserve one a new ip is provisioned by AWS for you at any new launch.

But wait, we also defined an SSH rule in our Security group.. How we do access our instances?

For accessing via SSH EC2 will require a key pair to be generated or imported.

In our EC2 instance configuration we referenced an existing key pair: ec2 tutorial 1, you should have created one from the aws console. To ssh into our instance:

ssh -i keypair.pem ec2-user@xx.xx.xx.xx

Where keypair.pem is our key pair and xx.xx.xx.xx is the elastic IP associated with our instance.

If everything is ok, your shell output will looks like this:


When your’e done, you can teardown all the created stuff by using:

terraform destroy

Was that easy?


  • We created a Security Group
  • We created a key pair for SSH access to our EC2 instances
  • We created our fist EC2 instance
  • We created an User data script to install apache
  • We successfully published a web-sever to the internet
  • We accessed our EC2 instance via SSH

Keep in mind that:

  • You might restrict SSH access only to known ip address ranges (like your VPN)
  • You might incur in fees if your instance it’s not eligible for free tier or you pass the monthly hour limit
  • UserData script are limited in size for complex startup you may want to build custom AMI’s

The diagram above represents what we built so far.

What’s next

In the next chapter, we will deploy a database instance (Mysql / Maria DB) inside our private subnet, and we will allow our Web-tier app to communicate with it. We will also configure a NAT instance to allow egress-only connectivity for our private instances.

Yuri Blanc

Yuri Blanc - FullStack developer I’m a web developer with love and passion. My main focus is about Java (J2EE), TypeScript, Angular 2+, React, NodeJS, Kotlin, Cloud Architect Solutions. I’m someone usually described as “passionate” and “creative” in all things related to information technologies!

© Yuri Blanc