Skip to content disloops

WordPress in AWS

This site is being hosted in Amazon Web Services (AWS). It relies on a number of cloud services, including RDS, S3, and CloudFront. The following are some of the steps that were required to set it up.

Provisioning a Server

Create an AWS account if you don't already have one and complete the steps listed in the Identity and Access Management (IAM) service. It's especially important to set up multi-factor authentication (MFA) for your account. You should have an IAM user in the "Administrators" group when you're done.

Next go to the EC2 service, Amazon's storefront for virtual machines. Before launching a new instance, we'll create a key pair than can be used to access it securely. Use the following command locally to create a key pair:

ssh-keygen -t rsa -b 4096

Upload the public key by clicking "Key Pairs" and then "Import Key Pair" from the EC2 dashboard. Now we can launch an instance.

Go back to the EC2 dashboard and click "Create Instance". Select an Amazon Linux image with an SSD volume. Just select free-tier architecture for now, it can be scaled up later. Change the volume size from 8 to 30 gig, the largest size allowed in the free tier. On security groups page, allow only SSH from your specific IP address for the time being. Amazon instances also have a "default" security group that allows your AWS instances to communicate with each other by default.

You'll want to create an Elastic IP (EIP), which is just a static IP address that can be used to access your instance. Use this to log in for the first time. The default user for Amazon Linux is: ec2-user

Server Configuration

WordPress relies on a LAMP stack in this environment – Linux, Apache, MySQL, and PHP. We'll install the necessary components below. You can also read Amazon's guide for creating a LAMP stack. Note: Because we're using Amazon's Relational Database Service (RDS), we do not need to install mysql55-server as the guide recommends.

Log into your server and update it:

sudo yum update

Install the packages necessary to run WordPress:

sudo yum install httpd24 php56 php56-mysqlnd

Start Apache and use the chkconfig command to make sure Apache will start with each reboot:

sudo service httpd start
sudo chkconfig httpd on

Apache serves files out of its document root at /var/www/html. We need to allow ec2-user to manipulate files in this directory. Create the www group and add ec2-user to it:

sudo groupadd www
sudo usermod -a -G www ec2-user

Now give group ownership of the web directory and its contents to the www group.

sudo chgrp -R www /var/www

Some WordPress features also require write access to the Apache document root. The web server runs as the apache user, so we need to add that user to the www group. We'll also make the apache user the explicit owner of the directory:

sudo usermod -a -G www apache
sudo chown -R apache /var/www

Change the directory permissions and set the group ID on any future subdirectories:

sudo chmod 2775 /var/www
find /var/www -type d -exec sudo chmod 2775 {} +

Add group write permissions to files in the web directory and its subdirectories:

find /var/www -type f -exec sudo chmod 0664 {} +

Now ec2_user and apache can edit files in the Apache document root. Log out and in again to enact the new group permissions. You can run the groups command to see what groups your user belongs to. You'll also want to restart Apache:

sudo service httpd restart

Note: there is an extra package that WordPress will eventually rely on to manipulate images. Install it now:

sudo yum install php56-gd

Setting up RDS

Amazon's Relational Database Service is a scalable, high-availability database platform for cloud applications. We'll use it as the WordPress backend. Select RDS from the AWS console, click "Launch DB Instance" and select a MySQL instance. The largest free-tier volume you can create is 20 gig. Make sure to save the instance ID, user, and password you create.

Set the "Publicly Accessible" option to "No" on the advanced settings page. Choose a non-default port for the database, something other than 3306 that isn't used by another service. Just select the "default" security group, which allows incoming traffic from any other instance belonging to that group.

Log in now with the credentials you created. The hostname can be found on the RDS dashboard:

mysql -h [db_hostname] -P [db_port] -u [db_user] -p

Create a database and a user for WordPress to use:

CREATE DATABASE `wordpress-db`;
CREATE USER 'wordpress-user'@'%' IDENTIFIED BY 'wordpress-user-pw';
GRANT ALL PRIVILEGES ON `wordpress-db`.* TO 'wordpress-user'@'%';
FLUSH PRIVILEGES;

