PHPEnv: Run Different PHP Versions Side by Side on Ubuntu

Running multiple PHP versions on the same server or development machine is never straightforward. One distro upgrade can break packages and turn your environment into a mess. For example, Ubuntu 20.04 ships with PHP 7.4, 22.04 comes with PHP 8.1, and 24.04 will use PHP 8.3. But what if you have projects that require both old and new PHP versions at the same time? That’s where PHPenv comes in. It’s an excellent tool for local development—and even for UAT servers.

To demonstrate how to use different PHP versions side by side, I’ll first install WordPress on Ubuntu 20.04 using Ansible. After that, I’ll set up PHPenv and switch to PHP 8.2.5 using pfm. The first part focuses on WordPress with Ansible, but if you want to skip straight to the PHPenv setup, feel free to jump ahead.

Install WordPress with Ansible

As a “lazy” sysadmin, I’m not a fan of repetitive manual work—so I’ll use Ansible to handle the installation. With just a few simple configurations, I’ll have WordPress up and running with FPM, Apache, and MySQL—all set automatically.

Install Ansible on Ubuntu 20.04

sudo apt update
sudo apt install software-properties-common -y
sudo add-apt-repository --yes --update ppa:ansible/ansible
sudo apt install ansible -y

Ansible needs to know which servers to manage. Create an inventory file

mkdir ~/ansible
cd ~/ansible
vim inventory.ini

Everything happens on the same host. Adding only localhost to inventory.ini

[wordpress]
127.0.0.1 ansible_connection=local

Test Ansible

ansible -i inventory.ini wordpress -m ping

You should get

127.0.0.1 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

Clone the DigitalOcean Playbooks Repo

cd ~
git clone https://github.com/do-community/ansible-playbooks.git
cd ansible-playbooks

cd wordpress-lamp_ubuntu1804, you should see:

inventory
playbook.yml
vars/default.yml
templates/

Configure Inventory. Paste this

[wordpress]
127.0.0.1 ansible_connection=local

Edit the variables file vars/default.yml

---
#System Settings
php_modules: [ 'php-curl', 'php-gd', 'php-mbstring', 'php-xml', 'php-xmlrpc', 'php-soap', 'php-intl', 'php-zip' ]

#MySQL Settings
mysql_root_password: StrongRootPass123
mysql_db: wordpress
mysql_user: wpuser
mysql_password: StrongWpPass123

http_host: localhost
http_conf: wordpress.conf
http_port: "80"

Run the Playbook

ansible-playbook -i inventory playbook.yml

After ansible finish it work, follow wordpress installation from the browser. Next we will setup fpm

sudo apt install php7.4-fpm -y
sudo systemctl enable php7.4-fpm
sudo systemctl start php7.4-fpm

Enable Required Apache Modules

sudo a2enmod proxy_fcgi setenvif
sudo a2enmod rewrite

Inside /etc/apache2/sites-available/wordpress.conf at the end of VirtualHost block add

<FilesMatch \.php$>
    SetHandler "proxy:unix:/run/php/php7.4-fpm.sock|fcgi://localhost/"
</FilesMatch>

Disable mod_php

sudo a2dismod php7.4
sudo systemctl restart apache2

Verify It’s Using FPM. Create a test file: /var/www/wordpress/info.php and add, without forget to delete it later

<?php phpinfo(); ?>

Visit http://YOUR_PUBLIC_IP/info.php. We are expecting FPM/FastCGI in ServerApi.

Till now We have wordpress with old php version and FPM. Let’s install phpenv. With it we will upgrade to php 8.2.5 again using FPM.

Install phpenv on Ubuntu

clone the repo

git clone https://github.com/phpenv/phpenv.git ~/.phpenv

add phpenv to PATH

echo 'export PATH="$HOME/.phpenv/bin:$PATH"' >> ~/.bashrc

initialize phpenv in your shell every time Bash starts

echo 'eval "$(phpenv init -)"' >> ~/.bashrc

exit server and enter again. We need utility for building versions of PHP to use them side by side with each other. BTW if you need to install additional plugins like composer, check this: https://github.com/ngyuki/phpenv-composer

git clone https://github.com/php-build/php-build $(phpenv root)/plugins/php-build

we will need some additional packages

sudo apt install pkg-config libxml2-dev libsqlite3-dev zlib1g-dev libbz2-dev libcurl4-openssl-dev libpng-dev libjpeg-dev libonig-dev libreadline-dev libtidy-dev libxslt1-dev libzip-dev librabbitmq-dev libsodium-dev libpq-dev libxml2-dev libxslt1-dev default-mysql-client default-libmysqlclient-dev build-essential autoconf bison m4 re2c

