myds
Building and Deploying a Community PHP 5.6 Patch on aaPanel
This guide documents a tested way to build an isolated PHP-FPM 5.6 binary with the community PHP2Shell backport, test it, and switch aaPanel Apache virtual hosts to it without replacing the existing PHP installation.
This is an unsupported legacy-runtime procedure. PHP 5.6 is end of life. The patch is community-maintained, not an official PHP 5.6 release. Test it on a staging system, keep backups, and retain a rollback path. The patch repository also documents a separate var_destroy unserialize use-after-free that is outside this patch; do not pass attacker-controlled data to unserialize().
Tested setup and patch source
The procedure below was exercised on Rocky Linux 9.8 x86_64 with aaPanel and PHP 5.6.40 NTS/FPM. It used this repository revision:
- Repository: Anton-Barinov/php2shell-patches
- Revision tested: 591ccdd15a781fbdbaf7bbea7485974f644fbe3d
- Patch: patches/php-5.6-enhanced-universal.patch
Use a current review of the patch before applying it elsewhere. Do not assume a community patch is equivalent to an upstream supported release.
- Record the existing PHP build and back up configuration
Use the PHP 5.6 paths from your aaPanel installation. Record the current version, configure command, extension directory, FPM configuration, and PHP configuration:
/www/server/php/56/bin/php -v
/www/server/php/56/bin/php -i | grep -E 'Configure Command|Thread Safety|extension_dir'
grep -nE 'include|[www]|pm.|listen|user|group' \
/www/server/php/56/etc/php-fpm.conf
Back up the PHP and web-server configuration before editing:
STAMP=$(date +%F-%H%M%S)
cp -a /www/server/php/56/etc/php.ini \
"/www/server/php/56/etc/php.ini.bak-$STAMP"
cp -a /www/server/php/56/etc/php-fpm.conf \
"/www/server/php/56/etc/php-fpm.conf.bak-$STAMP"
tar -czf "/root/php56-vhosts-$STAMP.tar.gz" \
-C /www/server/panel/vhost apache nginx/phpfpm_status.conf
cp -a /www/server/apache/conf/extra/httpd-vhosts.conf \
"/www/server/apache/conf/extra/httpd-vhosts.conf.bak-$STAMP"
Also inventory every active vhost reference to the old PHP 5.6 socket. aaPanel may keep per-site Apache configs and a separate PHP-FPM status location:
grep -RniE 'fastcgi_pass|SetHandler.*php-cgi-56.sock' \
/www/server/panel/vhost/apache \
/www/server/panel/vhost/nginx \
/www/server/apache/conf 2>/dev/null
- Fetch PHP 5.6.40 and apply the patch in a separate source tree
Do not build over the aaPanel production prefix. The example below uses an isolated source directory:
mkdir -p /usr/local/src/php56-patch-build
cd /usr/local/src/php56-patch-build
git clone --depth 1 \
https://github.com/Anton-Barinov/php2shell-patches.git
git -C php2shell-patches rev-parse HEAD
curl -fL --retry 3 -o php-5.6.40.tar.gz \
https://github.com/php/php-src/archive/refs/tags/php-5.6.40.tar.gz
mkdir php-5.6.40
tar -xzf php-5.6.40.tar.gz -C php-5.6.40 --strip-components=1
cd php-5.6.40
patch --dry-run -p1 \
< ../php2shell-patches/patches/php-5.6-enhanced-universal.patch
patch -p1 \
< ../php2shell-patches/patches/php-5.6-enhanced-universal.patch
Verify the repository revision and the dry-run result before applying. The dry run should report the expected PHP source files without rejects.
- Build with the existing extension and ABI requirements
The existing aaPanel PHP build used custom OpenSSL, curl, ICU, and other libraries. A minimal generic configure command can silently omit extensions or link against incompatible libraries.
Read the original Configure Command from php -i. Reuse its required options and library paths, but set a new prefix and a config path for the candidate. Keep the same architecture, NTS/ZTS mode, FPM support, and extension choices as the installed build. Install matching development headers for the extensions being built.
Example shape only—fill in the options from your existing build:
./buildconf --force
./configure \
--prefix=/www/server/php/56-patched-test \
--with-config-file-path=/www/server/php/56-patched-test/etc \
--enable-fpm \
[the matching options and library paths from the existing PHP 5.6 build]
make -j2
Do not copy the bracketed text literally. Review the configure output for missing libraries and extensions before continuing. If a build step requires a scanner generator such as re2c, install the matching build dependency rather than assuming generated files are current.
- Run the patch regression tests
Set both test executables so the session tests can use the freshly built CGI binary:
TEST_PHP_EXECUTABLE="$PWD/sapi/cli/php" \
TEST_PHP_CGI_EXECUTABLE="$PWD/sapi/cgi/php-cgi" \
NO_INTERACTION=1 REPORT_EXIT_STATUS=1 \
make test TESTS="ext/session/tests/rfc1867_no_user_code.phpt ext/session/tests/rfc1867_incomplete_class_reserved_props.phpt ext/standard/tests/serialize/incomplete_class_raw_payload_markers.phpt"
In the tested build, all three targeted tests passed. A broader run of ext/session/tests and ext/standard/tests/serialize also completed with no failures. Test results can vary with build options and environment; investigate skips, warnings, and failures rather than treating a successful compilation as sufficient.
Install only into the candidate prefix:
make install
mkdir -p /www/server/php/56-patched-test/var/log \
/www/server/php/56-patched-test/var/run
- Prepare a separate PHP-FPM config and socket
Copy the current PHP configuration, then set the candidate's extension directory to the existing PHP 5.6 extension directory only if the extension ABI matches (same PHP 5.6 build ABI, architecture, and thread-safety mode). Confirm each external loader/module loads with the candidate binary.
cp -a /www/server/php/56/etc/php.ini \
/www/server/php/56-patched-test/etc/php.ini
sed -i -E '/^[;[:space:]]extension_dir[[:space:]]=/d' \
/www/server/php/56-patched-test/etc/php.ini
printf '\nextension_dir = /www/server/php/56/lib/php/extensions/no-debug-non-zts-20131226\n' \
/www/server/php/56-patched-test/etc/php.ini
Use the actual extension directory reported by your server; do not assume the example ABI directory is universal.
Copy the FPM config, redirect all prefix-based paths, and give the candidate a distinct socket:
cp -a /www/server/php/56/etc/php-fpm.conf \
/www/server/php/56-patched-test/etc/php-fpm.conf
sed -i 's#/www/server/php/56#/www/server/php/56-patched-test#g' \
/www/server/php/56-patched-test/etc/php-fpm.conf
sed -i -E \
's#^[[:space:]]listen[[:space:]]=[[:space:]]*/tmp/php-cgi-56.sock#listen = /tmp/php-cgi-56-patched.sock#' \
/www/server/php/56-patched-test/etc/php-fpm.conf
Set pool capacity based on actual traffic and memory use. The test host retained its existing capacity settings (pm.max_children = 300, start_servers = 30, spare servers 20–50) and used pm.max_requests = 1. Recycling after every request causes high worker churn; measure it and choose a suitable value for your workload. It is not a substitute for the source patch.
Validate the candidate and confirm the required modules/loaders:
/www/server/php/56-patched-test/sbin/php-fpm -t \
-p /www/server/php/56-patched-test \
-y /www/server/php/56-patched-test/etc/php-fpm.conf
/www/server/php/56-patched-test/bin/php \
-d extension_dir=/www/server/php/56/lib/php/extensions/no-debug-non-zts-20131226 \
-c /www/server/php/56/etc/php.ini -m
Use the correct config and extension paths for your build. Test ionCube, Zend Guard, vendor extensions, and application-specific modules explicitly.
- Run the candidate FPM as its own systemd service
Create /etc/systemd/system/php-fpm-56-patched.service with the candidate paths:
[Unit]
Description=PHP 5.6 patched FPM
After=network.target
[Service]
Type=simple
ExecStartPre=/www/server/php/56-patched-test/sbin/php-fpm -t -p /www/server/php/56-patched-test -y /www/server/php/56-patched-test/etc/php-fpm.conf
ExecStart=/www/server/php/56-patched-test/sbin/php-fpm -F -p /www/server/php/56-patched-test -y /www/server/php/56-patched-test/etc/php-fpm.conf
ExecStop=/bin/kill -QUIT $MAINPID
Restart=on-failure
RestartSec=2
[Install]
WantedBy=multi-user.target
If a manually launched test FPM is already running, stop it gracefully before starting the unit. Then:
systemctl daemon-reload
systemctl enable --now php-fpm-56-patched.service
systemctl status --no-pager php-fpm-56-patched.service
ss -xl | grep 'php-cgi-56-patched.sock'
Before switching production traffic, send a FastCGI smoke request through the candidate socket and confirm the FPM SAPI, version, and required extensions. Keep the old FPM running at this point.
- Switch Apache vhosts, validate, and test
Update only active configuration files that reference the old socket. Back up the current files first. Also update the Apache PHP-FPM status location in /www/server/apache/conf/extra/httpd-vhosts.conf; otherwise the status URL can continue querying the old pool.
grep -RIlZ --include='*.conf' 'php-cgi-56.sock' \
/www/server/panel/vhost/apache \
/www/server/panel/vhost/nginx |
xargs -0 -r sed -i 's#/tmp/php-cgi-56.sock#/tmp/php-cgi-56-patched.sock#g'
sed -i 's#/tmp/php-cgi-56.sock#/tmp/php-cgi-56-patched.sock#g' \
/www/server/apache/conf/extra/httpd-vhosts.conf
/www/server/apache/bin/httpd -t &&
/www/server/apache/bin/httpd -k graceful
If Nginx is installed and actually serves traffic on your setup, validate and reload it too. Do not assume aaPanel has Nginx simply because an Nginx vhost directory exists.
Check that the Apache FPM status URL reports the candidate pool, verify no active config references the old socket, and test every PHP 5.6 site. For a local HTTP test, replace example.test with a hostname configured on your server:
curl -sS --max-time 10 -H 'Host: localhost' \
http://127.0.0.1/phpfpm_56_status | head -n 15
grep -Rni --include='*.conf' 'php-cgi-56.sock' \
/www/server/panel/vhost/apache \
/www/server/panel/vhost/nginx \
/www/server/apache/conf 2>/dev/null
curl -sS --max-time 15 --resolve example.test:80:127.0.0.1 \
-o /dev/null -w '%{http_code}\n' http://example.test/
Keep the old FPM service available until the sites and application-specific flows have been verified. Once confirmed, disable the old service so it does not return on reboot:
systemctl disable --now php-fpm-56.service
systemctl is-active php-fpm-56-patched.service
systemctl is-enabled php-fpm-56-patched.service
ss -xl | grep -F '/tmp/php-cgi-56'
The expected result is the patched service active/enabled, the old service inactive/disabled, and only the candidate socket listening.
Rollback
If the candidate causes errors, start the old service first and confirm its socket exists. Then change the active Apache vhost and status references back to /tmp/php-cgi-56.sock, test Apache configuration, and perform a graceful reload. Verify the sites before stopping the candidate service.
systemctl enable --now php-fpm-56.service
test -S /tmp/php-cgi-56.sock || exit 1
grep -RIlZ --include='*.conf' 'php-cgi-56-patched.sock' \
/www/server/panel/vhost/apache \
/www/server/panel/vhost/nginx |
xargs -0 -r sed -i 's#/tmp/php-cgi-56-patched.sock#/tmp/php-cgi-56.sock#g'
sed -i 's#/tmp/php-cgi-56-patched.sock#/tmp/php-cgi-56.sock#g' \
/www/server/apache/conf/extra/httpd-vhosts.conf
/www/server/apache/bin/httpd -t &&
/www/server/apache/bin/httpd -k graceful
Only after confirming the old pool is serving traffic should you disable the patched service.
Operational notes
- This changes PHP-FPM for web requests. The original CLI binary at /www/server/php/56/bin/php remains unchanged; cron jobs and shell scripts using it are not patched by this procedure.
- Keep the source tree, patch revision, build configuration, PHP-FPM unit, and backups for future maintenance and rollback.
- Monitor the FPM status page for a nonzero listen queue, max-children exhaustion, and slow requests after the cutover.
- Plan eventual application migration to a supported PHP branch. PHP 5.6.40 was the final scheduled PHP 5.6 release; PHP 5 support ended in January 2019.
References
- PHP 5.6.40 release announcement
- PHP unsupported branches
- Community PHP2Shell patches and patch notes
- Exact repository revision used in this procedure