没有不值得去解决的问题,也没有不值得去学习的技术!

Upgrading Nginx 1.24.0 to 1.30.4 and OpenSSL 3.5.7 with Alibaba Cloud OneinStack

Upgrading Nginx 1.24.0 to 1.30.4 and OpenSSL 3.5.7 with Alibaba Cloud OneinStack

The day before, I had just completed the PHP 8.5.9 upgrade on this Alibaba Cloud ECS.

The server runs Alibaba Cloud Linux 3, and the web environment was deployed using OneinStack. After the PHP upgrade, I checked Nginx and found the current version was still:

Plaintext
nginx/1.24.0
OpenSSL 1.1.1t

Nginx 1.24.0 has been in use for quite some time, and the statically compiled OpenSSL 1.1.1t is also relatively old.

Considering that this server hosts my Chinese WordPress site, English site, admin subdomain, and static asset subdomain, I ultimately decided to proceed with the Nginx upgrade.

The goals this time were:

Plaintext
Nginx 1.24.0 → Nginx 1.30.4
OpenSSL 1.1.1t → OpenSSL 3.5.7
PCRE 8.45 → Continue using PCRE 8.45

This article documents the entire upgrade process, including the source code download failure, missing Perl modules, Python version compatibility issues, HTTP/2 configuration warnings encountered along the way, and the final comprehensive validation results.

1. Server Environment Before the Upgrade

Before the upgrade, I executed:

Bash
/usr/local/nginx/sbin/nginx -V

The key information obtained was:

Plaintext
nginx version: nginx/1.24.0
built with OpenSSL 1.1.1t 7 Feb 2023

The original compilation parameters were as follows:

Plaintext
--prefix=/usr/local/nginx
--user=www
--group=www
--with-http_stub_status_module
--with-http_sub_module
--with-http_v2_module
--with-http_ssl_module
--with-stream
--with-stream_ssl_preread_module
--with-stream_ssl_module
--with-http_gzip_static_module
--with-http_realip_module
--with-http_flv_module
--with-http_mp4_module
--with-openssl=../openssl-1.1.1t
--with-pcre=../pcre-8.45
--with-pcre-jit
--with-ld-opt=-ljemalloc

To minimize upgrade risks, I did not replace PCRE during this operation, nor did I change the original module combination.

This time, I only handled two core components:

  1. Upgrade Nginx to 1.30.4.
  2. Upgrade the statically compiled OpenSSL to 3.5.7.

2. Why I Didn’t Directly Run OneinStack’s Upgrade Script

Two OneinStack directories are retained on the server:

Plaintext
/root/oneinstack
/root/oneinstack-latest-20260804

Here, /root/oneinstack-latest-20260804 is the new OneinStack copy prepared during the PHP 8.5.9 upgrade the day before.

Yesterday’s operations only involved PHP-related scripts; the Nginx upgrade logic was not modified, and the Nginx upgrade was not executed.

When preparing to upgrade Nginx today, I re-examined the relevant scripts in this OneinStack copy. The current script would compile the new Nginx version using OpenSSL 1.1.1w and switch the binary via Nginx’s online hot upgrade process.

However, this time I wanted to upgrade OpenSSL to 3.5.7 as well, and I could accept brief website downtime.

Therefore, instead of directly running OneinStack’s built-in Nginx upgrade feature, I adopted the following approach:

  1. Download and compile the new version in an isolated directory.
  2. Keep the online Nginx 1.24.0 unchanged during compilation.
  3. Test the current production configuration using the new binary.
  4. Back up the old binary and the complete Nginx configuration.
  5. Gracefully stop the old Nginx.
  6. Overwrite the production binary and start the new version.
  7. If configuration testing or startup fails, immediately restore the old binary.

Although this method causes brief downtime, the entire process is straightforward, and the rollback path is clearer.

3. Compiling the New Version in a Bypass Directory First

I created an isolated directory for this compilation:

Plaintext
/root/nginx-build-1.30.4-openssl-3.5.7

The temporary binary after compilation was placed in:

Plaintext
/root/nginx-1.30.4-openssl-3.5.7

Until the new binary passes configuration testing, the following in the production environment will not be modified:

Plaintext
/usr/local/nginx/sbin/nginx

This bypass compilation method has an obvious advantage: even if downloading, configuring, or compiling fails, the currently running Nginx will not be affected.

4. First Attempt to Download OpenSSL from GitHub Failed

The Nginx 1.30.4 source package can be downloaded normally from the official Nginx website, but when downloading OpenSSL 3.5.7 from GitHub Releases, the connection failed after persisting for about 90 seconds:

Plaintext
curl: (52) Empty reply from server
Connection to 121.40.248.29 closed.

This actually involves two issues.

The first issue is that this Alibaba Cloud ECS has unstable access to GitHub Release Assets.

The second issue is that I directly executed the following in the interactive SSH shell:

Bash
set -euo pipefail

Here, set -e means that as soon as any command returns a non-zero status, the shell exits immediately.

Therefore, after the curl download failed, not only did the download command stop, but the entire remote shell exited as well, which manifested as the SSH connection closing directly.

Later, I made two adjustments:

  1. Switched to an Alibaba Cloud mirror to download OpenSSL 3.5.7.
  2. Ran the subsequent download and compilation process in a separate Bash subprocess.

For example:

Bash
bash <<'BASH'
set -euo pipefail

# 下载、校验和编译操作
BASH

This way, even if the subprocess fails again, it will only terminate the subprocess without closing the current SSH session.

After the OpenSSL download completed, I also verified the SHA-256 of the source package:

Plaintext
a8c0d28a529ca480f9f36cf5792e2cd21984552a3c8e4aa11a24aa31aeac98e8

The verification result was:

Plaintext
openssl-3.5.7.tar.gz: 成功

5. Nginx Configuration Stage Passed Smoothly

After extracting the Nginx, OpenSSL, and PCRE source codes, I extracted the original compilation parameters from the current production binary.

Then I only replaced the OpenSSL path:

Plaintext
--with-openssl=../openssl-1.1.1t

Changed to:

Plaintext
--with-openssl=../openssl-3.5.7

PCRE was still retained:

Plaintext
--with-pcre=../pcre-8.45
--with-pcre-jit

Nginx’s configure stage completed smoothly and correctly identified:

Plaintext
using PCRE library: ../pcre-8.45
using OpenSSL library: ../openssl-3.5.7
using system zlib library

This indicates that there were no issues with Nginx’s configuration parameters, and we could proceed to the formal compilation stage.

6. First Compilation Attempt Missing Perl Module IPC::Cmd

After formally executing make, the OpenSSL configuration process first reported an error:

Plaintext
Can't locate IPC/Cmd.pm in @INC

Further information showed that the OpenSSL 3.5.7 configuration script requires the Perl module:

Plaintext
IPC::Cmd

On Alibaba Cloud Linux 3, it can be installed directly via the system package manager:

Bash
dnf install -y perl-IPC-Cmd

After installation, I verified it:

Bash
perl -MIPC::Cmd -e \
    'print "IPC::Cmd version: $IPC::Cmd::VERSION\n"'

The output was:

Plaintext
IPC::Cmd version: 1.02

I then re-executed the compilation.

7. Second Attempt Missing Time::Piece Again

After supplementing IPC::Cmd, OpenSSL was able to start generating the configuration, but a new error immediately appeared:

Plaintext
Can't locate Time/Piece.pm in @INC

This time, the missing module was:

Plaintext
Time::Piece

If I continued installing modules one by one, I might encounter other missing Perl core modules later.

Therefore, instead of installing Time::Piece separately again, I directly installed the complete Perl core components:

Bash
dnf install -y perl-core

Then verified them separately:

Bash
perl -MIPC::Cmd -e \
    'print "IPC::Cmd version: $IPC::Cmd::VERSION\n"'

perl -MTime::Piece -e \
    'print "Time::Piece version: $Time::Piece::VERSION\n"'

After the required modules were ready, I executed make again.

This time, Nginx 1.30.4, OpenSSL 3.5.7, and PCRE 8.45 all compiled successfully.

8. Testing Production Configuration with the New Binary First

After compilation, I did not immediately overwrite the online Nginx; instead, I checked the new binary first:

Bash
/root/nginx-1.30.4-openssl-3.5.7 -V

The main output information was:

Plaintext
nginx version: nginx/1.30.4
built with OpenSSL 3.5.7 9 Jun 2026

The new version retained the original modules and compilation parameters, only upgrading the OpenSSL path to 3.5.7.

Next, I used the new binary to directly test the current production configuration:

Bash
/root/nginx-1.30.4-openssl-3.5.7 \
    -p /usr/local/nginx/ \
    -t \
    -c /usr/local/nginx/conf/nginx.conf

Both the configuration syntax check and the full test succeeded:

Plaintext
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful

The SHA-256 of the new binary was:

Plaintext
bd95deac509fd0d7720541f35cb0231711a235262067ae62695bea1b17ebed67

At this point, Nginx 1.24.0 was still running online; the new version was merely saved as an independent file under /root.

9. New Version Detects Old HTTP/2 Configuration Warnings