Substitute your own database name, user, and password. Notice that backtick quotes are required around the database name, not single quotes. Your RDS instance now has two users with different privileges. You can display the user info as follows:

SELECT user,host FROM mysql.user;
SHOW GRANTS FOR 'wordpress-user';

Setting up WordPress

Many of the following steps are also listed in Amazon's guide for installing WordPress. Download the latest WordPress installation package with wget and unzip the package:

mkdir Downloads; cd Downloads
wget https://wordpress.org/latest.tar.gz
tar -xzf latest.tar.gz

The WordPress installation folder contains a sample configuration file called wp-config-sample.php. We will use this file as a template for our configuration:

cd wordpress
cp wp-config-sample.php wp-config.php
vi wp-config.php

Find the line that defines DB_NAME and change database_name_here to the database name you created for WordPress. Do the same for DB_USER and DB_PASSWORD:

define('DB_NAME', 'wordpress-db');
define('DB_USER', 'wordpress-user');
define('DB_PASSWORD', 'wordpress-user-pw');

Do the same for DB_HOST, but remember to append the non-default port to the hostname with a colon:

define('DB_HOST', 'db_hostname:db_port');

Visit the WordPress secret key generator to create a set of values that you can paste into your wp-config.php file. Find the section called "Authentication Unique Keys and Salts" and overwrite the values with the keys that you generate. Save the file when you're done.

Now we can move the WordPress files to the Apache document root:

cd /home/ec2-Downloads/wordpress
sudo mv * /var/www/html/

WordPress permalinks need to use Apache .htaccess files to work properly but this is not enabled by default. Open the httpd.conf file:

cd /etc/httpd/conf
sudo cp httpd.conf httpd.conf.old
sudo vi httpd.conf

Find the section that starts with <Directory "/var/www/html">. Change the AllowOverride None line to read AllowOverride All. There are multiple AllowOverride lines in this file, so be sure you change only the line in the <Directory "/var/www/html"> section. Restart Apache again afterward:

sudo service httpd restart

Check to make sure that the file and directory permissions are set correctly after the file transfers and editing are complete.

Setting up SSL

Next you'll need a domain name and an SSL certificate. I purchased a wildcard certificate so that I could use a sub-domain with CloudFront. I found NameCheap to be a good vendor for certificates. If you purchase a domain name through AWS Route 53, remember to select "Hide Contact Information" under "Privacy Protection" on the registration page. You can follow Amazon's directions to register or transfer a domain using Route 53.

Domain names and SSL certificates are usually attached to an Elastic Load Balancer (ELB) in AWS. ELBs distribute traffic across multiple EC2 instances to increase your capacity and achieve some fault tolerance.

Note: To take full advantage of this scalable architecture, EC2 instances should be launched using services like CloudFormation or Elastic Beanstalk. I found WordPress difficult to deploy using those tools, but we can still use an ELB with instances created manually.

Usually an ELB strips SSL and passes cleartext traffic to port 80 of an EC2 instance. However, this will break any WordPress installation set to use an HTTPS domain name. It creates a redirect loop that renders the WordPress console unreachable. The solution is just to pass SSL through to the EC2 instance, which means the certificates must be added there also.

You'll need to generate a private key and a Certificate Signing Request (CSR) file prior to purchasing a certificate:

openssl req -new -newkey rsa:2048 -nodes -keyout domain_com.key -out domain_com.csr

Replace domain_com with the domain name you're using. You'll be prompted to fill in some data about the CSR. If you're creating a wildcard certificate, the "Common Name" field should include an asterisk, as in *.domain.com

You should receive an archive of files once the signing request is approved by your provider. Something like:

AddTrustExternalCARoot.crt
COMODORSAAddTrustCA.crt
COMODORSADomainValidationSecureServerCA.crt
star_domain.crt

