Setting up WordPress on AWS
Setting up a basic Wordpress installation on AWS
18 November 2018

Authoring a blog is an excellent and easy way to connect with your users.  Popular self-hosted options include WordPress, Drupal, and Ghost.  This tutorial will show you how to configure a basic, secured installation of WordPress using Amazon Web Services.  You’ll configure a basic LAMP stack with Apache and MySQL, install and configure the WordPress installation on your domain, and then use certificates from Let’s Encrypt to secure connections to your server.

An alternative to this guide is to use Amazon’s Lightsail which offers a simpler setup.

Table of Contents

Introduction

Begin by registering for AWS.  This account will become your root account.  If you already have an Amazon account,  you may want to separate the two by using a different email address.  After the guide is complete, you’ll have your own WordPress installation active at https://domain.name, see the diagram below.

Basics first

AWS administrator account

When using AWS it’s good practice to separate account credentials into their functional roles.  This means not using the root account for simple tasks because if anyone procures or steals these credentials then they will have unfettered access to your entire account.  We will create a full-access administrator using the IAM interface.

Log into the AWS console and navigate to security credentials by clicking on your account name on the top right and clicking on “My Security Credentials.”  On the right hand side, click on Users and click the blue “Add user” button.  Select a username. Allow programmatic access and AWS Management Console access. Enter a password.  In the “Set Permissions” panel select “Attach existing policies directly” and check AdministratorAccess under “Policy name.” Click Next and then “Create user.”  Save the credentials and click on the console link provided to sign in as the new administrator.

EC2 instance

Amazon Elastic Compute Cloud instances is the backbone of your self-hosted WordPress installation.  While logged in as the full-access administrator created earlier, navigate to Services > [Compute] EC2.  Choose your region for the instance by clicking on the city name in the top right.  In the sidebar under “Network & Security” click on Security Groups.  Get your IP address by visiting http://checkip.amazonaws.com and then click Create Security Group. Give it a name and description and in the inbound section select type as All TCP and enter your IP address in the source field.
Next, click on Instances in the sidebar and Launch Instance. Create an ubuntu-bionic-18.04 instance.
A t2.micro instance is plenty for this tutorial. Generate and download the SSH keypairs if you have not yet. Assign the security group you created earlier to this instance.
Under [Network and Security] Elastic IPs, click Allocate new address and then under Actions, associate that with your new instance.
Using the public DNS located in the Instance information, connect via SSH.

$ ssh -i ~/Downloads/keyfile.pem ubuntu@public.dns.address

Update all packages, reboot, and reconnect, accepting the package maintainer’s version of updates if necessary.

$ sudo apt update
$ sudo apt upgrade
$ sudo reboot

You can make repeated connections easier by updating your ~/.ssh/config file. The ServerAlive entries keep your connection active while you’re referring to this tutorial.

~/.ssh/config

Host ec2-wordpress
Hostname public.dns.address
User ubuntu
IdentityFile ~/Downloads/keyfile.pem
ServerAliveInterval 300
ServerAliveCountMax 2

And then connect by typing ssh ec2-wordpress.

$ ssh ec2-wordpress

MySQL

$ sudo apt install mysql-server
$ sudo mysql_secure_installation

Create MySQL databases for WordPress to store its content.

$ sudo mysql

mysql> create database wordpress default character set utf8 collate utf8_unicode_ci;

The collate setting controls how characters are compared in the database. We will use UTF-8 throughout our setup.

Create a user account for WordPress that has control over the cms_wordpress database.

mysql> create user 'ronald'@'localhost' identified by 'password';
mysql> grant all on wordpress.* to 'ronald'@'localhost';
mysql> flush privileges;
mysql> quit;

Apache

$ sudo apt install apache2

Visiting the server’s IP address will show the default page. Make sure you read the entirety of the page to check for problems.

Create a virtual host for WordPress.

$ cd /etc/apache2/sites-available
$ sudo cp 000-default.conf 001-wordpress.conf
$ sudo vim 001-wordpress.conf

<VirtualHost *:80>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/wordpress

<Directory /var/www/wordpress>
Options FollowSymLinks
AllowOverride All
Require all granted
</Directory>
</VirtualHost>

Create the WordPress directory with the GUID bit to allow for newly created directories to inherit the directory’s group.

$ sudo mkdir -p /var/www/wordpress

