Deploy an Apache Web Server on AWS EC2 Using CloudFormation
Written by Demola Malomo (opens in a new tab)
AWS CloudFormation allows you to define and provision AWS infrastructure using code. Instead of clicking around the AWS console every time you want a server, you describe what you need in a template, and AWS handles the rest.
In this guide, we will deploy a single EC2 instance running an Apache web server using CloudFormation. By the end, you will understand how the template works and how the EC2 instance is configured automatically during launch.
What CloudFormation Actually Does
Normally, creating a server on AWS involves many manual steps:
- Create a security group
- Launch an EC2 instance
- Install Apache
- Open port 80
- Configure files
CloudFormation lets you describe all of this in one text file. AWS reads that file and creates everything for you in the correct order.
That text file is called a CloudFormation template. Before creating this template, there are few concepts you should understand. Let's explore them next.
Core CloudFormation Concepts You Should Know
Before jumping into the template, it helps to understand a few basic terms. You do not need to master them yet. Just enough to follow along.
-
Template: A YAML or JSON file that describes the AWS resources you want to create.
-
Stack: A running instance of a template. When you deploy a template, CloudFormation creates a stack.
-
Resources: The actual AWS services being created, such as EC2 instances, security groups, or IAM roles.
-
Parameters: Values you pass into the template to customize it, such as instance type or VPC ID.
-
Outputs: Values CloudFormation returns after the stack is created, like the instance ID or public URL.
-
AWS::CloudFormation::Init: A special metadata section used to install software and configure EC2 instances.
-
Helper scripts: Tools like
cfn-initandcfn-signalthat apply configurations and report success back to CloudFormation.
What We Are Building
This template will:
- Launch an EC2 instance inside a VPC and subnet you choose
- Open ports 22 and 80 using a security group
- Install Apache automatically during boot
- Create a simple HTML page
- Output a public URL you can open in your browser
Step 1: Create the CloudFormation Template File.
CloudFormation does not generate the template for you. You create it yourself. You can create the template using any text editor, such as Notepad, Sublime Text, or Visual Studio Code.
To get started, create a file named ec2-apache-stack.yaml.
Note that CloudFormation supports YAML and JSON. YAML is easier to read, so we will use that.
Step 2: Paste This Full, Correct Template
Paste everything below into the file you just created.
This template already includes all fixes and best practices needed for a first deployment.
AWSTemplateFormatVersion: '2010-09-09'
Description: Deploy an EC2 instance with Apache using AWS CloudFormation
Parameters:
InstanceType:
Type: String
Default: t3.micro
AllowedValues:
- t2.micro
- t3.micro
Description: EC2 instance type
KeyName:
Type: AWS::EC2::KeyPair::KeyName
Description: Existing EC2 KeyPair for SSH access
SSHLocation:
Type: String
Default: 0.0.0.0/0
Description: IP range allowed to SSH to the instance
VpcId:
Type: AWS::EC2::VPC::Id
Description: VPC where the instance will be deployed
SubnetId:
Type: AWS::EC2::Subnet::Id
Description: Subnet where the instance will be deployed
Resources:
WebServerSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allow HTTP and SSH access
VpcId: !Ref VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: !Ref SSHLocation
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
WebServerRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
Path: /
WebServerInstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Path: /
Roles:
- !Ref WebServerRole
WebServerInstance:
Type: AWS::EC2::Instance
DependsOn: WebServerInstanceProfile
Metadata:
AWS::CloudFormation::Init:
config:
packages:
yum:
httpd: []
files:
/var/www/html/index.html:
content: |
<html>
<head>
<title>Apache on EC2</title>
</head>
<body>
<h1>Apache is running</h1>
<p>This server was deployed using AWS CloudFormation.</p>
</body>
</html>
mode: '000644'
owner: root
group: root
services:
sysvinit:
httpd:
enabled: true
ensureRunning: true
Properties:
InstanceType: !Ref InstanceType
KeyName: !Ref KeyName
SubnetId: !Ref SubnetId
SecurityGroupIds:
- !Ref WebServerSecurityGroup
IamInstanceProfile: !Ref WebServerInstanceProfile
ImageId: !Sub '{{resolve:ssm:/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2}}'
UserData:
Fn::Base64: !Sub |
#!/bin/bash
yum update -y
yum install -y aws-cfn-bootstrap
/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource WebServerInstance --region ${AWS::Region}
systemctl restart httpd
Outputs:
WebsiteURL:
Description: Public URL of the Apache web server
Value: !Sub http://${WebServerInstance.PublicDnsName}
InstanceId:
Description: EC2 Instance ID
Value: !Ref WebServerInstanceI know this is a lot to take in, but don't worry. We will break it down step by step. At a high level, the template is divided into:
- Parameters for customization
- Resources such as the EC2 instance and security group
- Metadata used to install Apache
- Outputs that show useful information after deployment
Parameters Section
This is where CloudFormation asks you for values during deployment.
You will be prompted to:
- Choose an EC2 instance type
- Select an existing key pair
- Choose a VPC and subnet
- Specify which IP can SSH into the server
You do not hardcode these values.
Security Group
The security group:
- Allows SSH on port 22
- Allows web traffic on port 80
Without this, the server would exist but be unreachable.
IAM Role and Instance Profile
These allow the EC2 instance to:
- Use AWS helper scripts
- Be managed via Systems Manager
No passwords or keys are stored on the server.
EC2 Instance and Apache Setup
This is where you might get confused, so here is the flow in plain language:
- EC2 starts
- UserData runs first
- UserData installs CloudFormation helper tools
cfn-initreads theAWS::CloudFormation::Initsection- Apache is installed
- A web page is created
- Apache is started
Remember that nothing is manual.
AMI Selection
Instead of hardcoding AMI IDs, this template pulls the latest Amazon Linux 2 AMI automatically using SSM. This avoids broken templates when AWS updates AMIs.
Outputs
After deployment, CloudFormation shows:
- A clickable website URL
- The EC2 instance ID
Step 3: Deploy the Stack
You can deploy the stack using either the AWS Console or the AWS CLI. If you are new to CloudFormation, the console is usually easier the first time.
Deploying via the AWS Management Console
-
Open the AWS Console and go to CloudFormation (opens in a new tab).
-
Click Create stack, then choose With new resources.
-
Upload your
ec2-apache-stack.yamlfile. -
Give the stack a name, for example
apache-web-server. -
Fill in the required parameters:
- Choose an instance type
- Select an existing EC2 key pair
- Choose a VPC and subnet
- Restrict SSH access to your IP if possible
-
Review the settings and create the stack.
CloudFormation will begin creating resources immediately.
Deploying via the AWS CLI
Before running the command below, make sure you have the AWS CLI installed and configured:
- Install the AWS CLI: Follow the official instructions (opens in a new tab) for your operating system.
- Configure Credentials (opens in a new tab): Run
aws configureand enter your Access Key, Secret Key, and default Region. - Verify: Run
aws sts get-caller-identityto confirm you are authenticated.
Once ready, run the following command to create the stack:
aws cloudformation create-stack \
--stack-name apache-web-server \
--template-body file://ec2-apache-stack.yaml \
--parameters \
ParameterKey=InstanceType,ParameterValue=t2.micro \
ParameterKey=KeyName,ParameterValue=your-key-name \
ParameterKey=SSHLocation,ParameterValue=your-ip-address/32 \
ParameterKey=VpcId,ParameterValue=your-vpc-id \
ParameterKey=SubnetId,ParameterValue=your-subnet-id \
--capabilities CAPABILITY_IAMStep 4: Monitor Stack Creation
While the stack is being created:
- Open the stack in the CloudFormation console
- Check the Events tab to see progress
- Wait until the status changes to
CREATE_COMPLETE
If something fails, the Events tab will usually explain why.
Step 5: Access the Web Server
Once the stack completes:
- Go to the Outputs tab
- Copy the
WebsiteURL - Paste it into your browser
You should see the Apache welcome page generated by the template.
Step 6: Deploy Your Own Application (Optional)
You can extend this template depending on what you want to host.
-
Static websites: Add more files to the
configure_apachesection. -
PHP applications: Install PHP using
AWS::CloudFormation::Initand place PHP files under/var/www/html. -
Node.js applications: Install Node.js in
UserDataand run your application as a service. -
Clone from Git: Install Git and clone your repository during boot.
Step 7: Clean Up
When you no longer need the server, delete the stack. Deleting the stack removes all resources created by it, including the EC2 instance and security group.
CloudFormation Best Practices
Below are some best practices to follow when using CloudFormation:
- Use
AWS::CloudFormation::Initfor software setup instead of large UserData scripts - Avoid hardcoding credentials, always use IAM roles
- Restrict SSH access to trusted IPs
- Use change sets before updating production stacks
- Separate long-lived resources and short-lived resources into different stacks
Conclusion
Using CloudFormation to deploy EC2 with Apache removes guesswork from infrastructure setup. Once the template works, you can reuse it, version it, and extend it without repeating manual steps.
This approach is especially useful as your infrastructure grows or when you want consistent environments across teams and projects.