Wordpress using Mysql on AWS

Sahana B
10 min readOct 14, 2020

1. Write an Infrastructure as code using terraform, which automatically create a VPC.

2. In that VPC we have to create 2 subnets:

1. public subnet [ Accessible for Public World! ]

2. private subnet [ Restricted for Public World! ]

3. Create a public facing internet gateway for connect our VPC/Network to the internet world and attach this gateway to our VPC.

4. Create a routing table for Internet gateway so that instance can connect to outside world, update and associate it with public subnet.

5. Create a NAT gateway for connect our VPC/Network to the internet world and attach this gateway to our VPC in the public network

6. Update the routing table of the private subnet, so that to access the internet it uses the nat gateway created in the public subnet

7. Launch an ec2 instance which has Wordpress setup already having the security group allowing port 80 sothat our client can connect to our wordpress site. Also attach the key to instance for further login into it.

8. Launch an ec2 instance which has MYSQL setup already with security group allowing port 3306 in private subnet so that our wordpress vm can connect with the same. Also attach the key with the same.

Note: Wordpress instance has to be part of public subnet so that our client can connect our site.

WordPress is a free and open-source content management system written in PHP and paired with a MySQL database. It caters to business online development initiative with content management system facilities. WordPress allows the users to build a highly customizable website of their preference, and this manner makes it popular.

Usually, if the Wordpress Web portal is to be deployed for Company’s Official perspectives, it needs Cloud services for large scale contents. We have to create a web portal for our company with all the security as much as possible.

So, we use the WordPress software with a dedicated database server. The database should not be accessible from the outside world for security purposes. We only need the public WordPress for clients. We will also use BASTION HOST for more security.

For understanding the project, you need some prior knowledge that has been discussed in my previous articles.

So, let’s proceed ahead

AWS Configure

AWS Access Key ID [********************]:
AWS Secret Access Key [********************]:
Default region name [ap-south-1]:
Default output format [None]:

We configured our aws cli so that terraform can access the AWS account.

Module Approach

A module is a container for multiple resources that are used together. Modules can be used to create lightweight abstractions, so that you can describe your infrastructure in terms of its architecture, rather than directly in terms of physical objects.

The .tf files in your working directory when you run terraform plan or terraform apply together form the root module. That module may call other modules and connect them together by passing output values from one to the input values of another.

We will use this approach for better management of the project.

main.tf

provider "aws" {
region = "ap-south-1"
# profile = "thegreat"
}module "aws_code" {
source = "./aws"

}

This is the main file which has information regarding the provider and location of other modules.

key.tf

resource "tls_private_key" "prkey" {
algorithm = "RSA"
}resource "aws_key_pair" "webkey1" {
key_name = "webkey1"
public_key = tls_private_key.prkey.public_key_openssh
}

This will create key pair which will help in doing ssh to the instances if required

vpc.tf

resource "aws_vpc" "vpc" {
cidr_block = "192.168.0.0/16"
instance_tenancy = "default"
enable_dns_hostnames = true tags = {
Name = "web_vpc"
}
}

A virtual private cloud (VPC) is a virtual network dedicated to your AWS account. It is logically isolated from other virtual networks in the AWS Cloud. You can launch your AWS resources, such as Amazon EC2 instances, into your VPC. A VPC spans all of the Availability Zones in the Region.

This will create a VPC.

subnets.tf

resource "aws_subnet" "public_subnet" {
depends_on=[aws_vpc.vpc]
vpc_id = "${aws_vpc.vpc.id}"
availability_zone = "ap-south-1a"
cidr_block = "192.168.0.0/24"
map_public_ip_on_launch = true tags = { Name = "public_subnet"
}
}resource "aws_subnet" "private_subnet" {
depends_on=[aws_vpc.vpc]
vpc_id = "${aws_vpc.vpc.id}"
availability_zone = "ap-south-1a"
cidr_block = "192.168.1.0/24"
#map_public_ip_on_launch = true tags = { Name = "private_subnet"
}
}

A subnet is a logical subdivision of an IP network. The practice of dividing a network into two or more networks is called subnetting.

Here, we create two subnets i.e. Public and Private

Public subnet will be accessible to outside world

Private subnet will not be accessible to outside world

internet.tf

resource "aws_internet_gateway" "int_gate" {
vpc_id = "${aws_vpc.vpc.id}" tags = {
Name = "web_gate"
}
}resource "aws_route_table" "public_route_table" {
depends_on=[aws_subnet.public_subnet]
vpc_id = "${aws_vpc.vpc.id}" route {
cidr_block = "0.0.0.0/0"
gateway_id = "${aws_internet_gateway.int_gate.id}"
} tags = {
Name = "public_route_table"
}
}resource "aws_route_table_association" "rt_association" {
subnet_id = aws_subnet.public_subnet.id
route_table_id = aws_route_table.public_route_table.id
}

Internet gateway will connect our VPC to the internet world and attach this gateway to our VPC.

Routing table for Internet gateway so that instance can connect to the outside world, update and associate it with the public subnet.

nat.tf

NAT Gateway is a highly available AWS managed service that makes it easy to connect to the Internet from instances within a private subnet in an Amazon Virtual Private Cloud (Amazon VPC)

resource "aws_eip" "eip" {
vpc = true
public_ipv4_pool = "amazon"
}output "new_output" {
value= aws_eip.eip
}resource "aws_nat_gateway" "nat" {
depends_on=[aws_eip.eip]
allocation_id = aws_eip.eip.id
subnet_id = aws_subnet.public_subnet.idtags = {
Name = "nat"
}
}resource "aws_route_table" "private_route_table" {
depends_on=[aws_nat_gateway.nat]
vpc_id = "${aws_vpc.vpc.id}"route {
cidr_block = "0.0.0.0/0"
gateway_id = "${aws_nat_gateway.nat.id}"
}tags = {
Name = "private_route_table"
}
}resource "aws_route_table_association" "private_rt_association" {
depends_on=[aws_route_table.private_route_table]
subnet_id = aws_subnet.private_subnet.id
route_table_id = aws_route_table.private_route_table.id
}

