---
title: "PHP 5.6 on Elastic Beanstalk, and mounting storage"
date: "2021-10-25"
path: "/211-php-5-6-on-elastic-beanstalk-and-mounting-storage.html"
section: "/ramblings"
description: "Running a legacy PHP 5.6 application on an Amazon Linux 2 Elastic Beanstalk platform with Remi's packages, then mounting an S3 bucket with s3fs using an instance role instead of stored keys."
---

A legacy PHP application needed to move onto Elastic Beanstalk, and it would not run on anything newer than PHP 5.6. Beanstalk's PHP 5.6 platform had been retired; the current ones run PHP 7.x and 8.x on Amazon Linux 2. Amazon Linux 2 is close enough to CentOS 7 that Remi Collet's repository works on it, and Remi still builds PHP 5.6 as a separate software collection, so the plan was: take the stock Amazon Linux 2 PHP platform with nginx, swap its PHP for 5.6, and keep nginx and Beanstalk's own scripts happy.

PHP 5.6 has had no security fixes since the end of 2018. This is a way to buy time while the code is brought up to date, not a place to stay.

## Swapping PHP by hand

I did this on a running instance over SSH first to get it right.

Stop the web stack and remove the platform's libzip. Remi's zip extension needs its own build of the library, and removing the old one also removes the platform PHP packages that depend on it:

```bash
sudo service nginx stop
sudo service php-fpm stop
sudo yum remove -y libzip
```

Add EPEL and Remi's EL7 repository, enable the PHP 5.6 collection, and install the libraries the zip extension depends on:

```bash
sudo yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
sudo yum install -y https://rpms.remirepo.net/enterprise/remi-release-7.rpm
sudo yum-config-manager --enable remi-php56
sudo yum install -y libzstd
sudo yum --enablerepo=remi install -y libzip-last
```

Then PHP 5.6 and the extensions the application uses:

```bash
sudo yum install -y php56 php56-php-cli php56-php-common php56-php-fpm \
  php56-php-mysql php56-php-gd php56-php-mbstring php56-php-xml \
  php56-php-intl php56-php-opcache php56-php-process php56-php-pear \
  php56-php-pecl-jsonc php56-php-pecl-zip
```

Everything installs under `/opt/remi/php56/root`, alongside the system rather than over it. The `php56` command runs the 5.6 CLI, and `scl enable php56 bash` gives you a shell where plain `php` is 5.6.

## Making Beanstalk's config fit

When yum removed the platform PHP packages it kept their edited config files as `.rpmsave`. Those files are Beanstalk's: the nginx upstream that points at the PHP-FPM socket, and the FPM pool that listens on it. Put them back, and give the 5.6 FPM a copy of the pool:

```bash
sudo mv /etc/nginx/conf.d/php-fpm.conf.rpmsave /etc/nginx/conf.d/php-fpm.conf
sudo mv /etc/php-fpm.d/www.conf.rpmsave /etc/php-fpm.d/www.conf
sudo cp /etc/php-fpm.d/www.conf /opt/remi/php56/root/etc/php-fpm.d/
sudo mkdir -p /run/php-fpm
```

Beanstalk's deployment scripts restart a service called `php-fpm`. The 5.6 service is called `php56-php-fpm`, so give systemd a `php-fpm` unit that runs it. A copy in `/etc/systemd/system` takes priority over anything under `/usr/lib` and survives package updates:

```bash
sudo cp /usr/lib/systemd/system/php56-php-fpm.service /etc/systemd/system/php-fpm.service
sudo systemctl daemon-reload
```

Start it all and check:

```bash
echo "<?php phpinfo();" | sudo tee /var/www/html/index.php
sudo service php-fpm start
sudo service nginx start
```

The page should report PHP 5.6 and the FPM/FastCGI server API. Delete `index.php` afterwards.

## Making it survive

Beanstalk replaces instances whenever it scales or an instance fails a health check, and anything done by hand over SSH goes with them. On Amazon Linux 2 platforms the place for this is a platform hook: an executable script in your application bundle at `.platform/hooks/prebuild/`, which runs as root on every deployment before the application is put in place. Put the commands above into, say, `.platform/hooks/prebuild/01-php56.sh` with a guard at the top so it does nothing on an instance that already has 5.6:

```bash
#!/bin/bash
set -euo pipefail
if rpm -q php56-php-fpm > /dev/null 2>&1; then
  exit 0
fi
# the install steps from above, without sudo
```

Make it executable before you zip the bundle (`chmod +x`), or Beanstalk ignores it. Copy it to `.platform/confighooks/prebuild/` as well if configuration changes should run it too.

## Mounting an S3 bucket with s3fs

The application also expected a directory of data files on local disk, and the files lived in an S3 bucket. s3fs mounts a bucket as a FUSE filesystem. It's in EPEL for Amazon Linux 2 (`yum install s3fs-fuse`) and in the main archive on Ubuntu (`apt-get install s3fs`).

The quick way you'll see everywhere is a credentials file holding an access key:

```bash
echo "<access-key-id>:<secret-access-key>" | sudo tee /etc/passwd-s3fs
sudo chmod 600 /etc/passwd-s3fs
```

Don't. A long-lived key sitting in a file on every instance ends up in images, backups and support tickets, and it has to be rotated by hand. On EC2, including under Beanstalk, give the instance profile access to the bucket and let s3fs fetch temporary credentials from the instance metadata:

```bash
sudo mkdir -p /mnt/data
sudo s3fs example-bucket /mnt/data -o iam_role=auto -o allow_other
```

The role needs no more than this on the one bucket:

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::example-bucket"
    },
    {
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
      "Resource": "arn:aws:s3:::example-bucket/*"
    }
  ]
}
```

For a mount at boot, in `/etc/fstab`:

```text
example-bucket  /mnt/data  fuse.s3fs  _netdev,allow_other,iam_role=auto  0  0
```

`allow_other` lets users other than the one who mounted it, such as the web server user, read the files. `_netdev` waits for the network.

Bear in mind S3 is not a disk. There's no file locking, a rename is a copy and a delete, and listing a big directory is slow. It's fine for files that are written once and read many times. If the application needs real shared storage that several instances write to, EFS is the better fit: install `amazon-efs-utils` and mount it with `fs-0123456789abcdef0:/ /mnt/efs efs _netdev,tls 0 0` in `fstab`, with the instances' security group allowed to reach the file system on NFS (TCP 2049).

## Today

Amazon Linux 2 reached end of support in 2026, and CentOS 7, whose packages this relies on, ended in 2024. Any application still on PHP 5.6 needs to move.