$ sudo chown -R $USER:www-data /var/www/wordpress

$ sudo chmod -R g+s /var/www/wordpress

Enable rewrite functionality for links.

$ sudo a2enmod rewrite

Check that the new configuration syntax is correct and enable the WordPress site.

$ sudo apache2ctl configtest

$ sudo a2dissite 000-default.conf

$ sudo a2ensite 001-wordpress.conf

$ sudo systemctl reload apache2

PHP

Finally, we’ll install PHP and the associated bindings for MySQL and Apache.

$ sudo apt install php php-mysql libapache2-mod-php

WordPress

Download WordPress from the official website. As of writing, the latest version is 4.9.8.

$ cd /var/www/wordpress

$ wget wordpress.org/latest.tar.gz

$ tar xf latest.tar.gz --strip-components=1 -C .

$ mkdir /var/www/wordpress/wp-content/upgrade

$ sudo chmod g+w /var/www/wordpress/wp-content \
/var/www/wordpress/wp-content/themes /var/www/wordpress/wp-content/plugins

Create a .htaccess file to let WordPress create permalinks.

$ touch /var/www/wordpress/.htaccess

$ cp wp-config-sample.php wp-config.php

Edit wp-config.php

define('DB_NAME', 'wordpress');
define('DB_USER', 'ronald');
define('DB_PASSWORD', 'password');

$table_prefix = 'wp_'

Add your salts to the wp_config.php file.