The first three files form a CA bundle when chained together (we'll do that shortly). The fourth file is the actual certificate. To begin adding certificates to your EC2 instance, log in and install mod24_ssl. This is the Apache module that allows you to use SSL with your site:

sudo yum install mod24_ssl

Make a temporary directory to hold your certificates, then log out:

mkdir /home/ec2-user/temp
exit

Zip up the certificates and the private "domain_com.key" file you created. Transfer them from your localhost to the remote EC2 instance:

tar -cvf key_bundle.tar *.crt domain_com.key
scp -P [ssh_port] -i [ssh_key] key_bundle.tar ec2-user@[ip_addr]:/home/ec2-user/temp/

Replace the above items in brackets with the correct parameters. Afterwards, log back into your server, unzip the archive, and copy the keys to the correct directories:

cd /home/ec2-user/temp
tar -xvf key_bundle.tar
sudo cp star_domain.crt /etc/pki/tls/certs/
sudo cp domain_com.key /etc/pki/tls/private/

Then make sure they have the correct permissions:

sudo chown root:root /etc/pki/tls/certs/star_domain.crt
sudo chmod 400 /etc/pki/tls/certs/star_domain.crt
sudo chown root:root /etc/pki/tls/private/domain_com.key
sudo chmod 400 /etc/pki/tls/private/domain_com.key

Change star_domain.crt and domain_com.key to the correct names above. Next we'll create the CA bundle, place it with the other certificates, and correct the permissions again:

cd /home/ec2-user/temp
cat COMODORSADomainValidationSecureServerCA.crt COMODORSAAddTrustCA.crt AddTrustExternalCARoot.crt >> bundle.crt
sudo cp bundle.crt /etc/pki/tls/certs/
sudo chown root:root /etc/pki/tls/certs/bundle.crt
sudo chmod 444 /etc/pki/tls/certs/bundle.crt

Now we can edit the Apache configuration file for SSL called ssl.conf:

cd /etc/httpd/conf.d
sudo cp ssl.conf ssl.conf.old
sudo vi ssl.conf

Comment out the following lines and add the updated versions:

# SSLProtocol all -SSLv2
SSLProtocol all -SSLv2 -SSLv3
# SSLCertificateFile /etc/pki/tls/certs/localhost.crt
SSLCertificateFile /etc/pki/tls/certs/star_domain.crt
# SSLCertificateKeyFile /etc/pki/tls/private/localhost.key
SSLCertificateKeyFile /etc/pki/tls/private/domain_com.key
# SSLCACertificateFile /etc/pki/tls/certs/ca-bundle.crt
SSLCACertificateFile /etc/pki/tls/certs/bundle.crt

Replace the above filenames with the correct ones for your domain. We'll also add a new entry at the bottom of this file, before the last closing </VirtualHost> tag:

RequestHeader set X-Forwarded-Proto "https" early

This flags requests that are using HTTPS to connect. Along with a setting in httpd.conf, it will help us to force HTTPS for all connections. Edit the httpd.conf file again:

sudo vi /etc/httpd/conf/httpd.conf

Add the following lines after the closing </IfModule> tag of the <IfModule mime_magic_module> block, near the top of the file:

<IfModule rewrite_module>
    RewriteEngine on
    RewriteCond %{HTTP:X-Forwarded-Proto} !https
    RewriteRule .* https://%{HTTP_HOST}%{REQUEST_URI} [R,L]
</IfModule>

Now the server has been configured to accept HTTPS connections. Next we'll create the load balancer, which will receive incoming requests and forward them to the server.

Elastic Load Balancer

Upload your certificate to the AWS Identity and Access Manager (IAM):

cd /home/ec2-user/temp
aws iam upload-server-certificate --server-certificate-name star_domain_com --certificate-body file://star_domain.crt --private-key file://domain_com.key --certificate-chain file://bundle.crt

Change the necessary parameters above. This will make the certificate available in the console when creating the ELB. Navigate to the EC2 dashboard and click "Load Balancers" on the left. Click "Create Load Balancer" at the top. Add two rules to the new ELB:

  • HTTP (80) on the ELB  →  HTTP (80) on the instance
  • HTTPS (443) on the ELB  →  HTTPS (443) on the instance

Click "Next: Assign Security Groups" and create a security group that accepts both HTTP (80) and HTTPS (443) from your IP address. We'll eventually add the ELB to the "default" security group, so that the EC2 instance will accept traffic from it. Click "Next: Configure Security Settings" and select the certificate that you uploaded from the command line.

Continue the setup process: use the default health check settings, add your EC2 instance, tag it with a name, and create it. Back on the ELB console, highlight your new ELB and click the "Security" tab. Click "Edit" and add the "default" security group.

Now the domain name can be configured. Create a new "A" record for your domain in the Route 53 console. Use www as the subdomain and set the "Alias" option to "Yes". Select the canonical name of your load balancer from the "Alias Target" dropdown and click "Create".

Setting up S3 and CloudFront

Your domain name should take you to the WordPress login page once the DNS changes take effect. Log in and make sure your default domain is prefaced with https:// in the WordPress settings.

Amazon's Simple Storage Service (S3) is the preferred way to host static content in AWS. We want WordPress to store all media in an S3 bucket automatically. We'll also configure Amazon's CloudFront service to provide a custom subdomain for our S3 content. Note: Many of the following options are detailed throughout the documentation for the WordPress plugin we will eventually install.

Open S3 from the Amazon console. Create a new bucket and give it the exact name of the subdomain you intend to use, as in cdn.domain.com

Open IAM from the AWS console and click "Create New Users". Enter a name like WordPress and make sure that "Generate an access key for each user" is checked. Click "Create" and save the "Access Key ID" and "Secret Access Key" that appear.

Back in the IAM console, create a new WordPress group and add the WordPress user to it. Add an "Inline Policy" to the WordPress group, select "Custom Policy", and create a name like AmazonS3WordPress for it. Use the following policy:

{
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:CreateBucket",
                "s3:DeleteObject",
                "s3:Put*",
                "s3:Get*",
                "s3:List*"
            ],
            "Resource": [
                "arn:aws:s3:::cdn.domain.com",
                "arn:aws:s3:::cdn.domain.com/*"
            ]
        }
    ]
}