build php 8.2.5. Expecting “[Success]: Built 8.2.5 successfully.”

PHP_BUILD_CONFIGURE_OPTS="--with-sodium --with-mysqli --with-pdo-mysql" \
phpenv install 8.2.5

Reload configs

phpenv rehash

FPM proccess will run as www-data. (set user = www-data group = www-data) in file below

vim ~/.phpenv/versions/8.2.5/etc/php-fpm.d/www.conf

PHP-FPM will run as systemd proccess. Add this config in /etc/systemd/system/php-fpm-8.2.5.service

# It's not recommended to modify this file in-place, because it
# will be overwritten during upgrades.  If you want to customize,
# the best way is to use the "systemctl edit" command.

[Unit]
Description=The PHP FastCGI Process Manager
After=network.target

[Service]
Type=simple
PIDFile=/home/ubuntu/.phpenv/versions/8.2.5/var/run/php-fpm.pid
ExecStart=/home/ubuntu/.phpenv/versions/8.2.5/sbin/php-fpm --nodaemonize --fpm-config /home/ubuntu/.phpenv/versions/8.2.5/etc/php-fpm.conf
ExecReload=/bin/kill -USR2 $MAINPID

# Set up a new file system namespace and mounts private /tmp and /var/tmp directories
# so this service cannot access the global directories and other processes cannot
# access this service's directories.
PrivateTmp=true

# Mounts the /usr, /boot, and /etc directories read-only for processes invoked by this unit.
ProtectSystem=full

# Sets up a new /dev namespace for the executed processes and only adds API pseudo devices
# such as /dev/null, /dev/zero or /dev/random (as well as the pseudo TTY subsystem) to it,
# but no physical devices such as /dev/sda.
PrivateDevices=true

# Explicit module loading will be denied. This allows to turn off module load and unload
# operations on modular kernels. It is recommended to turn this on for most services that
# do not need special file systems or extra kernel modules to work.
ProtectKernelModules=true

# Kernel variables accessible through /proc/sys, /sys, /proc/sysrq-trigger, /proc/latency_stats,
# /proc/acpi, /proc/timer_stats, /proc/fs and /proc/irq will be made read-only to all processes
# of the unit. Usually, tunable kernel variables should only be written at boot-time, with the
# sysctl.d(5) mechanism. Almost no services need to write to these at runtime; it is hence
# recommended to turn this on for most services.
ProtectKernelTunables=true

# The Linux Control Groups (cgroups(7)) hierarchies accessible through /sys/fs/cgroup will be
# made read-only to all processes of the unit. Except for container managers no services should
# require write access to the control groups hierarchies; it is hence recommended to turn this on
# for most services
ProtectControlGroups=true

# Any attempts to enable realtime scheduling in a process of the unit are refused.
RestrictRealtime=true

# Restricts the set of socket address families accessible to the processes of this unit.
# Protects against vulnerabilities such as CVE-2016-8655
RestrictAddressFamilies=AF_INET AF_INET6 AF_NETLINK AF_UNIX

# Takes away the ability to create or manage any kind of namespace
RestrictNamespaces=true

[Install]
WantedBy=multi-user.target

Start the process

sudo systemctl start php-fpm-8.2.5.service

enable at startup

systemctl enable php-fpm-8.2.5.service

In project’s apache config, add this inside the . The default port is 9000. If you want to change it, it’s done in ~/.phpenv/versions/8.2.5/etc/php-fpm.d/www.conf:

<FilesMatch \.php$>
        SetHandler proxy:fcgi://127.0.0.1:9000
</FilesMatch>

inside project folder add file called .php-version with

8.2.5

In wp-config.php change localhost to 127.0.0.1. new PHP 8.2.5 FPM is using TCP instead of the Unix socket.

/** MySQL hostname */
define( 'DB_HOST', '127.0.0.1' );

WordPress should work with new php version now:)

Add even more php versions.

cd ~/.phpenv/plugins/php-build/
phpenv rehash
PHP_BUILD_CONFIGURE_OPTS="--with-sodium --with-mysqli --with-pdo-mysql" \
phpenv install 8.4.8

If you want to build new php version but you get error like this: [Error] Definition 8.4.8 not found -> download the last definitions with pulling last changes. Remote repo is : https://github.com/php-build/php-build.git

cd ~./phpenv/plugins/php-builds
git pull

Leave a Reply

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