Intro

We recently had the task of setting up hosting on AWS for a new site we’re building for a client using WordPress. The client wasn’t expecting the site to receive massive amounts of traffic initially however they planned to run campaigns to send sudden (albeit scheduled) bursts of traffic to the site. So we knew the hosting we setup needed to be small initially (for cost saving purposes) but very scalable.

We’ve had quite a bit of experience configuring auto-scaling hosting setups on AWS, however in the past we’ve always done this manually by provisioning each of the AWS services individually. The advantage of this approach is that we fully understand all the component parts required. However this client wanted to keep costs down initially, so I thought I’d give AWS Elastic Beanstalk a try as it offers the ability to spin up new environments very quickly.

The Tutorial

A little bit of Googling led me to this article ‘Deploying a high-availability WordPress website with an external Amazon RDS database to Elastic Beanstalk‘. Overall I found the article really good however the article was written a few years ago and there were a few little ‘issues’ I thought I’d make a note of here. Thankfully I’d recently installed Ubuntu on an old laptop so that was the first obstacle overcome (as I normally work on Windows).

Choose the right AMI

The first issue and the biggest show stopper was choosing the right AMI. The article and associated code appear to not work correctly with the Amazon Linux AMI 2. There were issues running the script to mount the file system and it was just erroring out. The following was written to the cfn-init.log:

2020-06-17 14:01:40,751 [ERROR] Command 01_mount (/tmp/mount-efs.sh) failed
2020-06-17 14:01:40,751 [ERROR] Error encountered during build of prebuild_0_myenv: Command 01_mount failed
Traceback (most recent call last):
  File "/usr/lib/python2.7/site-packages/cfnbootstrap/construction.py", line 542, in run_config
    CloudFormationCarpenter(config, self._auth_config).build(worklog)
  File "/usr/lib/python2.7/site-packages/cfnbootstrap/construction.py", line 260, in build
    changes['commands'] = CommandTool().apply(self._config.commands)
  File "/usr/lib/python2.7/site-packages/cfnbootstrap/command_tool.py", line 117, in apply
    raise ToolError(u"Command %s failed" % name)
ToolError: Command 01_mount failed
2020-06-17 14:01:40,753 [ERROR] -----------------------BUILD FAILED!------------------------
2020-06-17 14:01:40,753 [ERROR] Unhandled exception during build: Command 01_mount failed
Traceback (most recent call last):
  File "/opt/aws/bin/cfn-init", line 171, in 
    worklog.build(metadata, configSets)
  File "/usr/lib/python2.7/site-packages/cfnbootstrap/construction.py", line 129, in build
    Contractor(metadata).build(configSets, self)
  File "/usr/lib/python2.7/site-packages/cfnbootstrap/construction.py", line 530, in build
    self.run_config(config, worklog)
  File "/usr/lib/python2.7/site-packages/cfnbootstrap/construction.py", line 542, in run_config
    CloudFormationCarpenter(config, self._auth_config).build(worklog)
  File "/usr/lib/python2.7/site-packages/cfnbootstrap/construction.py", line 260, in build
    changes['commands'] = CommandTool().apply(self._config.commands)
  File "/usr/lib/python2.7/site-packages/cfnbootstrap/command_tool.py", line 117, in apply
    raise ToolError(u"Command %s failed" % name)
ToolError: Command 01_mount failed

 

That had me puzzled for a bit and I couldn’t find much online about it, but then I noticed the date of the article and thought I’d try a regular Amazon Linux AMI (rather than AMI2) and hey-presto it worked!

Database credentials reverted

The article suggests you create the environment variables for accessing the RDS database before uploading the code package. However when I did this I found the value I’d entered for the database password was reverted back to ‘test’ which is the value defined in the package… I’m not entirely sure why it did this, perhaps I added something wrong initially. But it’s something to watch out for. If you can’t connect to the database after ‘deploying’ your code, double check your database settings in Configuration > Software > Environment Variables.

404 of pages and posts after uploading new application version

So I thought I was up and running at this point. I had the environment running on EC2 instances via a Load Balancer and I had access to the WordPress install page which meant the RDS database was also connected OK. However I noticed that as soon as I re-uploaded the files to make a change to the code all the pages and posts appeared to return 404 errors… Again this had me puzzled for a little bit until I noticed that the package itself is missing an .htaccess file. I guess the initial install process creates one, however when a new instance is created it’s missing – hence the mod_rewrite rules are missing and everything was 404’ing. So I added the following .htaccess file to the package and re-uploaded and deployed and that seems to have done the trick.

# BEGIN WordPress

RewriteEngine On
RewriteBase /
RewriteRule ^index.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]

# END WordPress

 

Moving the site url and base url to environment settings