Although the configuration test passed, Nginx 1.30.4 issued several warnings:

Plaintext
the "listen ... http2" directive is deprecated,
use the "http2" directive instead

The affected virtual hosts included:

Plaintext
admin.shuijingwanwq.com.conf
cn-test.shuijingwanwq.com.conf
media.shuijingwanwq.com.conf
www.shuijingwanwq.com.conf

The original configuration syntax was:

Nginx
listen 443 ssl http2;
listen [::]:443 ssl http2;

This syntax still works in the new version, but it has been marked as deprecated.

Since it is only a warning and does not prevent Nginx from starting, I did not modify the configuration simultaneously before the formal binary switch.

This keeps the two steps independent of each other:

  1. First confirm that the Nginx 1.30.4 binary can normally take over production traffic.
  2. Then separately modify the HTTP/2 configuration and reload.

If issues arise during the process, it is easier to determine whether they are caused by the binary upgrade or the configuration modification.

10. Backing Up the Old Nginx and Complete Configuration

Before the formal switch, I created an upgrade backup directory:

Plaintext
/root/nginx-upgrade-backup-20260805-172623

The backup contents included:

Plaintext
Old Nginx 1.24.0 binary
Complete /usr/local/nginx/conf configuration directory
Old nginx -V output
SHA-256 of both old and new binaries

The old binary file was named:

Plaintext
nginx-1.24.0

If the new version’s configuration test failed or it could not start, the upgrade script would automatically:

  1. Stop the new Nginx that might have already started.
  2. Restore the old binary to the production path.
  3. Re-test the configuration using the old binary.
  4. Start the old Nginx.

11. Stopping the Old Nginx and Switching the Production Binary

After completing all checks and backups, I sent the QUIT signal to the old Nginx master process to allow it to exit gracefully:

Bash
kill -QUIT "$master_pid"

The old Nginx took about 29 seconds to stop completely.

Next, I installed the verified new binary to:

Plaintext
/usr/local/nginx/sbin/nginx

The installation command was similar to:

Bash
install \
    -m 0755 \
    /root/nginx-1.30.4-openssl-3.5.7 \
    /usr/local/nginx/sbin/nginx

After overwriting, I checked again:

Plaintext
nginx version: nginx/1.30.4
built with OpenSSL 3.5.7 9 Jun 2026

Then I re-ran the production configuration test and started Nginx 1.30.4:

Bash
/usr/local/nginx/sbin/nginx \
    -c /usr/local/nginx/conf/nginx.conf

After startup, the process status was:

Plaintext
nginx: master process /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
nginx: worker process
nginx: worker process

This indicates that both the new master and worker processes were running normally.

12. Why the Initial Local Origin Test Showed 000

After the switch, I used curl --resolve to force the domain to resolve to 127.0.0.1, directly testing the origin server.

The first test results were:

Plaintext
www.shuijingwanwq.com     000
en.shuijingwanwq.com      200
admin.shuijingwanwq.com   000

000 is not a real HTTP status code; it means curl failed to receive a complete response within the specified time.

Because the first command hid the error output, looking solely at 000 made it easy to mistakenly assume there were issues with the Nginx, TLS, or virtual host configuration.

Later, I re-tested using verbose mode:

Bash
curl \
    --http1.1 \
    --verbose \
    --insecure \
    --max-time 20 \
    --resolve "www.shuijingwanwq.com:443:127.0.0.1" \
    "https://www.shuijingwanwq.com/" \
    --output /dev/null

I finally discovered:

  1. Both www and admin had successfully established TLS 1.3 connections.
  2. Both sites ultimately returned HTTP/1.1 200 OK.
  3. The full response time was about 16 seconds.
  4. The timeout set for the first test was only 15 seconds.

In other words, the first 000 occurred simply because the test timeout was slightly shorter than the actual response time, not because the Nginx upgrade failed.

Upon re-testing, HTTPS requests to the origin server for all three sites returned 200 normally.

The public network test results were also normal:

Plaintext
https://www.shuijingwanwq.com/       200
https://en.shuijingwanwq.com/        200
https://admin.shuijingwanwq.com/     200

Among them:

  • The Chinese site is accessed via EdgeOne.
  • The English site is accessed via Cloudflare.
  • The admin subdomain is accessed directly at the origin server.

13. Modifying the Old HTTP/2 Configuration

After confirming that Nginx 1.30.4 was running normally, I began addressing the previously discovered HTTP/2 deprecation warnings.

First, I backed up the four virtual host configurations:

Plaintext
/root/nginx-http2-config-backup-20260805-173655

Then I changed the old syntax:

Nginx
listen 443 ssl http2;
listen [::]:443 ssl http2;

To:

Nginx
listen 443 ssl;
listen [::]:443 ssl;
http2 on;

This way, the listen port and enabling HTTP/2 were split into independent directives, conforming to the new Nginx configuration style.

14. Encountering Python 3.6 Compatibility Issues While Modifying Configuration

I initially used a Python script to batch modify the four virtual host configurations, which included the following type annotation:

Python
output: list[str] = []

An error occurred during execution:

Plaintext
TypeError: 'type' object is not subscriptable

Upon inspection, I found the server’s Python version was:

Plaintext
Python 3.6.8

Built-in generic syntax like list[str] requires a newer version of Python. Python 3.6 could not recognize it, so the script terminated before actually writing to the configuration files.

Fortunately, a backup had been created before the modification, and the script executed a restore operation, so the online configuration was not affected.

Later, I changed this line to standard syntax:

Python
output = []

While avoiding other newer Python syntax, the script was then able to execute normally in Python 3.6.8.

All four configuration files were ultimately converted to:

Nginx
listen 80;
listen [::]:80;
listen 443 ssl;
listen [::]:443 ssl;
http2 on;

After the configuration test succeeded, I executed a reload:

Bash
/usr/local/nginx/sbin/nginx -s reload

After the reload completed, the Nginx master process remained unchanged, and the two worker processes were updated, indicating that the new configuration was successfully loaded.

15. Comprehensive Validation Results

Finally, I executed:

Bash
/usr/local/nginx/sbin/nginx -V
/usr/local/nginx/sbin/nginx -t

Confirmed the online version was:

Plaintext
nginx version: nginx/1.30.4
built with OpenSSL 3.5.7 9 Jun 2026

Configuration test results:

Plaintext
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful

The deprecation warnings for the old HTTP/2 syntax have disappeared.

Nginx is listening normally on:

Plaintext
0.0.0.0:80
0.0.0.0:443
[::]:80
[::]:443

The master process and two worker processes are all running normally.

Public Network HTTP/2 Validation

All three public addresses returned 200 via HTTP/2:

Plaintext
www.shuijingwanwq.com
http_code=200
http_version=2

en.shuijingwanwq.com
http_code=200
http_version=2

admin.shuijingwanwq.com
http_code=200
http_version=2

Origin Server HTTP/2 Validation

Bypassing the CDN and resolving the domain directly to 127.0.0.1 yielded:

Plaintext
www.shuijingwanwq.com
http_code=200
http_version=2

admin.shuijingwanwq.com
http_code=200
http_version=2

This indicates that the origin server’s Nginx HTTP/2 configuration has actually taken effect, rather than just the CDN edge nodes supporting HTTP/2.

WordPress Functional Validation

The following key URLs all returned 200:

Plaintext
https://www.shuijingwanwq.com/wp-json/
https://www.shuijingwanwq.com/wp-login.php
https://en.shuijingwanwq.com/wp-json/
https://admin.shuijingwanwq.com/wp-admin/

This indicates that the WordPress REST API, login page, English site, and admin dashboard are all accessible normally.

PHP-FPM Validation

The PHP-FPM master and worker processes are running normally and listening on:

Plaintext
/dev/shm/php-cgi.sock

Therefore, the FastCGI communication between Nginx and PHP-FPM was not affected by this upgrade.

Error Log Validation

After checking the Nginx logs, no new severe errors like the following were found after this upgrade:

Plaintext
emerg
alert
crit
segfault
upstream timed out
connect() failed
prematurely closed

The default error log still shows the previously generated HTTP/2 deprecation warnings, but these are historical records left before the configuration was modified.

After the configuration modification and reload, no new warnings of the same kind have been generated.

16. Notices in PHP Logs Are Unrelated to This Nginx Upgrade

When finally checking the PHP-FPM logs, some PHP 8.5 compatibility notices can still be seen.

Nimble Builder has a notice regarding translation loading being triggered too early:

Plaintext
Translation loading for the nimble-builder domain was triggered too early

Organize Series uses methods deprecated in PHP 8.5:

Plaintext
SplObjectStorage::attach()
SplObjectStorage::contains()

Yoast SEO showed a deprecated notice for using null as an array offset.

These issues stem from the PHP 8.5.9 upgrade the day before and are not caused by Nginx 1.30.4 or OpenSSL 3.5.7.

Currently, they are just Notices or Deprecated, not Fatal Errors. Since the website frontend, backend, REST API, and PHP-FPM are all running normally, there is no need to expand the scope of modifications during this Nginx upgrade.

Related plugin compatibility issues can be handled as separate follow-up tasks.

