In this note, I detail all the steps required to create a bare-bone web server on Amazon EC2. I discuss creating the Amazon Virtual Private Cloud, subnets, internet gateway, security group, and Amazon EC2 instances to finally automate the process via Terraform and user data. Note: I did not include the concepts of load balancing, installing a certificate, or route53 in this note. At the end of this note, I have a link to my following note, where I added an application load balancer into the configuration.
A few YouTube videos or blogs are available on how to host a website on an Amazon EC2 instance. However, those were cases where the Amazon EC2 was hosted on the default VPC or created via the AWS console. There is nothing wrong with that. But I wanted to create the web servers right from the ground up, and that too using Terraform; hence, to capture my thought process, I came up with the idea of writing this note.
You will need a basic understanding of Terraform, Amazon VPC, subnets, internet gateway, security groups, route table, Amazon EC2, and user data to follow along. If you are new to these concepts, you can read about getting started with Terraform and how to create an Amazon EC2 instance and underlying infrastructure (like vpc, subnets, etc) using Terraform.
If you want to walk along, I have the code committed to my GitHub repository: add-aws-elb-ec2-terraform.
Note: I updated the code repository with a loadbalancer.tf
later since I was using the same repository to work on the load balancer note (link shared at the end of this note). Creating a load balancer with the Amazon EC2 instances would not hurt. However, when you start working with the repository and if you do not want to explore load balancers, you can comment out the file loadbalancer.tf
and the last three lines from output.tf
.
I broke down this use case into four steps.
Step 1: Create the network components
I created a pretty small VPC since this was a proof of concept. Notably, I set two flags to true. You can read them at AWS-Docs: Setting Up DNS in Your VPC. The rest of the infrastructure bits are standard. If you want to know more, please refer to this note.
UPDATE: As of March 2025, the network stack was converted to consume from a VPC module instead of the Terraform configuration, as shown below.
Step 2: Create a security group
Since this was a proof of concept and I did not require HTTPS traffic, I did not bother creating more than necessary.
Step 3: Create the user data file
User data is a feature that allows customization of Amazon EC2 (virtual machine) when provisioned and (if desired) with each restart. I was required to install the
httpd service
and update the index.html
page in this use case. I have a detailed note on user data at working-with-amazon-ec2-user-data-and-terraform.
Step 4: Create an Amazon EC2 instance with a user data file
This is the last step, where I provisioned three Amazon EC2 instances using the infrastructure bits discussed earlier. An interesting observation is that since I required the instances accessible from outside the VPC, I had to associate them with a public IP address.
With all the above code, I ran the usual Terraform commands of terraform apply
(after initializing the repository). And after Terraform provisioned the resources, I received the message below. That is due to the specification in the output block (in the output.tf
file).
These are the public DNS of the three Amazon EC2 instances that Terraform provisioned.
However, on the AWS console, I found the two Amazon EC2 instances were still initializing. Terraform does not manage the state of the Amazon EC2 instance after it provisions them, and the control passes over to the user data file. Stated differently, Terraform does not track whether the user data script was successful. However, I was confident that the user-data file I provided was correct (I’d run this code more than a few dozen times in the past). Therefore, I waited for the instances to pass the status checks before I tried to access the public DNS that Terraform provided.
Since I did not install a certificate on the Amazon EC2 instances, HTTPS was not going to work, and I tried the HTTP URL, which was
HTTP://$(the public DNS provided in the output)
And this is what I saw for all three public DNS addresses.
After working on this repository, I added a load balancer and updated the security group of the EC2 instances to accept requests only from the load balancer’s security group. Please note that after adding the load balancer, you can’t access the below message using the Amazon EC2 instance’s DNS because the updated security group rule will restrict that.
The “Not secure” right before the public DNS name denotes that this is an HTTP URL.
This concludes this note on how to provision a web server on an Amazon EC2 instance using Terraform and user data.
If you are familiar with Terraform, go ahead and fork the repository and try it. Then, let me know if you face any challenges. I also added an application load balancer to the three Amazon EC2 instances in my subsequent note at -add-an-application-load-balancer-to-amazon-ec2-using-terraform.
Please note that I did not discuss the security aspect of managing the secret key
and access key
in this note. There are two methods I am comfortable with: storing the values in the terraform.tfvars
file OR passing them through the command line with terraform command
. Depending on your use case and security posture, you can decide which option is secure and manageable.