Another little annoyance was when I needed to change the site URL within WordPress. This came about because I created an initial environment in Elastic Beanstalk and installed WordPress. Then for whatever reason I needed to create a fresh environment but use the same database. However as the endpoint (basically the URL I was running it from) changed, it meant the stylesheets etc wouldn’t load – basically WordPress was installed at the wrong URL. To overcome this issue I know you can hard-code your WP_HOME and WP_SITEURL values in the wp-config.php file and ignore those defined in the database during install. To update these I needed to re-package, upload and deploy a new version of the application in the environment. But rather than hard-coding the new endpoint URL in the wp-config.php file I extended the use of environment variables and added a couple of new ones to my environment > Configuration > Software > Environment Properties.

Whilst I was at it I also updated my WordPress config to turn off automatic updates (incase that caused a problem as suggested in the guide) and added an if{} statement to allow for a localhost version of the site for development. The start of the wp-config.php file looked like this:

<?php
if (isset($_SERVER['RDS_DB_NAME']))
{
        // Use environment settings
        define('DB_NAME', $_SERVER['RDS_DB_NAME']);
        define('DB_USER', $_SERVER['RDS_USERNAME']);
        define('DB_PASSWORD', $_SERVER['RDS_PASSWORD']);
        define('DB_HOST', $_SERVER['RDS_HOSTNAME'] . ':' . $_SERVER['RDS_PORT']);
        define('AUTH_KEY',         $_SERVER['AUTH_KEY']);
        define('SECURE_AUTH_KEY',  $_SERVER['SECURE_AUTH_KEY']);
        define('LOGGED_IN_KEY',    $_SERVER['LOGGED_IN_KEY']);
        define('NONCE_KEY',        $_SERVER['NONCE_KEY']);
        define('AUTH_SALT',        $_SERVER['AUTH_SALT']);
        define('SECURE_AUTH_SALT', $_SERVER['SECURE_AUTH_SALT']);
        define('LOGGED_IN_SALT',   $_SERVER['LOGGED_IN_SALT']);
        define('NONCE_SALT',       $_SERVER['NONCE_SALT']);
        define('WP_HOME',          $_SERVER['WP_HOME']);
        define('WP_SITEURL',       $_SERVER['WP_SITEURL']);
        define('WP_AUTO_UPDATE_CORE', false);
}
else
{
        // Use local settings
        define('DB_NAME', 'wordpress');
        define('DB_USER', 'admin');
        define('DB_PASSWORD', 'password');
        define('DB_HOST', 'localhost');
        define('AUTH_KEY',         'blah');
        define('SECURE_AUTH_KEY',  'blah');
        define('LOGGED_IN_KEY',    'blah');
        define('NONCE_KEY',        'blah');
        define('AUTH_SALT',        'blah');
        define('SECURE_AUTH_SALT', 'blah');
        define('LOGGED_IN_SALT',   'blah');
        define('NONCE_SALT',       'blah');
        define('WP_HOME',          'http://localhost');
        define('WP_SITEURL',       'http://localhost');
        define('WP_AUTO_UPDATE_CORE', false);
}

 

Other issues

As you’d expect I also ran into numerous other ‘teething’ issues; such as ensuring I was running the right type of load balancer (I wanted an application load balancer and for some reason my initial environment was created with the outdated ‘classic’ one).

Ultimately I appear to have a working version of WordPress installed and I can scale everything up and down nicely. Time will tell if it’s the best route to take and how easily configuring things like Wordfence will be. Not to mention how the team get on with the deployment process. I’m keen to investigate the possibility of hooking up CodeCommit to trigger deployments as we normally use Git for version control, so uploading a packaged zip file seems quite outdated and a little odd to be honest. I’m surprised there aren’t more deployment options available directly in ElasticBeanstalk, but I’ve heard this is possible via CodeCommit. On the plus side I like the control I have within EB for configuring the instance type, number of instances, scaling criteria and monitoring. Plus setting up a duplicate environment for staging should be relatively simple. I also really like how we can keep things like database credentials out of code control and have them as environment variables instead.

Pros

  • Easy to configure and scale
  • Visibility of environment / monitoring
  • Easy to duplicate environment
  • Keeping database credentials out of source code and in environment

 

Cons

  • Lack of version control or Git integration initially
  • Concerns over the time required for WordPress Updates as I can’t use the builtin WP updater
  • Concerns over plugins and how well they’ll cope with being installed locally, packaged up and deployed across multiple webservers, rather than directly making changes on the live webserver itself.
  • Lack of direct SSH access to the EC2 instances. Normally when things don’t work I know I can SSH into the machine and see what’s going on. I don’t have SSH access to the instances created by EB currently, although again I know this is something I can configure.

 

I hope that helps point someone in the right direction when setting up a WordPress hosting platform using Elastic Beanstalk. I’m yet to actually publish the site on a live URL (presumably via Route53) or setup the load balancer to work with a secure certificate, but I hope that won’t be too difficult…

Beanstalk Vectors by Vecteezy