Deploy an Apache Web Server on AWS EC2 Using Terraform
Written by Kenechi Dukor (opens in a new tab)
If you already know how to launch an EC2 instance manually, Terraform helps you do the same thing using code. Instead of clicking around the AWS console, you describe what you want and Terraform creates it for you.
In this guide, you will use Terraform to:
- Create an EC2 instance
- Allow web and SSH traffic
- Install Apache automatically
- Access the web server from a browser
This is a common starting point when learning Infrastructure as Code.
What You Need Before You Start
Make sure you have the following ready:
- Terraform installed
- AWS CLI installed and authenticated
- An existing VPC and subnet
- An existing EC2 key pair in AWS
Terraform will reference the key pair by name. It does not create one in this setup.
Step 1: Set Up the Project Folder
Create a new folder and add these files:
terraform-ec2-apache/
├── main.tf
├── variables.tf
├── outputs.tf
└── user_data.shEach file has a single purpose. This keeps things easy to follow.
Step 2: Define Inputs with Variables
The variables.tf file holds values you may want to change later, such as region or instance type.
variable "aws_region" {
type = string
default = "us-west-2"
}
variable "instance_type" {
type = string
default = "t2.micro"
}
variable "key_name" {
type = string
}
variable "vpc_id" {
type = string
}
variable "subnet_id" {
type = string
}You will pass key_name, vpc_id, and subnet_id when running Terraform.
Step 3: Add a Startup Script for Apache
The user_data.sh file runs when the EC2 instance starts. It installs and starts Apache without manual setup.
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Hello from Terraform</h1>" > /var/www/html/index.htmlThis is how the server becomes usable immediately after launch.
Step 4: Define the Infrastructure
All AWS resources are defined in main.tf.
AWS Provider
provider "aws" {
region = var.aws_region
}Security Group
This allows HTTP traffic for the website and SSH for access.
resource "aws_security_group" "web_sg" {
vpc_id = var.vpc_id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}Get the Amazon Linux AMI
Terraform fetches the latest Amazon Linux 2 image automatically.
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
}EC2 Instance
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux.id
instance_type = var.instance_type
key_name = var.key_name
subnet_id = var.subnet_id
vpc_security_group_ids = [aws_security_group.web_sg.id]
user_data = file("user_data.sh")
tags = {
Name = "terraform-apache"
}
}At this point, Terraform knows how to create the server.
Step 5: Expose the Result
The outputs.tf file prints useful information after deployment.
output "public_ip" {
value = aws_instance.web.public_ip
}
output "web_url" {
value = "http://${aws_instance.web.public_dns}"
}Step 6: Deploy with Terraform
Run the following commands in order:
terraform initterraform plan \
-var="key_name=your-key-name" \
-var="vpc_id=your-vpc-id" \
-var="subnet_id=your-subnet-id"terraform apply \
-var="key_name=your-key-name" \
-var="vpc_id=your-vpc-id" \
-var="subnet_id=your-subnet-id"Type yes when prompted.
Step 7: Access the Web Server
Once Terraform finishes, copy the web_url output and open it in a browser.
You should see the Apache page created in the startup script.
Step 8: Cleaning Up
When you no longer need the resources:
terraform destroy \
-var="key_name=your-key-name" \
-var="vpc_id=your-vpc-id" \
-var="subnet_id=your-subnet-id"Terraform removes everything it created.
Wrap Up
This walkthrough shows how Terraform replaces manual EC2 setup with a repeatable workflow. The same pattern works for APIs, internal tools, and test environments.
From here, you can:
- Add HTTPS
- Pull application code in user_data.sh
- Convert this into a reusable Terraform module
- Introduce remote state and environments