curl https://api.wordpress.org/secret-key/1.1/salt
define('AUTH_KEY', 'SALT_1');
define('SECURE_AUTH_KEY', 'SALT_2');
define('LOGGED_IN_KEY', 'SALT_3');
define('NONCE_KEY', 'SALT_4');
define('AUTH_SALT', 'SALT_5);
define('SECURE_AUTH_SALT', 'SALT_6');
define('LOGGED_IN_SALT', 'SALT_7');
define('NONCE_SALT', 'SALT_8');

Navigate to your web server address to confirm it works. Don’t configure anything yet as we’ll need to enable SSL first before passwords are entered.

If shown the error “You do not have permission to access / on this server”, then make sure you have enabled the site. To check, the /etc/apache2/sites-enabled directory will have an entry that links to the sites-available directory.
Also check your config file for path errors. If the path points to a valid location that doesn’t contain your WordPress installation, it will still pass the config test. If you update the config don’t forget to issue

$ sudo systemctl reload apache2

Security

Next, we’ll tighten the existing installation by closing common security gaps on a default installation.

Prevent local users on your server from setting up .htaccess files that can circumvent your security settings.

$ sudo vim /etc/apache2/sites-available/000-default.conf

<VirtualHost *:80>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/wordpress
<Directory "/">
AllowOverride None
</Directory>

<Directory /var/www/wordpress>
Options FollowSymLinks
AllowOverride All
Require all granted
</Directory>
</VirtualHost>

Restrict access to the wp-admin directory and wp-login file to your IP address. Issue ‘curl checkip.amazonaws.com’ for your IP address. Make sure you issue this command on your home computer and not the remote server.

$ sudo vim /etc/apache2/sites-available/001-wordpress.conf

<VirtualHost *:80>
DocumentRoot /var/www/wordpress

<Directory "/">
AllowOverride None
Require all denied
</Directory>

<Directory /var/www/wordpress>
Options FollowSymLinks
AllowOverride All
Require all granted

<Files "wp-login.php">
Require ip your.ip.address/32
</Files>
</Directory>

<Directory /var/www/wordpress/wp-admin>
Require ip your.ip.address/32
</Directory>
</VirtualHost>

Ensure the right services restart upon boot by checking for the enabled state for mysqld.service and apache2.service.

$ sudo systemctl list-unit-files

Reboot and go back to your server address in your browser to confirm everything is still working.

SSL and domain configuration

Route53

We will use Route53, Amazon’s cloud DNS, for routing. Navigate to Services > [Networking and Content Delivery] Route 53.
In the side bar, select Hosted zones. Then, Create Hosted Zone. In the domain name field, enter your domain name and in the drop down select Public Hosted Zone. The name server and start of authority entries should be pre-populated. Click on Create Record Set, select A under Type, and enter the elastic IP address you created earlier in the Value field. Next, update the DNS entry in your registrar to point to Amazon’s name servers. They are listed under the NS entries in your hosted zone. Consult with someone before doing this if you already use this domain name for email. Wait 5 minutes for the changes to propagate to all servers.
Check that the name resolution is working by visiting your domain name.

SSL Certificate

We will use Let’s Encrypt for our HTTPS configuration and enable automatic renewals.

First we’ll create a limited user to handle DNS updates. The HostedZoneID is available in Route53 next to the domain name you are using.

As the new full-access administrator, sign in and navigate to the Add user section again. This time, create a new user and only enable programmatic access.
In the following Permissions page click Create policy and then click the JSON tab.
Enter the following JSON in the text box.

{
"Version": "2012-10-17",
"Id": "certbot-dns-route53",
"Statement": [
{
"Effect": "Allow",
"Action": [
"route53:ListHostedZones",
"route53:GetChange"
],
"Resource": [
"*"
]
},
{
"Effect" : "Allow",
"Action" : [
"route53:ChangeResourceRecordSets"
],
"Resource" : [
"arn:aws:route53:::hostedzone/YOURHOSTEDZONEID"
]
}
]
}

Save the access credentials locally for access by certbot.

~/.aws/config
[default]
AWS_ACCESS_KEY_ID=yourid
AWS_SECRET_ACCESS_KEY=yourkey

Next we will install the certbot and configure

$ sudo apt install certbot python3-certbot-apache python3-certbot-dns-route53 awscli

Test that the AWS configuration is correct by querying your Route53 hosted zone.

$ aws route53 list-hosted-zones

Now get the certificate from Let’s Encrypt.

$ sudo certbot certonly --dns-route53 --dns-route53-propagation-seconds 30 -d domain.name

Check that the cronjob / SystemD timer has been initialised. Check for cert-bot timer.

$ sudo systemctl list-timers

Finally, enable auto-renewal of the certificate. All your configuration details will be store in /etc/letsencrypt/renewal/domain.name.conf

First we will test a dry-run of our update timer and then create the override file.

$ sudo systemd-run --service-type=oneshot \
-E AWS_ACCESS_KEY_ID=yourid \
-E AWS_SECRET_ACCESS_KEY=yoursecret \
/usr/bin/certbot -q renew \ 
--force-renewal \
--post-hook="systemctl restart apache2"

If the command issued successfully, then update the timer to use the options provided.

$ sudo systemctl edit certbot
[Service]
Environment="AWS_ACCESS_KEY_ID=yourid"
Environment="AWS_SECRET_ACCESS_KEY=yoursecret"
ExecStart=/usr/bin/certbot -q renew
ExecStart=/bin/systemctl restart apache2

Next, we’ll update our Apache configuration to use the new certificates.

$ sudo a2enmod ssl

$ sudo vim /etc/apache2/001-wordpress.conf

<VirtualHost *:443>
ServerAdmin webmaster@localhost
ServerName domain.name
DocumentRoot /var/www/wordpress
SSLEngine on
SSLCertificateFile "/etc/letsencrypt/live/domain.name/cert.pem"
SSLCertificateKeyFile "/etc/letsencrypt/live/domain.name/privkey.pem"

<Directory "/">
AllowOverride None
Require all denied
</Directory>

<Directory /var/www/wordpress>
Options FollowSymLinks
AllowOverride All
Require all granted

<Files "wp-login.php">
Require ip your.ip.address/32
</Files>
</Directory>

<Directory /var/www/wordpress/wp-admin>
Require ip your.ip.address/32
</Directory>
</VirtualHost>

Navigate to https://domain.name to test. If everything looks good, then we will go ahead and add a redirect for HTTP to HTTPS. Add this snippet anywhere in the file.

$ sudo vim /etc/apache2/001-wordpress.conf

<VirtualHost *:80>
ServerName domain.name
Redirect permanent / https://domain.name/
</VirtualHost>

$ sudo systemctl restart apache2

Confim the redirect works by going to http://domain.name. You may need to clear your cache first.  Update wp-config.php to use https, not http, in the WP_HOME and WP_SITEURL definitions.

$ vim /var/www/wordpress/wp-config.php

define('WP_HOME', 'https://domain.name');
define('WP_SITEURL', 'https://domain.name');

First Post

The backend is configured and almost ready. Go ahead and create an administrator of your blog by going to https://domain.name and start writing.

In future guides, we’ll share how to use a content delivery network to reduce the load on your server and how to mitigate some common attacks upon your server.