Elastic Beanstalk crons on multi-instance environments

Wow. What a lot of pain.

So let me first confirm what won’t work. Whether Apache 1, Apache 2, leader_only or EB_IS_COMMAND_LEADER testing, you cannot consistenty get exactly one (not zero, or 2+) instances to run a cron job over a long period of time.

The issue is the the leader information isn’t safe. It’s only available during deployments, when you usually first set up the cron job. But when an instance replacement happens—for whatever reason—there is no related deployment, the test to check if the new instance is the leader fails, and you end up with no crons.

So for anything you do at the moment of deployment—database migrations, logging, whatever— that you only want to run once, even on multi-instance environments in an Elastic Beanstalk Application, you can use and trust the leader tests available to you.

But for anything you want to run regularly, on a schedule across weeks and months, you cannot trust those options.

After a lot of research, and trial and error there are two options available to you.

Worker environments

The ‘official’ way is probably via a worker environment. Essentially an instance set up specifically to schedule and run short or long lives processes.

You can learn more about them via the links below, but for the rest of this article we’ll be talking about the alternative option of ‘runtime leader testing’.


Runtime leader testing

This option means running the cron on every instance of an environment, but the initial section of the code being run checks the if the instance id that it’s running on is the first to be returned in the list of all instances. That is, only the first instance id returned by the aws elastic beanstalk api will continue to run the task.

Below the examples are in PHP, but it’s a valid method for other languages too.

We’re assuming you’ve already set up a cron and know how to alter it to make it run on all instances of your environment.

The steps in this guide:

  • Create an IAM user just for this process and give it limited read permissions
  • Add the credentials to EB configuration
  • Write code to check the instance id from the api against the current instance id

Adding the user

  • Open IAM in AWS console
  • Click Users
  • Click Add users
  • Type your User name – we used “[applicationCode]-eb-read-user”
  • Click the ‘Access Key’ access type
  • Click Next: Permissions
  • Click the ‘Attach existing policies directly’ tab
  • Type ‘AWSElasticBeanstalkReadOnly’ and select it
    • NB: This permission actually gives more access than is required and you could trim it further
  • Click Next: Tags
  • Click Next: Review
  • Click Create user
  • Take a copy of the KEY and SECRET

Storing the key and secret in the environment

  • Open your environment
  • Click configuration
  • Click ‘edit’ on the ‘Software’ section
  • Add two new items, one for the key (examples use AWS_KEY) and one for the secret (examples use AWS_SECRET)

The code

So this is php, but the basic idea is:

  • Install the aws library – here we use composer
  • Add some code to call the library
  • Compare the first returned instance id against the id of the instance running the code

Install the aws library

$composer require aws/aws-sdk-php

Add the code to do the check