Enter the name of your S3 bucket in place of cdn.domain.com above. This policy will give users in the WordPress some limited access to the bucket we just created. Now edit wp-config.php again to provide WordPress with the user credentials:

sudo vi /var/www/html/wp-config.php

Add the following lines, using the correct values:

define('AWS_ACCESS_KEY_ID', 'access_key_id');
define('AWS_SECRET_ACCESS_KEY', 'secret_access_key');

Before creating a CloudFront distribution for the S3 bucket, we need to make the wildcard certificate available to CloudFront. Log into the EC2 instance and upload the certificate files to CloudFront:

cd /home/ec2-user/temp/
aws iam upload-server-certificate --server-certificate-name cdn_domain_com --certificate-body file://star_domain_com.crt --private-key file://domain_com.key --certificate-chain file://bundle.crt --path /cloudfront/cdn-domain-com/

Change the parameters above where necessary. From the AWS console, open the CloudFront dashboard, click "Create Distribution", and select a "Web" distribution. Set the following options:

  • Select your S3 bucket from the "Origin Domain Name" dropdown
  • Select "Redirect HTTP to HTTPS" for the "Viewer Protocol Policy" option
  • Enter your custom subdomain in the "Alternate Domain Names (CNAMEs)" field. This should be the same as your bucket name
  • Choose "Custom SSL Certificate" and select the certificate you uploaded
    • Leave the other values in place and click "Create Distribution". Now navigate to the Route 53 dashboard. Create a new "A" record for your subdomain, set the "Alias" option to "Yes", and select the canonical name of your CloudFront distribution from the "Alias Target" dropdown.

      After completing those steps, the following plugins will allow WordPress to use your S3 bucket for all static content: Amazon Web Services and Amazon S3 and CloudFront

      When configuring the plugins, you will not be able to list the available buckets because of the limited privileges we gave the WordPress user. Just enter the bucket name manually. For the "Domain" option, select "CloudFront or custom domain" and enter the subdomain that you set up. For the "SSL" option, select "Always SSL".

      Conclusion

      Remember to log back into your EC2 instance and delete the temporary directory where your keys were stored:

      sudo rm -rf /home/ec2-user/temp

      That concludes the basic setup that was required to launch this website. Going forward, your site should utilize RDS, S3, CloudFront, and use SSL for all connections.

Leave a Reply

Your email address will not be published. Required fields are marked *