We want the private subnet instance to connect to public network as we need to update some Softwares or updae some software, so that instance should be able to connect to the outside world and for that, we need to have SNAT enabled, so as the Source Ip of the Instance which is private is translated to public IP of the router so that it can connect to the public network but DNAT is not enabled, it means nobody from public world can connect to that instance.

Elastic IP is created

NAT Gateway

Route Table for Private Subnet

sg.tf

resource "aws_security_group" "public" {
depends_on=[aws_subnet.public_subnet]
vpc_id = "${aws_vpc.vpc.id}"
name = "public_subnet"
description = "public_subnet"ingress {
description = "allow_http"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]}ingress {
description = "allow_https"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]}ingress {
description = "allow_ssh"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]}ingress {
description = "allow_icmp"
from_port = -1
to_port = -1
protocol = "icmp"
cidr_blocks = [ "0.0.0.0/0" ]
ipv6_cidr_blocks = ["::/0"]}egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]}tags = {
Name = "public_subnet"
}
}

This security group will allow to connect to website(port 80 and 443), ssh and icmp(for pinging)

resource "aws_security_group" "bositon_ssh" {
depends_on=[aws_subnet.public_subnet]
name = "bositon_ssh"
description = "Allow ssh bositon inbound traffic"
vpc_id = aws_vpc.vpc.idingress {
description = "allow_ssh"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}tags = {
Name = "bositon_ssh"
}
}

This security group will allow ssh.

resource "aws_security_group" "sql_web" {
depends_on=[aws_subnet.public_subnet]
name = "sql_web"
description = "Allow only sql inbound traffic"
vpc_id = aws_vpc.vpc.idingress {
description = "mysql_port"
from_port = 3306
to_port = 3306
protocol = "tcp"
security_groups=[aws_security_group.public.id]

}ingress {
description = "allow_icmp"
from_port = -1
to_port = -1
protocol = "icmp"
security_groups=[aws_security_group.public.id]
ipv6_cidr_blocks=["::/0"]

}egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}tags = {
Name = "sql_web"
}
}

This will allow to connect to mysql and allow icmp.

resource "aws_security_group" "sql_bositon_ssh" {
depends_on=[aws_subnet.public_subnet]
name = "only_ssh_sql_bositon"
description = "Allow ssh only from bositon"
vpc_id = aws_vpc.vpc.idingress {
description = "allow_ssh"
from_port = 22
to_port = 22
protocol = "tcp"
security_groups=[aws_security_group.bositon_ssh.id]

}egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}tags = {
Name = "sql_bositon_ssh"
}
}

This will allow ssh but from the instance only which has bositon_ssh security group.

ec2.tf

I had created AMI for WordPress and MYSQL and I am going to use that AMI for the task. You can make one for yourself or use pre-created one.

Wordpress

resource "aws_instance" "wordpress" {
depends_on=[aws_security_group.public, aws_key_pair.webkey1]
ami = "ami-0f37a73ce5f0d08fc"
instance_type = "t2.micro"
availability_zone = "ap-south-1a"
key_name = "webkey1"
subnet_id = "${aws_subnet.public_subnet.id}"
security_groups = ["${aws_security_group.public.id}"]

tags = {
Name = "wordpress"
}
}

MySQL

resource "aws_instance" "mysql" {
depends_on=[aws_security_group.sql_web,aws_security_group.sql_bositon_ssh, aws_key_pair.webkey1]
ami = "ami-0415c57c5a21a4d71"
instance_type = "t2.micro"
subnet_id= aws_subnet.private_subnet.id
vpc_security_group_ids = [aws_security_group.sql_web.id ,aws_security_group.sql_bositon_ssh.id]
key_name = "webkey1"tags = {
Name = "mysql"
}
}

BASTION HOST

A bastion host is a special-purpose computer on a network specifically designed and configured to withstand attacks. The computer generally hosts a single application, for example, a proxy server, and all other services are removed or limited to reduce the threat to the computer.

I have launched an instance in the public subnet and attached a security group that will allow only the ssh from the public world to that instance and then created a security group for Mysql instance which will allow ssh from that Bastion host only. In this way, we can make the instance running in the Private subnet to be more secure.

resource "aws_instance" "bositon_host" {
depends_on=[aws_security_group.bositon_ssh, aws_key_pair.webkey1]
ami = "ami-0732b62d310b80e97"
instance_type = "t2.micro"
subnet_id = "${aws_subnet.public_subnet.id}"
vpc_security_group_ids = ["${aws_security_group.bositon_ssh.id}"]
key_name = "webkey1"tags = {
Name = "bositon_host"
}
}

We need to do some configuration before proceding. We have to configure databse for wordpress in MySql. We have to import key into bastion_host because we can do ssh into mysql only from bastion_host

After that it will look something like this in wordpress and mysql resp.

We can see that after enabling NAT gateway, MySQL is able to ping to the outside world

How to run the codes in modules

  1. We will run terraform init command where our main.tf file is present
  2. We will run terraform plan command to see the execution plan. Terraform performs a refresh, unless explicitly disabled, and then determines what actions are necessary to achieve the desired state specified in the configuration files.
  3. Terraform apply -auto-approve command is used to apply the changes required to reach the desired state of the configuration, or the predetermined set of actions generated by a terraform plan execution plan. We use -auto-approve command, so that confirmation will not be asked
  4. Terraform destroy -auto-approve command is used to destroy the Terraform-managed infrastructure.

Output

--

--