17. Backup Locations After This Upgrade

This upgrade retained two main sets of backups.

The Nginx 1.24.0 binary and the complete pre-upgrade configuration are located at:

Plaintext
/root/nginx-upgrade-backup-20260805-172623

The four virtual host files before the HTTP/2 configuration modification are located at:

Plaintext
/root/nginx-http2-config-backup-20260805-173655

In addition, the compilation directory and the bypass binary are temporarily retained as well:

Plaintext
/root/nginx-build-1.30.4-openssl-3.5.7
/root/nginx-1.30.4-openssl-3.5.7

I will not delete these files for now until the new version has been running stably for a while.

18. Key Lessons Worth Documenting from This Upgrade Process

1. Do Not Casually Enable set -e in an Interactive SSH Shell

set -e is useful for automation scripts, but if enabled directly in the current SSH shell, a failure in an ordinary command could also cause the entire remote shell to exit.

A safer approach is to run high-risk operations in an isolated subprocess:

Bash
bash <<'BASH'
set -euo pipefail

# 具体操作
BASH

This way, even if the subprocess fails, the current SSH session will typically remain active.

2. Test the New Binary in a Bypass First

Before overwriting the production binary, you should at least confirm:

  1. The version and OpenSSL version are correct.
  2. Compilation parameters are consistent with the original version.
  3. The new binary can load the current production configuration.
  4. Both configuration syntax and full tests succeed.
  5. The new binary’s checksum has been calculated and recorded.

3. Do Not Modify Too Many Variables at Once

This time, I retained PCRE 8.45 and did not migrate to PCRE2 simultaneously.

Although PCRE 8.45 will also need separate evaluation later, modifying Nginx, OpenSSL, PCRE, and the virtual host configurations all in a single operation would significantly increase the difficulty of troubleshooting.

4. Warnings and Errors Need to Be Handled Differently

The listen ... http2 deprecation notice does not prevent Nginx from starting, so you can complete the binary upgrade first and then modify the configuration separately.

If you modify multiple configuration files simultaneously upon seeing warnings, it will make the upgrade process harder to verify.

5. A curl Return of 000 Does Not Necessarily Mean a Service Failure

000 is not an HTTP status code returned by the server.

It usually means:

Plaintext
DNS resolution failed
Connection failed
TLS handshake failed
Request timed out
Client aborted actively

This time, www and admin returned 000 simply because the full response time was slightly longer than the set 15-second timeout.

After re-displaying detailed errors and appropriately increasing the timeout, both sites returned 200 normally.

6. Automation Scripts Must Account for Older Python Versions on Servers

This server still uses Python 3.6.8.

In server operations scripts, you should avoid directly using:

Python
list[str]
dict[str, str]

As well as other syntax only supported by newer Python versions.

For simple operations scripts, prioritizing compatibility with the server’s existing runtime environment is more important than pursuing new type annotations.

19. Final Results

This upgrade ultimately achieved:

Plaintext
Nginx 1.24.0 → Nginx 1.30.4
OpenSSL 1.1.1t → OpenSSL 3.5.7
Old listen ... http2 syntax → Independent http2 on directive

Final confirmation:

  • Nginx configuration test succeeded.
  • Master and worker processes are normal.
  • IPv4 and IPv6 ports 80 and 443 are listening normally.
  • Chinese site, English site, and admin subdomain all return 200.
  • All three public network sites are accessible via HTTP/2.
  • Direct origin connection also supports HTTP/2.
  • WordPress REST API, login page, and admin dashboard are normal.
  • PHP-FPM is running normally.
  • No new severe Nginx errors have occurred.

Although the process encountered a download failure, two missing Perl modules, Python 3.6 syntax incompatibility, and a misjudgment of curl 000, because I consistently adhered to bypass compilation, configuration pre-checking, complete backups, and rollback-capable switching, it ultimately did not cause any unrecoverable issues.

With this, the Nginx and OpenSSL upgrade on this Alibaba Cloud Linux 3 server is officially complete.

系列导航

关于作者 · 持续学习与技术实践

我是一名拥有 15 年以上开发经验的技术从业者,自 2013 年起持续运营个人技术博客,记录工作与个人项目中遇到的真实问题,以及解决问题过程中的探索、实践和思考。

除了技术写作,我也在持续开发和维护自己的独立项目,探索新技术和新工具在实际场景中的应用。我始终相信,没有不值得去解决的问题,也没有不值得去学习的技术。

如果这篇文章对你有所帮助,欢迎浏览博客中的其他文章,也欢迎交流技术经验与想法。

关于我  ·  GitHub  ·  邮件联系