1. Upgrade Background
My WordPress production environment runs on Alibaba Cloud ECS, using OneinStack to deploy Nginx, PHP-FPM, and Redis.
The current website is not a standard single-domain WordPress installation; instead, multiple domains access the same WordPress installation:
Chinese site: https://www.shuijingwanwq.com
English site: https://en.shuijingwanwq.com
Admin domain: https://admin.shuijingwanwq.com
Media domain: https://media.shuijingwanwq.com
Specifically:
- Polylang identifies languages based on different domains;
- W3 Total Cache provides Page Cache and Object Cache;
- Object Cache uses Redis;
- The Chinese site is connected to EdgeOne;
- The English site is connected to Cloudflare;
- The backend is accessed via an independent
adminsubdomain.
Before the formal upgrade, I reviewed the major software versions and the upgrade order on the server. For relevant background, you can refer to the previous record:
Previous record on server software versions and upgrade order
The main versions confirmed at that time included:
Operating System: Alibaba Cloud Linux 3
WordPress: 7.0.2
Nginx: 1.24.0
PHP: 8.1.19
Redis Server: 7.0.11
W3 Total Cache: 2.10.3
Polylang: 3.8.6
I ultimately decided not to upgrade all components simultaneously, but to handle PHP first.
The reason is simple: PHP directly affects WordPress, plugins, WP-CLI, PHP-FPM, and extension compatibility. If Nginx, PHP, and Redis are upgraded at the same time, it is difficult to quickly determine which layer caused an issue when something goes wrong.
Therefore, this round only upgrades PHP from 8.1.19 to 8.5.9, leaving Nginx and the Redis server for subsequent separate handling.
2. Confirm the Production Environment Structure Before Upgrading
The current PHP installation directory is:
/usr/local/php
The Socket used by PHP-FPM is:
/dev/shm/php-cgi.sock
The systemd service file also starts PHP-FPM from this directory:
/usr/local/php/sbin/php-fpm
PHP-FPM currently uses dynamic process management:
pm = dynamic
pm.max_children = 10
pm.start_servers = 2
pm.min_spare_servers = 2
pm.max_spare_servers = 4
pm.max_requests = 500
request_terminate_timeout = 600s
request_slowlog_timeout = 3s
The WordPress memory limit is set to:
define('WP_MEMORY_LIMIT', '256M');
define('WP_MAX_MEMORY_LIMIT', '512M');
Before upgrading, I also confirmed that all three domains point to the same WordPress root directory and process PHP requests through the same PHP-FPM Socket.
This step is crucial because, after the PHP upgrade is complete, you cannot just check php -v in the command line; you must also verify that Nginx is actually connecting to the new PHP-FPM.
3. Adjusting the OneinStack PHP 8.5 Installation Script
I downloaded the new version of OneinStack and used its PHP 8.5 installation logic.
However, using the original script directly does not fully adapt to the current environment, so I made a few adjustments first.
1. Adjust OPcache Configuration
After this PHP 8.5 build completed, php -v directly displayed:
with Zend OPcache v8.5.9
Therefore, the configuration only needs to retain the opcache.* parameter; there is no need to repeatedly load opcache.so as a regular external extension.
2. Retain Phar
WP-CLI on the server is a Phar file:
/usr/local/bin/wp
Therefore, Phar must be retained when building PHP 8.5; otherwise, WP-CLI might not run after the upgrade.
3. Handle iconv Header Conflict
The server contains:
/usr/local/include/iconv.h
During the configuration phase, PHP 8.5 detected both GNU libiconv and the system iconv, resulting in a mixed-usage conflict.
The final solution was to temporarily move this header file during PHP configuration, compilation, and installation, and then automatically restore it after the installation finished.
This handling only targeted the header file; the original libiconv dynamic library was not deleted.
4. Retain Necessary Extensions
Before upgrading, I confirmed that the main extensions actually required by the current WordPress installation include:
mysqli
pdo_mysql
mbstring
curl
intl
zip
gd
fileinfo
Phar
LDAP
Redis
Imagick
OPcache
Although the MongoDB extension was previously installed in the old PHP, no usage traces were found in the WordPress configuration, Drop-ins, MU Plugins, or active plugins, so the MongoDB extension was not reinstalled this time.
4. Executing the PHP 8.5.9 Production Upgrade
For the formal installation, I first stopped PHP-FPM and moved the original PHP directory to a new timestamped directory:
/usr/local/php-8.1.19-before-php85-20260804-182956
I then used OneinStack to install PHP 8.5.9 and specified the extensions:
cd /root/oneinstack-latest-20260804
./install.sh \
--php_option 15 \
--php_extensions imagick,ldap,redis
At the end of the installation, OneinStack output:
####################Congratulations########################
Total OneinStack Install Time: 10 minutes
PHP install dir: /usr/local/php
However, the status received by the outer installation script was:
INSTALL_STATUS=1
PHP 8.5.9 installation not completed.
If you only look at the exit status, it is easy to conclude that the entire PHP upgrade failed.
But I did not immediately reinstall; instead, I checked the actual results first.
5. Installation Status of 1 Does Not Mean the PHP Core Failed to Install
After checking, I confirmed:
PHP 8.5.9 (cli)
Zend Engine v4.5.9
Zend OPcache v8.5.9
PHP-FPM had also started normally:
Active: active (running)
The Socket exists normally:
/dev/shm/php-cgi.sock
The main modules have been loaded:
curl
fileinfo
gd
intl
ldap
mbstring
mysqli
pdo_mysql
Phar
redis
Zend OPcache
zip
Requests to the three origin sites were also all normal:
www.shuijingwanwq.com HTTP=200
en.shuijingwanwq.com HTTP=200
admin.shuijingwanwq.com HTTP=200
This shows that the PHP 8.5.9 core, PHP-FPM, and the LDAP and Redis extensions were actually all installed successfully.
After further checking the installation logs, the only error that actually caused the return status of 1 was Imagick:
fatal error: ext/standard/php_smart_string.h:
No such file or directory
PHP imagick module install failed
OneinStack was using Imagick 3.8.0 at the time. When compiled in the PHP 8.5 environment, it referenced a file that does not exist in the current PHP headers, so it failed.
Therefore, this was not a PHP 8.5.9 installation failure, but a failure to install the additional Imagick extension.
6. Switching to Imagick 3.8.1
I then downloaded Imagick 3.8.1:
imagick-3.8.1.tgz
SHA-256:
3a3587c0a524c17d0dad9673a160b90cd776e836838474e173b549ed864352ee
When I first ran configure, I encountered a new error:
Please provide a path to MagickWand-config
or Wand-config program.
Indeed, none of these could be found among the system commands:
convert
magick
MagickWand-config
However, further inspection of the old PHP 8.1 imagick.so revealed that the ImageMagick it actually linked to was located at:
/usr/local/imagemagick
The corresponding dynamic libraries are:
/usr/local/imagemagick/lib/libMagickWand-7.Q16HDRI.so.10
/usr/local/imagemagick/lib/libMagickCore-7.Q16HDRI.so.10
The ImageMagick version is:
ImageMagick 7.1.1-10 Q16-HDRI
The problem was simply that /usr/local/imagemagick/bin was not located within the current PATH.
Therefore, I reconfigured the environment variables and explicitly specified the ImageMagick installation path:
export PATH="/usr/local/imagemagick/bin:/usr/local/php/bin:$PATH"
export PKG_CONFIG_PATH="/usr/local/imagemagick/lib/pkgconfig:/usr/local/imagemagick/lib64/pkgconfig:${PKG_CONFIG_PATH:-}"
export LD_LIBRARY_PATH="/usr/local/imagemagick/lib:${LD_LIBRARY_PATH:-}"
/usr/local/php/bin/phpize
./configure \
--with-php-config=/usr/local/php/bin/php-config \
--with-imagick=/usr/local/imagemagick
make -j2
make install
This time, the compilation succeeded, generating:
/usr/local/php/lib/php/extensions/no-debug-non-zts-20250925/imagick.so
I then wrote it into the PHP configuration:
[imagick]
extension=imagick.so
And reloaded PHP-FPM.
Final confirmation:
imagick module version => 3.8.1
Imagick compiled with ImageMagick version =>
ImageMagick 7.1.1-10 Q16-HDRI
Imagick using ImageMagick library version =>
ImageMagick 7.1.1-10 Q16-HDRI
I also actually generated a 2×2 pixel PNG via PHP:
imagick=3.8.1 bytes=127 format=PNG
This demonstrates that Imagick is not only loadable as an extension, but actual image operations can be performed successfully.
7. Verifying Whether WordPress is Actually Running on PHP 8.5.9
After upgrading PHP, you cannot just verify the PHP-FPM process.
My site also involves:
- WordPress;
- Polylang;
- W3 Total Cache;
- Redis Object Cache;
- Three domains: Chinese, English, and admin;
- canonical and hreflang;
- REST API.
Therefore, I continued with WordPress runtime verification.
WP-CLI returned:
WordPress:7.0.2
PHP_VERSION=8.5.9
WP_MEMORY_LIMIT=256M
WP_MAX_MEMORY_LIMIT=512M
WP_CACHE=true
ext_object_cache=true
redis=6.3.0
imagick=3.8.1
ldap=loaded
opcache=8.5.9
The two W3TC Drop-in files also still exist:
wp-content/advanced-cache.php
wp-content/object-cache.php
The Redis server responded normally:
PONG
8. Verifying Multilingual Page Output
To bypass existing page caches, I added random query parameters to the requests and directly requested the local origin server.
Chinese homepage output:
<html lang="zh-CN">
<link rel="alternate"
href="https://www.shuijingwanwq.com/"
hreflang="zh" />
<link rel="alternate"
href="https://en.shuijingwanwq.com/"
hreflang="en" />
<link rel="canonical"
href="https://www.shuijingwanwq.com/" />
English homepage output:
<html lang="en-US">
<link rel="alternate"
href="https://www.shuijingwanwq.com/"
hreflang="zh" />
<link rel="alternate"
href="https://en.shuijingwanwq.com/"
hreflang="en" />
<link rel="canonical"
href="https://en.shuijingwanwq.com/" />
Next, I checked a pair of linked Chinese and English articles:
Chinese article:
https://www.shuijingwanwq.com/2026/07/31/21018/
English article:
https://en.shuijingwanwq.com/2026/07/31/21022/
For both articles, the following:
<html lang>;- canonical;
- Chinese and English hreflang;
- RSS Feed;
- oEmbed URL;
- REST API URL;
all pointed to the correct language domains.
This shows that Polylang’s multi-domain language identification was not broken after the PHP upgrade.
9. An Easily Misjudged WP-CLI Object Cache Test
Initially, I tested the Object Cache using two independent WP-CLI processes:
The first process wrote:
cache_set=true
The second process read:
cache_get=false
Scanning Redis directly also failed to find the corresponding test key.
At first, it looked like the W3TC Redis Object Cache had stopped working after the PHP 8.5 upgrade.
But upon further inspection, I found that the object cache class in WP-CLI was:
WP_Object_Cache
Whereas the object cache class in frontend PHP-FPM requests was:
W3TC\ObjectCache_WpObjectCache
In other words, the current WP-CLI context did not load W3TC’s Redis persistence implementation; it only used WordPress’s default in-process object cache.
Therefore:
- A value just written can be read within the same WP-CLI process;
- The second WP-CLI process cannot read it;
- This cannot be used to determine whether the frontend Redis Object Cache is working properly.
10. Verifying Redis Object Cache via Two Independent FPM Requests
To verify the actual frontend environment, I temporarily created a PHP probe and executed it via two separate HTTP requests:
wp_cache_set()
wp_cache_get()
wp_cache_delete()
After writing under the Chinese domain, the second Chinese request was able to read it:
host=www.shuijingwanwq.com
object_cache_class=W3TC\ObjectCache_WpObjectCache
set=true
found=true
value=www.shuijingwanwq.com
After writing under the English domain, the second English request could also read it:
host=en.shuijingwanwq.com
object_cache_class=W3TC\ObjectCache_WpObjectCache
set=true
found=true
value=en.shuijingwanwq.com
This proves that the W3TC Redis Object Cache can persist across requests in the PHP 8.5.9 FPM environment.
More importantly, the object written under the Chinese domain could not be read under the English domain:
www write
en read:
found=false
The object written under the English domain likewise could not be read under the Chinese domain:
en write
www read:
found=false
Ultimately, both sides could precisely delete the test objects:
delete=true
This verification confirmed two things:
- W3TC Redis Object Cache under PHP 8.5.9 is working properly;
- The Object Cache for the Chinese and English sites is isolated by Host.
After the test, the temporary PHP probe was automatically deleted.
Throughout this process, Redis was not flushed, nor was W3TC’s Purge All Caches executed.
11. REST API and Error Log Verification
The REST APIs for both the Chinese and English sites returned 200:
www.shuijingwanwq.com REST HTTP=200
en.shuijingwanwq.com REST HTTP=200
I then checked the PHP-FPM and Nginx error logs and found no:
Fatal error
Parse error
Uncaught exception
Segmentation fault
Cannot load
Undefined symbol
upstream prematurely closed
connect failed
Ultimately, the three origin sites remained normal:
www.shuijingwanwq.com HTTP=200
en.shuijingwanwq.com HTTP=200
admin.shuijingwanwq.com HTTP=200
12. Plugin Deprecated Notices Exposed by PHP 8.5
After the PHP 8.5 upgrade was completed, several types of compatibility notices appeared in the WP-CLI output.
1. SyntaxHighlighter Evolved
The current version is:
SyntaxHighlighter Evolved 3.7.2
The plugin source code contains:
case 'false';
default;
PHP 8.5 suggests using a colon:
case 'false':
default:
However, I am currently batch processing historical SyntaxHighlighter articles. Once all the historical articles have been converted, this plugin will be directly deactivated.
Therefore, I did not modify the third-party plugin source code this time.
2. Organize Series
Organize Series uses:
SplObjectStorage::attach()
SplObjectStorage::contains()
PHP 8.5 has already output Deprecated for these two methods, recommending the use of:
SplObjectStorage::offsetSet()
SplObjectStorage::offsetExists()
Currently, these notices do not cause frontend errors, so they were not handled alongside the PHP upgrade; I will inspect them separately later.
3. WP-CLI Deprecated Output
When executing a WP-CLI command containing an invalid field, the following appeared consecutively:
Using null as an array offset is deprecated
However, after reusing the correct command and capturing standard output and standard error separately, the results were:
command_status=0
stderr_bytes=0
json_valid=true
This indicates that WP-CLI 2.12.0 can still output and parse JSON normally under PHP 8.5.9, without affecting the ongoing historical article batch processing.
13. Deleting the xprober.php Automatically Left by OneinStack
After the OneinStack installation finished, it left behind the following in the default site directory:
/data/wwwroot/default/xprober.php
This file can output a large amount of server and PHP environment information, and there is no need to expose it in the web directory long-term.
I first backed it up to:
/root/xprober.php.after-php85-20260804-190732
I then deleted it from the public directory and specifically checked:
/data/wwwroot/default
/data/wwwroot/www.shuijingwanwq.com
Ultimately, no other public copies of xprober.php were found.
After deletion, PHP-FPM and the three domains remained normal.
14. Final Upgrade Results
The final outcome of this PHP upgrade is as follows:
PHP 8.1.19 → PHP 8.5.9: Pass
PHP-FPM: Pass
Zend OPcache 8.5.9: Pass
Redis PHP Extension 6.3.0: Pass
Imagick 3.8.1: Pass
ImageMagick 7.1.1-10: Pass
LDAP: Pass
Phar / WP-CLI: Pass
WordPress 7.0.2: Pass
W3TC Redis Object Cache: Pass
www / en Object Cache Host isolation: Pass
Chinese page lang / canonical / hreflang: Pass
English page lang / canonical / hreflang: Pass
REST API: Pass
www / en / admin three origin sites: Pass
It should be noted that:
Redis PHP Extension: Recompiled with PHP 8.5
Redis Server: Still 7.0.11
Nginx: Still 1.24.0
So what was completed this time was a PHP layer upgrade, not a unified upgrade of the entire server software stack.
15. Future Plans
The server is currently batch processing historical WordPress articles.
Continuing to upgrade Nginx or the Redis server at this stage would introduce too many variables into the production environment simultaneously. If a batch timeout, object cache anomaly, or page output change occurs, it would be difficult to quickly distinguish whether it is an issue with the article processing script or caused by the underlying service upgrade.
Therefore, the upcoming schedule is:
- Continue completing the historical article conversion;
- Deactivate SyntaxHighlighter after article processing is complete;
- Separately handle PHP 8.5 Deprecated notices that still need to be retained;
- Only upgrade Nginx in the next round;
- After Nginx stabilizes, upgrade the Redis server separately.
The most noteworthy point to record during this upgrade process is:
A non-zero status returned by the installation script does not necessarily mean the entire software is unusable.
The correct approach is not to immediately reinstall upon seeing INSTALL_STATUS=1, but to continue verifying:
- Which component failed;
- Whether the PHP core is installed;
- Whether PHP-FPM is running;
- Whether the Socket exists;
- Whether necessary extensions are loaded;
- Whether WordPress starts normally;
- Whether multi-domain language output is correct;
- Whether the Object Cache is truly persisting across requests;
- Whether there are any fatal errors in the service logs.
Ultimately, it was confirmed that the reason for the status of 1 this time was merely the Imagick 3.8.0 compilation failure. After installing Imagick 3.8.1, PHP 8.5.9 has been running stably in the current WordPress multi-domain production environment.