$client = ElasticBeanstalkClient::factory([
    'credentials' => [
        'key' => $_ENV['AWS_KEY'],
        'secret' => $_ENV['AWS_SECRET'],
    'region'  => '[[your region]]',
    'version' => 'latest'

Replace [[your_region]] above with your own

$result = $client->describeEnvironmentResources([
    'EnvironmentName' => '[[your_environment_name]]'

Then load in the current instance’s id

$currentId = file_get_contents("http://instance-data/latest/meta-data/instance-id");

Then compare the API’s first result to the current one. If the same, so something, otherwise don’t.

if ($currentId == $result['EnvironmentResources']['Instances'][0]['Id'])
    // do something. Only one instance will get here
    // don't do anything. All other instances will be here

Other useful resources:


Updating PHP on an AWS Lightsail WordPress Stack

Updated 27th September 2020 to consider restart of all services where restarting just apache isn’t enough, and also to share choice when it comes to import first vs SSL first as you may lose permalinks. Thanks to Peter and Simon in the comments.

Updated 25th January 2021 to add an initial paragraph clarifying that you can’t ‘choose’ a PHP version on Lightsail and to further clarify that you must first check which version of PHP a new AWS Lightsail instance will be built with to confirm it will resolve the message being displayed in WordPress.

Updated 11th May 2021 to confirm that snapshots cannot be used to update the PHP version of a Lightsail Bitnami instance.

Updated 22nd September 2022 to add a link to a guide to check which PHP version you’ll get if you spin up a new Lightsail WordPress instance. Also added a note about WordPress supported PHP versions.

Updated 7th September 2023 to add a mention of the option to use Bitnami’s bncert tool to create an SSL certificate

NB: If you’re hoping for a way to click ‘update php’ or ‘use X version of PHP’ in Lightsail, it doesn’t (at time of writing) exist. This guidance will walk you though creating a new Lightsail instance with a newer version of PHP installed and moving everything over to it.

The AWS Lightsail service is great. Click a few buttons and you have a powerful VPS with WordPress up and running for only a few dollars a month. The Bitnami integration allows you to choose from a whole set of Stacks to include upon setup. The LAMP option allows a competent PHP developer to run almost anything on Lightsail.

But after a while running the Lightsail VPS the setup is going to get stale, and because Bitnami bundles everything up, it’s not recommended to go poking around with only a few parts of it.

That’s the challenge I was faced with. Logging into the WordPress admin console a message is displayed which is telling me WordPress would prefer a newer version of PHP please. My version was old and not receiving security updates.

I read a fair bit about my options, but after looking at the choices I decided the best thing was to do an export and import onto a new Lightsail instance.

Continue reading

Yii upgrade service

Yii 1.1 is currently in maintenance mode following Yii 2 being released way back in May 2013.

Support and bug fixes for Yii 1 were only provided until December 31, 2016 and security fixes and PHP 7 compatibility ends December 31, 2020.

That means that right now the only reason updates to Yii 1 are being created and made available are for security issues found in the existing codebase, and from 2021 it’s mothballed completely.

If you’re maintaining a Yii 1 codebase it’s time to start thinking about how to migrate away from Yii completely, or upgrade your codebase to Yii 2.

There is an official guide aimed at supporting the upgrade process, but it is unlikely to get you from Yii 1 to Yii 2 without significant pain and head scratching.

Converting from Yii 1 to Yii 2 is completely unlike developing code for either framework, and you’d want to source a Yii upgrade professional rather than getting an inexperienced developer or team involved with a large or mission critical codebase.

Personally I’ve now upgraded four codebases from Yii 1 to Yii 2. All the upgrades have been different. All follow a similar path, and the repeatable process I’ve created, but each has its own edge cases, bespoke widgets and complexities.

The work takes many days per codebase before testing and perfection can happen, and where an existing development team are involved with ongoing development or support of the Yii 1 system, it’s best handled as a team effort. Acceptance that the process isn’t anywhere close to just flicking a switch or downloading a patch is required from day one.

At this point I’m considering two next steps.

Firstly, if your company has significant budget and is in need of support moving from Yii 1 to Yii 2, I’d be interested in hearing from you. You may want a supportive voice helping your existing developers to make the move. You may also be looking to hand over a Yii 1 codebase and get it back ready for some testing and iteration.

Secondly, I’m considering sharing some or all of the process I’ve created for manually moving a codebase over from Yii 1 to Yii 2, the gotchas, the process and how to update specific things within Yii 1, where it isn’t as simple as a like

Feel free to send me a direct message on Twitter @harrybailey or leave a comment below if you’d like to contact me.

Ping a url or script using IFTTT (with no repercussions)

Update: This actually doesn’t work as I’d hoped. Because flickr reports an error each time, eventually IFTTT disables the recipe after a few hours of calls. Back to the drawing board.

I’ve been trying to get IFTTT (if this then that) to send a call to a php script file when a trigger goes off for months. The problem wasn’t getting it to work in the first place, but for it not to leave any artifacts hanging around, or repercussions as I call them. I wanted the script to be triggered and that to be the end of it, with no files being created or errors being recorded.

An example of the problem would be the IFTTT Google Docs channel. You can upload a file to google docs from a url. You can define that url to be your php script and then tell your script to return a 404 (after it’s done its coding goodness) but when you view Google Docs a new file has still been created. If you want to use this for a large number of pings, you’re going to end up with a folder full of pointless files and a waste of your disc space. The Evernote and WordPress channels are a similar story. Even when you attempt to fail the call to your url (with a 404 or 503) they still create a note or post.

Continue reading

Yii (and Yii2) wildcard / catch all url rules

Both Yii and Yii2 have url management and routing built in. They will compare the url of the request to a list of rules you’ve defined.

Sometimes you might want to do various checks of the url in yii config main and then send every other request to a particular controller.

The rule you need to add last to the urlManager is…

'(.*)' => 'controller/action',

… and now any rule that isn’t matched previously will end up being sent to your defined controller and action.

In Yii2 you can also do the same:

'<url:(.*)>' => 'controller/action'