diff --git a/.travis.yml b/.travis.yml
index 6033ee185fcbd7ecfbb6214ae859751d947a1633..4eb8d3e5ce4f6796a905015c150387d5a979cb29 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -42,8 +42,6 @@ env:
     - LUAJIT_LIB=$LUAJIT_PREFIX/lib
     - LUAJIT_INC=$LUAJIT_PREFIX/include/luajit-2.1
     - LUA_INCLUDE_DIR=$LUAJIT_INC
-    - PCRE_VER=8.45
-    - PCRE2_VER=10.42
     - PCRE_PREFIX=/opt/pcre
     - PCRE2_PREFIX=/opt/pcre2
     - PCRE_LIB=$PCRE_PREFIX/lib
@@ -61,12 +59,14 @@ env:
     - TEST_NGINX_SLEEP=0.006
     - MALLOC_PERTURB_=9
   jobs:
-    #- NGINX_VERSION=1.21.4 OPENSSL_VER=1.1.0l OPENSSL_PATCH_VER=1.1.0d
-    #- NGINX_VERSION=1.25.1 OPENSSL_VER=1.1.0l OPENSSL_PATCH_VER=1.1.0d
-    - NGINX_VERSION=1.21.4 OPENSSL_VER=1.1.1w OPENSSL_PATCH_VER=1.1.1f
-    - NGINX_VERSION=1.27.0 OPENSSL_VER=1.1.1w OPENSSL_PATCH_VER=1.1.1f USE_PCRE2=Y TEST_NGINX_TIMEOUT=5
-    - NGINX_VERSION=1.27.0 BORINGSSL=1 TEST_NGINX_USE_HTTP3=1 USE_PCRE2=Y TEST_NGINX_QUIC_IDLE_TIMEOUT=3
+    #- NGINX_VERSION=1.21.4 OPENSSL_VER=1.1.1w OPENSSL_PATCH_VER=1.1.1f
     #- NGINX_VERSION=1.25.1 OPENSSL_VER=1.1.1w TEST_NGINX_USE_HTTP2=1
+    - NGINX_VERSION=1.27.1 OPENSSL_VER=1.1.1w OPENSSL_PATCH_VER=1.1.1f TEST_NGINX_TIMEOUT=5 PCRE_VER=8.45
+    - NGINX_VERSION=1.27.1 OPENSSL_VER=3.0.15 OPENSSL_PATCH_VER=3.0.15 TEST_NGINX_TIMEOUT=5 PCRE2_VER=10.42
+    - NGINX_VERSION=1.27.1 OPENSSL_VER=1.1.1w OPENSSL_PATCH_VER=1.1.1f TEST_NGINX_TIMEOUT=5 PCRE_VER=8.45 TEST_NGINX_USE_HTTP2=1
+    - NGINX_VERSION=1.27.1 OPENSSL_VER=3.0.15 OPENSSL_PATCH_VER=3.0.15 TEST_NGINX_TIMEOUT=5 PCRE2_VER=10.42 TEST_NGINX_USE_HTTP2=1
+    - NGINX_VERSION=1.27.1 OPENSSL_VER=3.0.15 OPENSSL_PATCH_VER=3.0.15 TEST_NGINX_USE_HTTP3=1 TEST_NGINX_QUIC_IDLE_TIMEOUT=3 PCRE2_VER=10.42
+    - NGINX_VERSION=1.27.1 BORINGSSL=1 TEST_NGINX_USE_HTTP3=1 TEST_NGINX_QUIC_IDLE_TIMEOUT=3 PCRE2_VER=10.42
 
 services:
   - memcached
@@ -80,10 +80,12 @@ before_install:
 
 install:
   - if [ ! -f download-cache/drizzle7-$DRIZZLE_VER.tar.gz ]; then wget -P download-cache https://github.com/openresty/openresty-deps-prebuild/releases/download/v20230902/drizzle7-$DRIZZLE_VER.tar.gz; fi
-  - if [ "$USE_PCRE2" != "Y" ] && [ ! -f download-cache/pcre-$PCRE_VER.tar.gz ]; then wget -P download-cache https://downloads.sourceforge.net/project/pcre/pcre/${PCRE_VER}/pcre-${PCRE_VER}.tar.gz; fi
-  - if [ "$USE_PCRE2" = "Y" ] && [ ! -f download-cache/pcre2-$PCRE2_VER.tar.gz ]; then wget -P download-cache https://github.com/PCRE2Project/pcre2/releases/download/pcre2-${PCRE2_VER}/pcre2-${PCRE2_VER}.tar.gz; fi
-  - if [ -n "$OPENSSL_VER" ] && [ ! -f download-cache/openssl-$OPENSSL_VER.tar.gz ]; then wget -P download-cache https://www.openssl.org/source/openssl-$OPENSSL_VER.tar.gz || wget -P download-cache https://www.openssl.org/source/old/${OPENSSL_VER//[a-z]/}/openssl-$OPENSSL_VER.tar.gz; fi
-  - if [ -n "$OPENSSL_VER" ] && [ ! -f download-cache/openssl-$OPENSSL_VER.tar.gz ]; then wget -P download-cache https://www.openssl.org/source/openssl-$OPENSSL_VER.tar.gz || wget -P download-cache https://www.openssl.org/source/old/${OPENSSL_VER//[a-z]/}/openssl-$OPENSSL_VER.tar.gz; fi
+  #- if [ -n "$PCRE_VER" ] && [ ! -f download-cache/pcre-$PCRE_VER.tar.gz ]; then wget -P download-cache https://downloads.sourceforge.net/project/pcre/pcre/${PCRE_VER}/pcre-${PCRE_VER}.tar.gz; fi
+  #- if [ -n "$PCRE2_VER" ] && [ ! -f download-cache/pcre2-$PCRE2_VER.tar.gz ]; then wget -P download-cache https://github.com/PCRE2Project/pcre2/releases/download/pcre2-${PCRE2_VER}/pcre2-${PCRE2_VER}.tar.gz; fi
+  #- if [ -n "$OPENSSL_VER" ] && [ ! -f download-cache/openssl-$OPENSSL_VER.tar.gz ]; then wget -P download-cache https://github.com/openssl/openssl/releases/download/openssl-$OPENSSL_VER/openssl-$OPENSSL_VER.tar.gz || wget -P download-cache https://www.openssl.org/source/openssl-$OPENSSL_VER.tar.gz || wget -P download-cache https://www.openssl.org/source/old/${OPENSSL_VER//[a-z]/}/openssl-$OPENSSL_VER.tar.gz; fi
+  - if [ -n "$OPENSSL_VER" ]; then wget https://github.com/openresty/openresty-deps-prebuild/releases/download/v1.0.0/openssl-${OPENSSL_VER}-x64-focal.tar.gz; fi
+  - if [ -n "$PCRE_VER" ]; then wget https://github.com/openresty/openresty-deps-prebuild/releases/download/v1.0.0/pcre-${PCRE_VER}-x64-focal.tar.gz; fi
+  - if [ -n "$PCRE2_VER" ]; then wget https://github.com/openresty/openresty-deps-prebuild/releases/download/v1.0.0/pcre2-${PCRE2_VER}-x64-focal.tar.gz; fi
   - wget https://github.com/openresty/openresty-deps-prebuild/releases/download/v20230902/boringssl-20230902-x64-focal.tar.gz
   - wget https://github.com/openresty/openresty-deps-prebuild/releases/download/v20230902/curl-h3-x64-focal.tar.gz
   - git clone https://github.com/openresty/test-nginx.git
@@ -135,10 +137,13 @@ script:
   - sudo make install-libdrizzle-1.0 > build.log 2>&1 || (cat build.log && exit 1)
   - cd ../mockeagain/ && make CC=$CC -j$JOBS && cd ..
   - cd lua-cjson/ && make -j$JOBS && sudo make install && cd ..
-  - if [ "$USE_PCRE2" != "Y" ]; then tar zxf download-cache/pcre-$PCRE_VER.tar.gz; cd pcre-$PCRE_VER/; ./configure --prefix=$PCRE_PREFIX --enable-jit --enable-utf --enable-unicode-properties > build.log 2>&1 || (cat build.log && exit 1); make -j$JOBS > build.log 2>&1 || (cat build.log && exit 1); sudo PATH=$PATH make install > build.log 2>&1 || (cat build.log && exit 1); cd ..; fi
-  - if [ "$USE_PCRE2" = "Y" ]; then tar zxf download-cache/pcre2-$PCRE2_VER.tar.gz; cd pcre2-$PCRE2_VER/; ./configure --prefix=$PCRE2_PREFIX --enable-jit --enable-utf > build.log 2>&1 || (cat build.log && exit 1); make -j$JOBS > build.log 2>&1 || (cat build.log && exit 1); sudo PATH=$PATH make install > build.log 2>&1 || (cat build.log && exit 1); cd ..; fi
-  - if [ -n "$OPENSSL_VER" ]; then tar zxf download-cache/openssl-$OPENSSL_VER.tar.gz; cd openssl-$OPENSSL_VER/; patch -p1 < ../../openresty/patches/openssl-$OPENSSL_PATCH_VER-sess_set_get_cb_yield.patch; ./config shared enable-ssl3 enable-ssl3-method -g --prefix=$OPENSSL_PREFIX -DPURIFY > build.log 2>&1 || (cat build.log && exit 1); make -j$JOBS > build.log 2>&1 || (cat build.log && exit 1); sudo make PATH=$PATH install_sw > build.log 2>&1 || (cat build.log && exit 1); cd ..; fi
+  #- if [ -n "PCRE_VER" ]; then tar zxf download-cache/pcre-$PCRE_VER.tar.gz; cd pcre-$PCRE_VER/; ./configure --prefix=$PCRE_PREFIX --enable-jit --enable-utf --enable-unicode-properties > build.log 2>&1 || (cat build.log && exit 1); make -j$JOBS > build.log 2>&1 || (cat build.log && exit 1); sudo PATH=$PATH make install > build.log 2>&1 || (cat build.log && exit 1); cd ..; fi
+  #- if [ -n "$PCRE2_VER" ]; then tar zxf download-cache/pcre2-$PCRE2_VER.tar.gz; cd pcre2-$PCRE2_VER/; ./configure --prefix=$PCRE2_PREFIX --enable-jit --enable-utf > build.log 2>&1 || (cat build.log && exit 1); make -j$JOBS > build.log 2>&1 || (cat build.log && exit 1); sudo PATH=$PATH make install > build.log 2>&1 || (cat build.log && exit 1); cd ..; fi
+  #- if [ -n "$OPENSSL_VER" ]; then tar zxf download-cache/openssl-$OPENSSL_VER.tar.gz; cd openssl-$OPENSSL_VER/; patch -p1 < ../../openresty/patches/openssl-$OPENSSL_PATCH_VER-sess_set_get_cb_yield.patch; ./config shared enable-ssl3 enable-ssl3-method -g --prefix=$OPENSSL_PREFIX --libdir=lib -DPURIFY > build.log 2>&1 || (cat build.log && exit 1); make -j$JOBS > build.log 2>&1 || (cat build.log && exit 1); sudo make PATH=$PATH install_sw > build.log 2>&1 || (cat build.log && exit 1); cd ..; fi
   - if [ -n "$BORINGSSL" ]; then sudo mkdir -p /opt/ssl && sudo tar -C /opt/ssl -xf boringssl-20230902-x64-focal.tar.gz --strip-components=1; fi
+  - if [ -n "$OPENSSL_VER" ]; then sudo mkdir -p /opt/ssl && sudo tar -C /opt/ssl -xf openssl-$OPENSSL_VER-x64-focal.tar.gz --strip-components=2; fi
+  - if [ -n "$PCRE_VER" ]; then sudo mkdir -p $PCRE_PREFIX && sudo tar -C $PCRE_PREFIX -xf pcre-$PCRE_VER-x64-focal.tar.gz --strip-components=2; fi
+  - if [ -n "$PCRE2_VER" ]; then sudo mkdir -p $PCRE2_PREFIX && sudo tar -C $PCRE2_PREFIX -xf pcre2-$PCRE2_VER-x64-focal.tar.gz --strip-components=2; fi
   - export NGX_BUILD_CC=$CC
   - sh util/build-without-ssl.sh $NGINX_VERSION > build.log 2>&1 || (cat build.log && exit 1)
   - sh util/build-with-dd.sh $NGINX_VERSION > build.log 2>&1 || (cat build.log && exit 1)
diff --git a/README.markdown b/README.markdown
index 29a81c1ca4219ba4b74a7cfef9f3fc99dbe54487..35c0b83e58c16083c77e2528ddb0cf738295b061 100644
--- a/README.markdown
+++ b/README.markdown
@@ -269,7 +269,7 @@ memory use. Request contexts are segregated using lightweight Lua coroutines.
 Loaded Lua modules persist in the Nginx worker process level resulting in a
 small memory footprint in Lua even when under heavy loads.
 
-This module is plugged into Nginx's "http" subsystem so it can only speaks
+This module is plugged into Nginx's "http" subsystem so it can only speak
 downstream communication protocols in the HTTP family (HTTP 0.9/1.0/1.1/2.0,
 WebSockets, etc...).  If you want to do generic TCP communications with the
 downstream clients, then you should use the
@@ -283,7 +283,7 @@ Typical Uses
 
 Just to name a few:
 
-* Mashup'ing and processing outputs of various Nginx upstream outputs (proxy, drizzle, postgres, redis, memcached, and etc) in Lua,
+* Mashup'ing and processing outputs of various Nginx upstream outputs (proxy, drizzle, postgres, redis, memcached, etc.) in Lua,
 * doing arbitrarily complex access control and security checks in Lua before requests actually reach the upstream backends,
 * manipulating response headers in an arbitrary way (by Lua)
 * fetching backend information from external storage backends (like redis, memcached, mysql, postgresql) and use that information to choose which upstream backend to access on-the-fly,
@@ -337,7 +337,7 @@ It is discouraged to build this module with Nginx yourself since it is tricky
 to set up exactly right.
 
 Note that Nginx, LuaJIT, and OpenSSL official releases have various limitations
-and long standing bugs that can cause some of this module's features to be
+and long-standing bugs that can cause some of this module's features to be
 disabled, not work properly, or run slower. Official OpenResty releases are
 recommended because they bundle [OpenResty's optimized LuaJIT 2.1 fork](https://github.com/openresty/luajit2) and
 [Nginx/OpenSSL
@@ -421,7 +421,7 @@ While building this module either via OpenResty or with the Nginx core, you can
 * `NGX_LUA_USE_ASSERT`
 	When defined, will enable assertions in the ngx_lua C code base. Recommended for debugging or testing builds. It can introduce some (small) runtime overhead when enabled. This macro was first introduced in the `v0.9.10` release.
 * `NGX_LUA_ABORT_AT_PANIC`
-	When the LuaJIT VM panics, ngx_lua will instruct the current nginx worker process to quit gracefully by default. By specifying this C macro, ngx_lua will abort the current nginx worker process (which usually result in a core dump file) immediately. This option is useful for debugging VM panics. This option was first introduced in the `v0.9.8` release.
+	When the LuaJIT VM panics, ngx_lua will instruct the current nginx worker process to quit gracefully by default. By specifying this C macro, ngx_lua will abort the current nginx worker process (which usually results in a core dump file) immediately. This option is useful for debugging VM panics. This option was first introduced in the `v0.9.8` release.
 
 To enable one or more of these macros, just pass extra C compiler options to the `./configure` script of either Nginx or OpenResty. For instance,
 
@@ -1184,7 +1184,7 @@ Directives
 The basic building blocks of scripting Nginx with Lua are directives. Directives are used to specify when the user Lua code is run and
 how the result will be used. Below is a diagram showing the order in which directives are executed.
 
-![Lua Nginx Modules Directives](https://cloud.githubusercontent.com/assets/2137369/15272097/77d1c09e-1a37-11e6-97ef-d9767035fc3e.png)
+![Lua Nginx Modules Directives](./doc/images/lua_nginx_modules_directives.drawio.png)
 
 [Back to TOC](#table-of-contents)
 
@@ -3695,6 +3695,7 @@ Nginx API for Lua
 * [ngx.decode_args](#ngxdecode_args)
 * [ngx.encode_base64](#ngxencode_base64)
 * [ngx.decode_base64](#ngxdecode_base64)
+* [ngx.decode_base64mime](#ngxdecode_base64mime)
 * [ngx.crc32_short](#ngxcrc32_short)
 * [ngx.crc32_long](#ngxcrc32_long)
 * [ngx.hmac_sha1](#ngxhmac_sha1)
@@ -4149,7 +4150,7 @@ Then `GET /main` will give the output
 
 Here, modification of the `ngx.ctx.blah` entry in the subrequest does not affect the one in the parent request. This is because they have two separate versions of `ngx.ctx.blah`.
 
-Internal redirects (triggered by nginx configuration directives like `error_page`, `try_files`, `index` and etc) will destroy the original request `ngx.ctx` data (if any) and the new request will have an empty `ngx.ctx` table. For instance,
+Internal redirects (triggered by nginx configuration directives like `error_page`, `try_files`, `index`, etc.) will destroy the original request `ngx.ctx` data (if any) and the new request will have an empty `ngx.ctx` table. For instance,
 
 ```nginx
 
@@ -6254,7 +6255,7 @@ ngx.encode_base64
 
 **context:** *set_by_lua&#42;, rewrite_by_lua&#42;, access_by_lua&#42;, content_by_lua&#42;, header_filter_by_lua&#42;, body_filter_by_lua&#42;, log_by_lua&#42;, ngx.timer.&#42;, balancer_by_lua&#42;, ssl_certificate_by_lua&#42;, ssl_session_fetch_by_lua&#42;, ssl_session_store_by_lua&#42;, ssl_client_hello_by_lua&#42;*
 
-Encodes `str` to a base64 digest.
+Encodes `str` to a base64 digest. For base64url encoding use [`base64.encode_base64url`](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/base64.md#encode_base64url).
 
 Since the `0.9.16` release, an optional boolean-typed `no_padding` argument can be specified to control whether the base64 padding should be appended to the resulting digest (default to `false`, i.e., with padding enabled).
 
@@ -6267,7 +6268,25 @@ ngx.decode_base64
 
 **context:** *set_by_lua&#42;, rewrite_by_lua&#42;, access_by_lua&#42;, content_by_lua&#42;, header_filter_by_lua&#42;, body_filter_by_lua&#42;, log_by_lua&#42;, ngx.timer.&#42;, balancer_by_lua&#42;, ssl_certificate_by_lua&#42;, ssl_session_fetch_by_lua&#42;, ssl_session_store_by_lua&#42;, ssl_client_hello_by_lua&#42;*
 
-Decodes the `str` argument as a base64 digest to the raw form. Returns `nil` if `str` is not well formed.
+Decodes the `str` argument as a base64 digest to the raw form. For base64url decoding use [`base64.decode_base64url`](https://github.com/openresty/lua-resty-core/blob/master/lib/ngx/base64.md#decode_base64url).
+
+The `str` should be standard 'base64' encoding for RFC 3548 or RFC 4648, and will returns `nil` if is not well formed or any characters not in the base encoding alphabet. Padding may be omitted from the input.
+
+[Back to TOC](#nginx-api-for-lua)
+
+ngx.decode_base64mime
+---------------------
+**syntax:** *newstr = ngx.decode_base64mime(str)*
+
+**context:** *set_by_lua&#42;, rewrite_by_lua&#42;, access_by_lua&#42;, content_by_lua&#42;, header_filter_by_lua&#42;, body_filter_by_lua&#42;, log_by_lua&#42;, ngx.timer.&#42;, balancer_by_lua&#42;, ssl_certificate_by_lua&#42;, ssl_session_fetch_by_lua&#42;, ssl_session_store_by_lua&#42;*
+
+**requires:** `resty.core.base64` or `resty.core`
+
+Decodes the `str` argument as a base64 digest to the raw form.
+The `str` follows base64 transfer encoding for MIME (RFC 2045), and will discard characters outside the base encoding alphabet.
+Returns `nil` if `str` is not well formed.
+
+ '''Note:''' This method requires the <code>resty.core.base64</code> or <code>resty.core</code> modules from the [lua-resty-core](https://github.com/openresty/lua-resty-core) library.
 
 [Back to TOC](#nginx-api-for-lua)
 
@@ -7971,14 +7990,14 @@ An optional Lua table can be specified as the last argument to this method to sp
 * `backlog`
 	if specified, this module will limit the total number of opened connections
 	for this pool. No more connections than `pool_size` can be opened
-	for this pool at any time. If the connection pool is full, subsequent
-	connect operations will be queued into a queue equal to this option's
-	value (the "backlog" queue).
+	for this pool at any time. If `pool_size` number of connections are in use,
+	subsequent connect operations will be queued into a queue equal to this
+	option's value (the "backlog" queue).
 	If the number of queued connect operations is equal to `backlog`,
 	subsequent connect operations will fail and return `nil` plus the
 	error string `"too many waiting connect operations"`.
-	The queued connect operations will be resumed once the number of connections
-	in the pool is less than `pool_size`.
+	The queued connect operations will be resumed once the number of active
+	connections becomes less than `pool_size`.
 	The queued connect operation will abort once they have been queued for more
 	than `connect_timeout`, controlled by
 	[settimeouts](#tcpsocksettimeouts), and will return `nil` plus
@@ -8996,7 +9015,7 @@ this context.
 
 You must notice that each timer will be based on a fake request (this fake request is also based on a fake connection). Because Nginx's memory release is based on the connection closure, if you run a lot of APIs that apply for memory resources in a timer, such as [tcpsock:connect](#tcpsockconnect), will cause the accumulation of memory resources. So it is recommended to create a new timer after running several times to release memory resources.
 
-You can pass most of the standard Lua values (nils, booleans, numbers, strings, tables, closures, file handles, and etc) into the timer callback, either explicitly as user arguments or implicitly as upvalues for the callback closure. There are several exceptions, however: you *cannot* pass any thread objects returned by [coroutine.create](#coroutinecreate) and [ngx.thread.spawn](#ngxthreadspawn) or any cosocket objects returned by [ngx.socket.tcp](#ngxsockettcp), [ngx.socket.udp](#ngxsocketudp), and [ngx.req.socket](#ngxreqsocket) because these objects' lifetime is bound to the request context creating them while the timer callback is detached from the creating request's context (by design) and runs in its own (fake) request context. If you try to share the thread or cosocket objects across the boundary of the creating request, then you will get the "no co ctx found" error (for threads) or "bad request" (for cosockets). It is fine, however, to create all these objects inside your timer callback.
+You can pass most of the standard Lua values (nils, booleans, numbers, strings, tables, closures, file handles, etc.) into the timer callback, either explicitly as user arguments or implicitly as upvalues for the callback closure. There are several exceptions, however: you *cannot* pass any thread objects returned by [coroutine.create](#coroutinecreate) and [ngx.thread.spawn](#ngxthreadspawn) or any cosocket objects returned by [ngx.socket.tcp](#ngxsockettcp), [ngx.socket.udp](#ngxsocketudp), and [ngx.req.socket](#ngxreqsocket) because these objects' lifetime is bound to the request context creating them while the timer callback is detached from the creating request's context (by design) and runs in its own (fake) request context. If you try to share the thread or cosocket objects across the boundary of the creating request, then you will get the "no co ctx found" error (for threads) or "bad request" (for cosockets). It is fine, however, to create all these objects inside your timer callback.
 
 Please note that the timer Lua handler has its own copy of the `ngx.ctx` magic
 table. It won't share the same `ngx.ctx` with the Lua handler creating the timer.
@@ -9489,7 +9508,7 @@ The type of `args` must be one of type below:
 * nil
 * table (the table may be recursive, and contains members of types above.)
 
-The `ok` is in boolean type, which indicate the C land error (failed to get thread from thread pool, pcall the module function failed, .etc). If `ok` is `false`, the `res1` is the error string.
+The `ok` is in boolean type, which indicate the C land error (failed to get thread from thread pool, pcall the module function failed, etc.). If `ok` is `false`, the `res1` is the error string.
 
 The return values (res1, ...) are returned by invocation of the module function. Normally, the `res1` should be in boolean type, so that the caller could inspect the error.
 
diff --git a/doc/HttpLuaModule.wiki b/doc/HttpLuaModule.wiki
index a0e6d28face6b460f36344eab9b4477ac47d81b1..733f11dc66705ad2bdb8c48d414da29104e8d432 100644
--- a/doc/HttpLuaModule.wiki
+++ b/doc/HttpLuaModule.wiki
@@ -5256,7 +5256,21 @@ Since the <code>0.9.16</code> release, an optional boolean-typed <code>no_paddin
 
 '''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*, ssl_client_hello_by_lua*''
 
-Decodes the <code>str</code> argument as a base64 digest to the raw form. Returns <code>nil</code> if <code>str</code> is not well formed.
+Decodes the <code>str</code> argument as a base64 digest to the raw form.
+The <code>str</code> should be standard 'base64' encoding for RFC 3548 or RFC 4648, and will returns <code>nil</code> if is not well formed or any characters not in the base encoding alphabet.
+
+== ngx.decode_base64mime ==
+'''syntax:''' ''newstr = ngx.decode_base64mime(str)''
+
+'''context:''' ''set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*''
+
+'''requires:''' <code>resty.core.base64</code> or <code>resty.core</code>
+
+Decodes the <code>str</code> argument as a base64 digest to the raw form.
+The <code>str</code> follows base64 transfer encoding for MIME (RFC 2045), and will discard characters outside the base encoding alphabet.
+Returns <code>nil</code> if <code>str</code> is not well formed.
+
+ '''Note:''' This method requires the <code>resty.core.base64</code> or <code>resty.core</code> modules from the [https://github.com/openresty/lua-resty-core lua-resty-core] library.
 
 == ngx.crc32_short ==
 
diff --git a/doc/images/lua_nginx_modules_directives.drawio.png b/doc/images/lua_nginx_modules_directives.drawio.png
new file mode 100644
index 0000000000000000000000000000000000000000..41112147f2fb1320c9ce132cdb334a1ef94109f7
Binary files /dev/null and b/doc/images/lua_nginx_modules_directives.drawio.png differ
diff --git a/src/api/ngx_http_lua_api.h b/src/api/ngx_http_lua_api.h
index 4b374f56eec23484c5c6f9d256a2309d066feff8..021044eb87f9b1c146e5ace14732aea76cd88dba 100644
--- a/src/api/ngx_http_lua_api.h
+++ b/src/api/ngx_http_lua_api.h
@@ -19,7 +19,7 @@
 /* Public API for other Nginx modules */
 
 
-#define ngx_http_lua_version  10027
+#define ngx_http_lua_version  10028
 
 
 typedef struct ngx_http_lua_co_ctx_s  ngx_http_lua_co_ctx_t;
diff --git a/src/ngx_http_lua_misc.c b/src/ngx_http_lua_misc.c
index 4e93f68f1e7fef259cd0da806c70c87969ad7397..8b7416716116ee08731e63856d7fd82a10717f09 100644
--- a/src/ngx_http_lua_misc.c
+++ b/src/ngx_http_lua_misc.c
@@ -64,8 +64,11 @@ ngx_http_lua_ffi_get_resp_status(ngx_http_request_t *r)
 
 
 int
-ngx_http_lua_ffi_set_resp_status(ngx_http_request_t *r, int status)
+ngx_http_lua_ffi_set_resp_status_and_reason(ngx_http_request_t *r, int status,
+    const char *reason, size_t reason_len)
 {
+    u_char *buf;
+
     if (r->connection->fd == (ngx_socket_t) -1) {
         return NGX_HTTP_LUA_FFI_BAD_CONTEXT;
     }
@@ -77,6 +80,14 @@ ngx_http_lua_ffi_set_resp_status(ngx_http_request_t *r, int status)
         return NGX_DECLINED;
     }
 
+    /* per RFC-7230 sec 3.1.2, the status line must be 3 digits, it also makes
+     * buffer size calculation easier */
+    if (status < 100 || status > 999) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "invalid HTTP status code %d", status);
+        return NGX_DECLINED;
+    }
+
     r->headers_out.status = status;
 
     if (r->err_status) {
@@ -91,6 +102,18 @@ ngx_http_lua_ffi_set_resp_status(ngx_http_request_t *r, int status)
 
         ngx_str_set(&r->headers_out.status_line, "101 Switching Protocols");
 
+    } else if (reason != NULL && reason_len > 0) {
+        reason_len += 4; /* "ddd <reason>" */
+        buf = ngx_palloc(r->pool, reason_len);
+        if (buf == NULL) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no memory");
+            return NGX_DECLINED;
+        }
+
+        ngx_snprintf(buf, reason_len, "%d %s", status, reason);
+        r->headers_out.status_line.len = reason_len;
+        r->headers_out.status_line.data = buf;
+
     } else {
         r->headers_out.status_line.len = 0;
     }
@@ -99,6 +122,13 @@ ngx_http_lua_ffi_set_resp_status(ngx_http_request_t *r, int status)
 }
 
 
+int
+ngx_http_lua_ffi_set_resp_status(ngx_http_request_t *r, int status)
+{
+    return ngx_http_lua_ffi_set_resp_status_and_reason(r, status, NULL, 0);
+}
+
+
 int
 ngx_http_lua_ffi_req_is_internal(ngx_http_request_t *r)
 {
diff --git a/src/ngx_http_lua_output.c b/src/ngx_http_lua_output.c
index b2a98d133ab4a2d81173bff674fec3c490dc6c4f..8681947263337f3a1e41f23d7ad69e122802cace 100644
--- a/src/ngx_http_lua_output.c
+++ b/src/ngx_http_lua_output.c
@@ -722,16 +722,14 @@ ngx_http_lua_ngx_send_headers(lua_State *L)
                                | NGX_HTTP_LUA_CONTEXT_ACCESS
                                | NGX_HTTP_LUA_CONTEXT_CONTENT);
 
-    if (!r->header_sent && !ctx->header_sent) {
-        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
-                       "lua send headers");
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "lua send headers");
 
-        rc = ngx_http_lua_send_header_if_needed(r, ctx);
-        if (rc == NGX_ERROR || rc > NGX_OK) {
-            lua_pushnil(L);
-            lua_pushliteral(L, "nginx output filter error");
-            return 2;
-        }
+    rc = ngx_http_lua_send_header_if_needed(r, ctx);
+    if (rc == NGX_ERROR || rc > NGX_OK) {
+        lua_pushnil(L);
+        lua_pushliteral(L, "nginx output filter error");
+        return 2;
     }
 
     lua_pushinteger(L, 1);
diff --git a/src/ngx_http_lua_socket_tcp.c b/src/ngx_http_lua_socket_tcp.c
index 5010dfa6ed6a2cee25b7ee51c7aa626b2c44b4be..998881d1cadc37188929acfefaa1f51dd6346046 100644
--- a/src/ngx_http_lua_socket_tcp.c
+++ b/src/ngx_http_lua_socket_tcp.c
@@ -5725,7 +5725,7 @@ ngx_http_lua_socket_keepalive_close_handler(ngx_event_t *ev)
     ngx_http_lua_socket_pool_t          *spool;
 
     int                n;
-    char               buf[1];
+    unsigned char      buf[1];
     ngx_connection_t  *c;
 
     c = ev->data;
@@ -5746,19 +5746,10 @@ ngx_http_lua_socket_keepalive_close_handler(ngx_event_t *ev)
     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ev->log, 0,
                    "lua tcp socket keepalive close handler check stale events");
 
-    n = recv(c->fd, buf, 1, MSG_PEEK);
-#if (NGX_HTTP_SSL)
-    /* ignore ssl protocol data like change cipher spec */
-    if (n == 1 && c->ssl != NULL) {
-        n = c->recv(c, (unsigned char *) buf, 1);
-        if (n == NGX_AGAIN) {
-            n = -1;
-            ngx_socket_errno = NGX_EAGAIN;
-        }
-    }
-#endif
+    /* consume the possible ssl-layer data implicitly */
+    n = c->recv(c, buf, 1);
 
-    if (n == -1 && ngx_socket_errno == NGX_EAGAIN) {
+    if (n == NGX_AGAIN) {
         /* stale event */
 
         if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
diff --git a/src/ngx_http_lua_ssl_certby.c b/src/ngx_http_lua_ssl_certby.c
index 0901f06eabd97612a172194a7a90ad7107a06551..72a651bdbf352a049ddb6e543566e1d64fbe6e7b 100644
--- a/src/ngx_http_lua_ssl_certby.c
+++ b/src/ngx_http_lua_ssl_certby.c
@@ -1433,7 +1433,7 @@ ngx_http_lua_ffi_set_priv_key(ngx_http_request_t *r,
 
     pkey = cdata;
     if (pkey == NULL) {
-        *err = "invalid private key failed";
+        *err = "invalid private key";
         goto failed;
     }
 
diff --git a/src/ngx_http_lua_string.c b/src/ngx_http_lua_string.c
index 4c755f67f3a95504108dc296098c258ed43a3fad..3028483609f9550f738dc3a67436d079151b1f59 100644
--- a/src/ngx_http_lua_string.c
+++ b/src/ngx_http_lua_string.c
@@ -424,6 +424,26 @@ ngx_http_lua_ffi_decode_base64(const u_char *src, size_t slen, u_char *dst,
 }
 
 
+int
+ngx_http_lua_ffi_decode_base64mime(const u_char *src, size_t slen, u_char *dst,
+    size_t *dlen)
+{
+    ngx_int_t      rc;
+    ngx_str_t      in, out;
+
+    in.data = (u_char *) src;
+    in.len = slen;
+
+    out.data = dst;
+
+    rc = ngx_http_lua_decode_base64mime(&out, &in);
+
+    *dlen = out.len;
+
+    return rc == NGX_OK;
+}
+
+
 size_t
 ngx_http_lua_ffi_unescape_uri(const u_char *src, size_t len, u_char *dst)
 {
diff --git a/src/ngx_http_lua_subrequest.c b/src/ngx_http_lua_subrequest.c
index 2ccd271a902d58ea197696852786408810e3aeac..edb24e9c0d9d79b41f13a7a689c7d4c608f5c108 100644
--- a/src/ngx_http_lua_subrequest.c
+++ b/src/ngx_http_lua_subrequest.c
@@ -173,12 +173,6 @@ ngx_http_lua_ngx_location_capture_multi(lua_State *L)
         return luaL_error(L, "no request object found");
     }
 
-#if (NGX_HTTP_V2)
-    if (r->main->stream) {
-        return luaL_error(L, "http2 requests not supported yet");
-    }
-#endif
-
     ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module);
     if (ctx == NULL) {
         return luaL_error(L, "no ctx found");
diff --git a/src/ngx_http_lua_util.c b/src/ngx_http_lua_util.c
index f1e0cd08f51b296ac7878e97e8ef67b92e4f3c46..c73bddc6092acd872a33947113dcc2e49b543972 100644
--- a/src/ngx_http_lua_util.c
+++ b/src/ngx_http_lua_util.c
@@ -764,10 +764,6 @@ ngx_http_lua_send_http10_headers(ngx_http_request_t *r,
         }
 
         r->headers_out.content_length_n = size;
-
-        if (r->headers_out.content_length) {
-            r->headers_out.content_length->hash = 0;
-        }
     }
 
 send:
@@ -4407,6 +4403,86 @@ ngx_http_lua_copy_escaped_header(ngx_http_request_t *r,
 }
 
 
+ngx_int_t
+ngx_http_lua_decode_base64mime(ngx_str_t *dst, ngx_str_t *src)
+{
+    size_t          i;
+    u_char         *d, *s, ch;
+    size_t          data_len = 0;
+    u_char          buf[4];
+    size_t          buf_len = 0;
+    static u_char   basis[] = {
+        77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+        77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+        77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 62, 77, 77, 77, 63,
+        52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 77, 77, 77, 77, 77, 77,
+        77,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
+        15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 77, 77, 77, 77, 77,
+        77, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+        41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 77, 77, 77, 77, 77,
+
+        77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+        77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+        77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+        77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+        77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+        77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+        77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77,
+        77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77, 77
+    };
+
+    for (i = 0; i < src->len; i++) {
+        ch = src->data[i];
+        if (ch == '=') {
+            break;
+        }
+
+        if (basis[ch] == 77) {
+            continue;
+        }
+
+        data_len++;
+    }
+
+    if (data_len % 4 == 1) {
+        return NGX_ERROR;
+    }
+
+    s = src->data;
+    d = dst->data;
+
+    for (i = 0; i < src->len; i++) {
+        if (s[i] == '=') {
+            break;
+        }
+
+        if (basis[s[i]] == 77) {
+            continue;
+        }
+
+        buf[buf_len++] = s[i];
+        if (buf_len == 4) {
+            *d++ = (u_char) (basis[buf[0]] << 2 | basis[buf[1]] >> 4);
+            *d++ = (u_char) (basis[buf[1]] << 4 | basis[buf[2]] >> 2);
+            *d++ = (u_char) (basis[buf[2]] << 6 | basis[buf[3]]);
+            buf_len = 0;
+        }
+    }
+
+    if (buf_len > 1) {
+        *d++ = (u_char) (basis[buf[0]] << 2 | basis[buf[1]] >> 4);
+    }
+
+    if (buf_len > 2) {
+        *d++ = (u_char) (basis[buf[1]] << 4 | basis[buf[2]] >> 2);
+    }
+
+    dst->len = d - dst->data;
+
+    return NGX_OK;
+}
+
+
 ngx_addr_t *
 ngx_http_lua_parse_addr(lua_State *L, u_char *text, size_t len)
 {
diff --git a/src/ngx_http_lua_util.h b/src/ngx_http_lua_util.h
index faea7a072c2830053d107537924db92eeb0a63d1..85c6e614d6c51b487e57906f457f064aa1c9dbbf 100644
--- a/src/ngx_http_lua_util.h
+++ b/src/ngx_http_lua_util.h
@@ -261,6 +261,7 @@ void ngx_http_lua_cleanup_free(ngx_http_request_t *r,
 #if (NGX_HTTP_LUA_HAVE_SA_RESTART)
 void ngx_http_lua_set_sa_restart(ngx_log_t *log);
 #endif
+ngx_int_t ngx_http_lua_decode_base64mime(ngx_str_t *dst, ngx_str_t *src);
 
 ngx_addr_t *ngx_http_lua_parse_addr(lua_State *L, u_char *text, size_t len);
 
diff --git a/t/005-exit.t b/t/005-exit.t
index 0783c69295ab7465dc0313bb95584a7ff822ad39..bbaeda517eb252c20825071afb6594c654bb96e2 100644
--- a/t/005-exit.t
+++ b/t/005-exit.t
@@ -124,6 +124,7 @@ GET /api?user=agentz
 
 === TEST 6: working with ngx_auth_request (simplest form, w/o ngx_memc)
 --- skip_eval: 3:$ENV{TEST_NGINX_USE_HTTP3}
+--- no_http2
 --- http_config eval
 "
     lua_package_cpath '$::LuaCpath';
@@ -197,6 +198,7 @@ Logged in 56
 
 === TEST 7: working with ngx_auth_request (simplest form)
 --- skip_eval: 3:$ENV{TEST_NGINX_USE_HTTP3}
+--- no_http2
 --- http_config eval
 "
     lua_package_cpath '$::LuaCpath';
@@ -269,6 +271,7 @@ Logged in 56
 
 
 === TEST 8: working with ngx_auth_request
+--- no_http2
 --- skip_eval: 3:$ENV{TEST_NGINX_USE_HTTP3}
 --- http_config eval
 "
@@ -762,6 +765,7 @@ GET /t
 
 
 === TEST 27: accepts NGX_ERROR
+--- no_http2
 --- config
     location = /t {
         content_by_lua_block {
@@ -780,6 +784,7 @@ curl: (95) HTTP/3 stream 0 reset by server
 
 
 === TEST 28: accepts NGX_DECLINED
+--- no_http2
 --- config
     location = /t {
         content_by_lua_block {
diff --git a/t/014-bugs.t b/t/014-bugs.t
index d34f42e23d6b546efdf25f53395202f5c13f39a7..303187929f7048ce05f5de4f229fccace0eef561 100644
--- a/t/014-bugs.t
+++ b/t/014-bugs.t
@@ -201,7 +201,7 @@ https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2
    header field names MUST be converted to lowercase prior to their
    encoding in HTTP/2.  A request or response containing uppercase
    header field names MUST be treated as malformed
-
+--- no_http2
 --- config
     location /sub {
         content_by_lua '
diff --git a/t/015-status.t b/t/015-status.t
index aa816c08d6a02e9d499094346694463c560a5dfd..c768aebcea11c9573b985fa509d0e2cdc9f62e44 100644
--- a/t/015-status.t
+++ b/t/015-status.t
@@ -9,7 +9,7 @@ log_level('warn');
 #repeat_each(120);
 repeat_each(2);
 
-plan tests => repeat_each() * (blocks() * 2 + 9);
+plan tests => repeat_each() * (blocks() * 2 + 10);
 
 #no_diff();
 #no_long_string();
@@ -293,3 +293,55 @@ ngx.status: 654
 --- no_error_log
 [error]
 --- error_code: 654
+
+
+
+=== TEST 17: set status and reason
+--- config
+location = /upstream {
+    content_by_lua_block {
+        local resp = require "ngx.resp"
+        resp.set_status(500, "user defined reason")
+        ngx.say("set_status_and_reason")
+    }
+}
+
+location /t {
+   content_by_lua_block {
+       local sock = ngx.socket.tcp()
+       local port = ngx.var.server_port
+       local ok, err = sock:connect("127.0.0.1", port)
+       if not ok then
+           ngx.say("failed to connect: ", err)
+           return
+       end
+
+       local req = "GET /upstream HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n"
+
+       local bytes, err = sock:send(req)
+       if not bytes then
+           ngx.say("failed to send request: ", err)
+           return
+       end
+
+       local found = false
+       while true do
+           local line, err, part = sock:receive()
+           if line then
+               if ngx.re.find(line, "HTTP/1.1 500 user defined reason") then
+                   ngx.say("match")
+               end
+           else
+               break
+           end
+       end
+
+       sock:close()
+   }
+}
+--- request
+GET /t
+--- response_body
+match
+--- no_error_log
+[error]
diff --git a/t/016-resp-header.t b/t/016-resp-header.t
index 6cf699d88ed4426c86b9f7040220340902cd3771..b30090812c7aa2a03488a7b8b75247d2f39322c3 100644
--- a/t/016-resp-header.t
+++ b/t/016-resp-header.t
@@ -298,6 +298,7 @@ Fooy: cony1, cony2
 
 
 === TEST 15: set header after ngx.print
+--- no_http2
 --- config
     location /lua {
         default_type "text/plain";
diff --git a/t/020-subrequest.t b/t/020-subrequest.t
index 37914be061a264c5a2de8856ef3c13acbed605b1..e91f3f6253042a54fb4e09cc76dccd47e81769b9 100644
--- a/t/020-subrequest.t
+++ b/t/020-subrequest.t
@@ -1198,6 +1198,7 @@ body:
 
 
 === TEST 43: subrequests with an output body filter returning NGX_ERROR
+--- no_http2
 --- config
     location /sub {
         echo hello world;
diff --git a/t/023-rewrite/exit.t b/t/023-rewrite/exit.t
index 9add80441c2c729796c83847c8f28c659f512a17..7f01717eb6632a7d8b4ed51722b553aa5ce29f28 100644
--- a/t/023-rewrite/exit.t
+++ b/t/023-rewrite/exit.t
@@ -120,6 +120,7 @@ GET /api?user=agentz
 
 
 === TEST 6: working with ngx_auth_request (simplest form, w/o ngx_memc)
+--- no_http2
 --- http_config eval
 "
     lua_package_cpath '$::LuaCpath';
@@ -192,6 +193,7 @@ Logged in 56
 
 
 === TEST 7: working with ngx_auth_request (simplest form)
+--- no_http2
 --- http_config eval
 "
     lua_package_cpath '$::LuaCpath';
@@ -264,6 +266,7 @@ Logged in 56
 
 
 === TEST 8: working with ngx_auth_request
+--- no_http2
 --- http_config eval
 "
     lua_package_cpath '$::LuaCpath';
diff --git a/t/024-access/exit.t b/t/024-access/exit.t
index b77778213a97ea9d92e92d3a8ccb76461b49f75c..661ee2d8bcc4d693f57d24a31019f05a99ee9ab0 100644
--- a/t/024-access/exit.t
+++ b/t/024-access/exit.t
@@ -114,6 +114,7 @@ GET /api?user=agentz
 
 
 === TEST 6: working with ngx_auth_request (simplest form, w/o ngx_memc)
+--- no_http2
 --- http_config eval
 "
     lua_package_cpath '$::LuaCpath';
@@ -182,6 +183,7 @@ Logged in 56
 
 
 === TEST 7: working with ngx_auth_request (simplest form)
+--- no_http2
 --- http_config eval
 "
     lua_package_cpath '$::LuaCpath';
@@ -249,6 +251,7 @@ Logged in 56
 
 
 === TEST 8: working with ngx_auth_request
+--- no_http2
 --- http_config eval
 "
     lua_package_cpath '$::LuaCpath';
diff --git a/t/026-mysql.t b/t/026-mysql.t
index 02e14b938200d5d8ca6a1f44a24213bb048e631c..e7ab1706006a1ccb539db7a55e5e05f48aecc2e8 100644
--- a/t/026-mysql.t
+++ b/t/026-mysql.t
@@ -16,6 +16,7 @@ run_tests();
 __DATA__
 
 === TEST 1: when mysql query timed out, kill that query by Lua
+--- no_http2
 --- http_config
     upstream backend {
         drizzle_server 127.0.0.1:$TEST_NGINX_MYSQL_PORT protocol=mysql
diff --git a/t/033-ctx.t b/t/033-ctx.t
index 782a0fab6ea691558ee792315dda12cc52155e37..77bd15f8c19f4be61892dde2277b3ce872150ad5 100644
--- a/t/033-ctx.t
+++ b/t/033-ctx.t
@@ -279,7 +279,7 @@ GET /t
 --- error_log
 ngx.ctx = 32
 --- curl_error eval
-qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 0 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
+qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
 
 
 
diff --git a/t/041-header-filter.t b/t/041-header-filter.t
index 23fdac02cdc5faa2736d521f987eac722ff88282..84bb39fba21bcef8f88fe63a9e77875cd3537007 100644
--- a/t/041-header-filter.t
+++ b/t/041-header-filter.t
@@ -125,7 +125,7 @@ GET /read
 --- error_code
 --- response_body
 --- curl_error eval
-qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 0 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
+qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
 
 
 
@@ -449,6 +449,7 @@ GET /lua
 
 
 === TEST 21: lua error (string)
+--- no_http2
 --- config
     location /lua {
         set $foo '';
@@ -468,11 +469,12 @@ failed to run header_filter_by_lua*: header_filter_by_lua(nginx.conf:47):2: Some
 --- no_error_log
 [alert]
 --- curl_error eval
-qr/curl: \(56\) Failure when receiving data from the peer|curl: \(92\) HTTP\/2 stream 0 was not closed cleanly|curl: \(52\) Empty reply from server|curl: \(95\) HTTP\/3 stream 0 reset by server/
+qr/curl: \(56\) Failure when receiving data from the peer|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(28\) Remote peer returned unexpected data|curl: \(52\) Empty reply from server|curl: \(95\) HTTP\/3 stream 0 reset by server/
 
 
 
 === TEST 22: lua error (nil)
+--- no_http2
 --- config
     location /lua {
         set $foo '';
@@ -492,7 +494,7 @@ failed to run header_filter_by_lua*: unknown reason
 --- no_error_log
 [alert]
 --- curl_error eval
-qr/curl: \(56\) Failure when receiving data from the peer|curl: \(92\) HTTP\/2 stream 0 was not closed cleanly|curl: \(52\) Empty reply from server|curl: \(95\) HTTP\/3 stream 0 reset by server/
+qr/curl: \(56\) Failure when receiving data from the peer|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(28\) Remote peer returned unexpected data|curl: \(52\) Empty reply from server|curl: \(95\) HTTP\/3 stream 0 reset by server/
 
 
 
@@ -508,7 +510,7 @@ GET /lua
 --- error_log
 API disabled in the context of header_filter_by_lua*
 --- curl_error eval
-qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 0 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
+qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
 
 
 
@@ -524,7 +526,7 @@ GET /lua
 --- error_log
 API disabled in the context of header_filter_by_lua*
 --- curl_error eval
-qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 0 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
+qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
 
 
 
@@ -540,7 +542,7 @@ GET /lua
 --- error_log
 API disabled in the context of header_filter_by_lua*
 --- curl_error eval
-qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 0 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
+qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
 
 
 
@@ -556,7 +558,7 @@ GET /lua
 --- error_log
 API disabled in the context of header_filter_by_lua*
 --- curl_error eval
-qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 0 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
+qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
 
 
 
@@ -572,7 +574,7 @@ GET /lua
 --- error_log
 API disabled in the context of header_filter_by_lua*
 --- curl_error eval
-qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 0 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
+qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
 
 
 
@@ -592,7 +594,7 @@ GET /lua
 --- error_log
 API disabled in the context of header_filter_by_lua*
 --- curl_error eval
-qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 0 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
+qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
 
 
 
@@ -612,7 +614,7 @@ GET /lua
 --- error_log
 API disabled in the context of header_filter_by_lua*
 --- curl_error eval
-qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 0 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
+qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
 
 
 
@@ -628,7 +630,7 @@ GET /lua
 --- error_log
 API disabled in the context of header_filter_by_lua*
 --- curl_error eval
-qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 0 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
+qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
 
 
 
@@ -644,7 +646,7 @@ GET /lua
 --- error_log
 API disabled in the context of header_filter_by_lua*
 --- curl_error eval
-qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 0 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
+qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
 
 
 
@@ -660,7 +662,7 @@ GET /lua
 --- error_log
 API disabled in the context of header_filter_by_lua*
 --- curl_error eval
-qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 0 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
+qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
 
 
 
@@ -694,7 +696,7 @@ GET /lua
 --- error_log eval
 qr/API disabled in the context of header_filter_by_lua\*|http3 requests are not supported without content-length header/ms
 --- curl_error eval
-qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 0 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
+qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
 
 
 
@@ -718,7 +720,7 @@ if (defined $ENV{TEST_NGINX_USE_HTTP3}) {
 
 $err_log;
 --- curl_error eval
-qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 0 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
+qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
 
 
 
@@ -734,7 +736,7 @@ GET /lua
 --- error_log
 API disabled in the context of header_filter_by_lua*
 --- curl_error eval
-qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 0 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
+qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
 
 
 
@@ -750,7 +752,7 @@ GET /lua
 --- error_log
 API disabled in the context of header_filter_by_lua*
 --- curl_error eval
-qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 0 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
+qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
 
 
 
@@ -799,7 +801,7 @@ in function 'error'
 in function 'bar'
 in function 'foo'
 --- curl_error eval
-qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 0 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
+qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
 
 
 
@@ -819,7 +821,7 @@ GET /lua?a=1&b=2
 --- error_log eval
 qr/failed to load external Lua file ".*?test2\.lua": cannot open .*? No such file or directory/
 --- curl_error eval
-qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 0 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
+qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
 
 
 
@@ -867,7 +869,7 @@ failed to load inlined Lua code: header_filter_by_lua(nginx.conf:41):2: unexpect
 --- no_error_log
 no_such_error
 --- curl_error eval
-qr/curl: \(56\) Failure when receiving data from the peer/
+qr/curl: \(56\) Failure when receiving data from the peer|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly: INTERNAL_ERROR \(err 2\)/
 
 
 
@@ -898,7 +900,7 @@ failed to load inlined Lua code: header_filter_by_lua(nginx.conf:49):2: unexpect
 --- no_error_log
 no_such_error
 --- curl_error eval
-qr/curl: \(56\) Failure when receiving data from the peer/
+qr/curl: \(56\) Failure when receiving data from the peer|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly: INTERNAL_ERROR \(err 2\)/
 
 
 
@@ -924,4 +926,4 @@ failed to load inlined Lua code: header_filter_by_lua(...90123456789012345678901
 --- no_error_log
 [alert]
 --- curl_error eval
-qr/curl: \(56\) Failure when receiving data from the peer/
+qr/curl: \(56\) Failure when receiving data from the peer|curl: \(56\) Failure when receiving data from the peer|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly: INTERNAL_ERROR \(err 2\)/
diff --git a/t/056-flush.t b/t/056-flush.t
index 4376b189304b51e651395c9a3296cb6bdb797fa8..d2b107c75428f559d6ada3c34343e3c0531f83f1 100644
--- a/t/056-flush.t
+++ b/t/056-flush.t
@@ -516,7 +516,7 @@ GET /test
 my @errlog;
 if (defined $ENV{TEST_NGINX_USE_HTTP2}) {
     @errlog = [
-qr/lua writes elapsed 0\.[7-9]\d+ sec/,
+qr/lua writes elapsed (?:0\.[7-9]\d+|[12]\.\d+) sec/,
 qr/lua flush requires waiting: buffered 0x[0-9a-f]+, delayed:1/,
 ];
 } else {
@@ -526,7 +526,6 @@ qr/lua flush requires waiting: buffered 0x[0-9a-f]+, delayed:1/,
 ];
 }
 @errlog;
-
 --- no_error_log
 [error]
 --- timeout: 4
diff --git a/t/058-tcp-socket.t b/t/058-tcp-socket.t
index ef2b05f0d03c4a8ed9e63921f751dd385f0ca8ba..db5cb60e84c98a23c9f39009d4fef52514da60ae 100644
--- a/t/058-tcp-socket.t
+++ b/t/058-tcp-socket.t
@@ -1,6 +1,7 @@
 # vim:set ft= ts=4 sw=4 et fdm=marker:
 
 use Test::Nginx::Socket::Lua;
+use Test::Nginx::Socket::Lua::Stream;
 
 repeat_each(2);
 
@@ -10,6 +11,7 @@ our $HtmlDir = html_dir;
 
 $ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211;
 $ENV{TEST_NGINX_RESOLVER} ||= '8.8.8.8';
+$ENV{TEST_NGINX_HTML_DIR} ||= html_dir();
 
 #log_level 'warn';
 log_level 'debug';
@@ -4497,3 +4499,67 @@ reused times: 3, setkeepalive err: closed
 --- no_error_log
 [error]
 --- skip_eval: 3: $ENV{TEST_NGINX_EVENT_TYPE} && $ENV{TEST_NGINX_EVENT_TYPE} ne 'epoll'
+
+
+
+=== TEST 74: setkeepalive with TLSv1.3
+--- skip_openssl: 3: < 1.1.1
+--- stream_server_config
+        listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl;
+        ssl_certificate     ../../cert/test.crt;
+        ssl_certificate_key ../../cert/test.key;
+        ssl_protocols TLSv1.3;
+
+        content_by_lua_block {
+            local sock = assert(ngx.req.socket(true))
+            local data
+            while true do
+                data = assert(sock:receive())
+                assert(data == "hello")
+            end
+        }
+--- config
+    location /test {
+        lua_ssl_protocols TLSv1.3;
+        content_by_lua_block {
+            local sock = ngx.socket.tcp()
+            sock:settimeout(2000)
+
+            local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock")
+            if not ok then
+                ngx.say("failed to connect: ", err)
+                return
+            end
+
+            ngx.say("connected: ", ok)
+
+            local ok, err = sock:sslhandshake(false, nil, false)
+            if not ok then
+                ngx.say("failed to sslhandshake: ", err)
+                return
+            end
+
+            local ok, err = sock:send("hello\n")
+            if not ok then
+                ngx.say("failed to send: ", err)
+                return
+            end
+
+            -- sleep a while to make sure the NewSessionTicket message has arrived
+            ngx.sleep(1)
+
+            local ok, err = sock:setkeepalive()
+            if not ok then
+                ngx.say("failed to setkeepalive: ", err)
+            else
+                ngx.say("setkeepalive: ", ok)
+            end
+        }
+    }
+--- request
+GET /test
+--- response_body
+connected: 1
+setkeepalive: 1
+--- no_error_log
+[error]
diff --git a/t/062-count.t b/t/062-count.t
index 957590292b5235218ee4379dbd07b3d36c1fd31d..d0eabb1f2d819c1b93eb40e2115ac3db2d8c35f3 100644
--- a/t/062-count.t
+++ b/t/062-count.t
@@ -34,7 +34,7 @@ __DATA__
 --- request
 GET /test
 --- response_body
-ngx: 116
+ngx: 117
 --- no_error_log
 [error]
 
@@ -55,7 +55,7 @@ ngx: 116
 --- request
 GET /test
 --- response_body
-116
+117
 --- no_error_log
 [error]
 
@@ -83,7 +83,7 @@ GET /test
 --- request
 GET /test
 --- response_body
-n = 116
+n = 117
 --- no_error_log
 [error]
 
@@ -306,7 +306,7 @@ GET /t
 --- response_body_like: 404 Not Found
 --- error_code: 404
 --- error_log
-ngx. entry count: 116
+ngx. entry count: 117
 
 
 
diff --git a/t/068-socket-keepalive.t b/t/068-socket-keepalive.t
index 626b4416785dafa7652fd23e1fe47d4f2eaf98ab..423d391799af1b8b433afe2ef01e281895c86f0e 100644
--- a/t/068-socket-keepalive.t
+++ b/t/068-socket-keepalive.t
@@ -1384,6 +1384,7 @@ bad argument #3 to 'connect' (bad "pool" option type: boolean)
 
 
 === TEST 23: clear the redis store
+--- no_http2
 --- config
     location /t {
         redis2_query flushall;
@@ -3033,6 +3034,7 @@ lua tcp socket keepalive create connection pool for key "B"
 
 
 === TEST 54: wrong first argument for setkeepalive
+--- no_http2
 --- quic_max_idle_timeout: 1.2
 --- http_config eval
     "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';"
@@ -3115,6 +3117,7 @@ qr{HTTP/3 stream 0 reset by server}
 
 
 === TEST 55: wrong second argument for setkeepalive
+--- no_http2
 --- quic_max_idle_timeout: 1.2
 --- http_config eval
     "lua_package_path '$::HtmlDir/?.lua;./?.lua;;';"
diff --git a/t/091-coroutine.t b/t/091-coroutine.t
index bfbdb389376943b506a46acfb20652b3667cfabe..16930ed3c0de4d9f805062dd406376782483056a 100644
--- a/t/091-coroutine.t
+++ b/t/091-coroutine.t
@@ -763,7 +763,7 @@ GET /lua
 --- error_log
 API disabled in the context of header_filter_by_lua*
 --- curl_error eval
-qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 0 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
+qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
 
 
 
@@ -1423,6 +1423,7 @@ GET /t
 
 
 === TEST 35: coroutine.wrap runtime errors do not log errors
+--- no_http2
 --- config
     location = /t {
         content_by_lua_block {
@@ -1700,7 +1701,7 @@ GET /t
     "in function 'co'"
 ]
 --- curl_error eval
-qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 0 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
+qr/curl: \(52\) Empty reply from server|curl: \(92\) HTTP\/2 stream 1 was not closed cleanly|curl: \(95\) HTTP\/3 stream 0 reset by server/
 
 
 
diff --git a/t/094-uthread-exit.t b/t/094-uthread-exit.t
index 0194e44b31f83e682057ed06a83f412a54f10ec8..375d8d6023a7ff8c053be563cbd311ab40bd32df 100644
--- a/t/094-uthread-exit.t
+++ b/t/094-uthread-exit.t
@@ -8,7 +8,7 @@ our $StapScript = $t::StapThread::StapScript;
 
 repeat_each(2);
 
-plan tests => repeat_each() * (blocks() * 4);
+plan tests => repeat_each() * (blocks() * 4 - 2);
 
 $ENV{TEST_NGINX_RESOLVER} ||= '8.8.8.8';
 $ENV{TEST_NGINX_MEMCACHED_PORT} ||= '11211';
@@ -1135,7 +1135,6 @@ free request
 attempt to abort with pending subrequests
 --- no_error_log
 [alert]
-[warn]
 
 
 
@@ -1313,6 +1312,7 @@ attempt to abort with pending subrequests
 
 
 === TEST 16: exit in entry thread (user thread is still pending on ngx.location.capture_multi), without pending output
+--- no_http2
 --- config
     location /lua {
         client_body_timeout 12000ms;
@@ -1407,6 +1407,7 @@ qr#curl: \(52\) Empty reply from server|curl: \(95\) HTTP/3 stream 0 reset by se
 
 
 === TEST 17: exit(444) in user thread (entry thread is still pending on ngx.location.capture), with pending output
+--- no_http2
 --- config
     location /lua {
         client_body_timeout 12000ms;
@@ -1492,6 +1493,7 @@ qr#curl: \(52\) Empty reply from server|curl: \(95\) HTTP/3 stream 0 reset by se
 
 
 === TEST 18: exit(408) in user thread (entry thread is still pending on ngx.location.capture), with pending output
+--- no_http2
 --- config
     location /lua {
         client_body_timeout 12000ms;
@@ -1655,6 +1657,6 @@ free request
 --- no_error_log
 [alert]
 [error]
-[warn]
+
 --- curl_error eval
 qr#curl: \(52\) Empty reply from server|curl: \(95\) HTTP/3 stream 0 reset by server#
diff --git a/t/095-uthread-exec.t b/t/095-uthread-exec.t
index 4cd121da1e27681950e33f9cc017e9dcca37975f..9ef63560901f13b23d32141d145ca2024a1fa90f 100644
--- a/t/095-uthread-exec.t
+++ b/t/095-uthread-exec.t
@@ -344,6 +344,7 @@ attempt to abort with pending subrequests
 
 
 === TEST 6: exec in entry thread (user thread is still pending on ngx.location.capture), without pending output
+--- no_http2
 --- config
     location /lua {
         client_body_timeout 12000ms;
diff --git a/t/096-uthread-redirect.t b/t/096-uthread-redirect.t
index 62909b944cce0b710575ffb2d4b9d1d4f3dbdf37..5df5ecfb090cdc3d18ea506cf36107316b7c8b07 100644
--- a/t/096-uthread-redirect.t
+++ b/t/096-uthread-redirect.t
@@ -190,6 +190,7 @@ free request
 
 
 === TEST 3: ngx.redirect() in entry thread (user thread is still pending on ngx.location.capture_multi), without pending output
+--- no_http2
 --- config
     location /lua {
         client_body_timeout 12000ms;
diff --git a/t/129-ssl-socket.t b/t/129-ssl-socket.t
index ca8d5a49e6a0f8648fb1c9f165f11b3b5558b9b6..611e72ab2f1591f3c3cdc38a64eecebbef70e094 100644
--- a/t/129-ssl-socket.t
+++ b/t/129-ssl-socket.t
@@ -1155,7 +1155,7 @@ SSL reused session
         server_name         test.com;
         ssl_certificate     $TEST_NGINX_CERT_DIR/cert/test.crt;
         ssl_certificate_key $TEST_NGINX_CERT_DIR/cert/test.key;
-        ssl_protocols       TLSv1;
+        ssl_protocols       TLSv1.2;
 
         location / {
             content_by_lua_block {
@@ -1165,7 +1165,7 @@ SSL reused session
     }
 --- config
     server_tokens off;
-    lua_ssl_ciphers ECDHE-RSA-AES256-SHA;
+    lua_ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384;
 
     location /t {
         content_by_lua '
@@ -1229,7 +1229,7 @@ lua ssl free session: ([0-9A-F]+)
 $/
 --- error_log eval
 ['lua ssl server name: "test.com"',
-qr/SSL: TLSv\d(?:\.\d)?, cipher: "ECDHE-RSA-AES256-SHA (SSLv3|TLSv1)/]
+qr/SSL: TLSv\d(?:\.\d)?, cipher: "ECDHE-RSA-AES256-GCM-SHA384 (SSLv3|TLSv1\.2)/]
 --- no_error_log
 SSL reused session
 [error]
@@ -1245,7 +1245,7 @@ SSL reused session
         server_name         test.com;
         ssl_certificate     $TEST_NGINX_CERT_DIR/cert/test.crt;
         ssl_certificate_key $TEST_NGINX_CERT_DIR/cert/test.key;
-        ssl_protocols       TLSv1;
+        ssl_protocols       TLSv1.2;
 
         location / {
             content_by_lua_block {
@@ -1255,7 +1255,7 @@ SSL reused session
     }
 --- config
     server_tokens off;
-    lua_ssl_protocols TLSv1;
+    lua_ssl_protocols TLSv1.2;
 
     location /t {
         content_by_lua '
@@ -1319,7 +1319,7 @@ lua ssl free session: ([0-9A-F]+)
 $/
 --- error_log eval
 ['lua ssl server name: "test.com"',
-qr/SSL: TLSv1, cipher: "ECDHE-RSA-AES256-SHA (SSLv3|TLSv1)/]
+qr/SSL: TLSv1\.2, cipher: "ECDHE-RSA-AES256-GCM-SHA384 TLSv1\.2/]
 --- no_error_log
 SSL reused session
 [error]
@@ -2614,10 +2614,10 @@ SSL reused session
 
 --- request
 GET /t
---- response_body
-connected: 1
-failed to do SSL handshake: 18: self signed certificate
-
+--- response_body eval
+qr/connected: 1
+failed to do SSL handshake: 18: self[- ]signed certificate
+/ms
 --- user_files eval
 ">>> test.key
 $::TestCertificateKey
@@ -2626,8 +2626,8 @@ $::TestCertificate"
 
 --- grep_error_log eval: qr/lua ssl (?:set|save|free) session: [0-9A-F]+/
 --- grep_error_log_out
---- error_log
-lua ssl certificate verify error: (18: self signed certificate)
+--- error_log eval
+qr/lua ssl certificate verify error: \(18: self[- ]signed certificate\)/
 --- no_error_log
 SSL reused session
 [alert]
diff --git a/t/138-balancer.t b/t/138-balancer.t
index 41be75fcdc0a6119578620681c0fd2db3864b4be..f48c7fa41dcffafeed976ec15eb21073c65ddd52 100644
--- a/t/138-balancer.t
+++ b/t/138-balancer.t
@@ -12,7 +12,7 @@ use Test::Nginx::Socket::Lua;
 
 repeat_each(2);
 
-plan tests => repeat_each() * (blocks() * 4 + 7);
+plan tests => repeat_each() * (blocks() * 4 - 3);
 
 #no_diff();
 no_long_string();
@@ -41,8 +41,6 @@ __DATA__
 '[lua] balancer_by_lua(nginx.conf:27):2: hello from balancer by lua! while connecting to upstream,',
 qr{\[crit\] .*? connect\(\) to 0\.0\.0\.1:80 failed .*?, upstream: "http://0\.0\.0\.1:80/t"},
 ]
---- no_error_log
-[warn]
 
 
 
@@ -67,7 +65,6 @@ qr{\[crit\] .*? connect\(\) to 0\.0\.0\.1:80 failed .*?, upstream: "http://0\.0\
 [lua] balancer_by_lua(nginx.conf:27):2: hello from balancer by lua! while connecting to upstream,
 --- no_error_log eval
 [
-'[warn]',
 qr{\[crit\] .*? connect\(\) to 0\.0\.0\.1:80 failed .*?, upstream: "http://0\.0\.0\.1:80/t"},
 ]
 
@@ -95,8 +92,6 @@ qr{\[crit\] .*? connect\(\) to 0\.0\.0\.1:80 failed .*?, upstream: "http://0\.0\
 '[lua] balancer_by_lua(nginx.conf:27):2: hello from balancer by lua! while connecting to upstream,',
 qr{\[crit\] .*? connect\(\) to 0\.0\.0\.1:80 failed .*?, upstream: "http://0\.0\.0\.1:80/t"},
 ]
---- no_error_log
-[warn]
 
 
 
@@ -125,8 +120,6 @@ qr{\[crit\] .*? connect\(\) to 0\.0\.0\.1:80 failed .*?, upstream: "http://0\.0\
 "2: variable foo = 33",
 qr/\[crit\] .* connect\(\) .*? failed/,
 ]
---- no_error_log
-[warn]
 
 
 
@@ -153,8 +146,6 @@ Foo: bar
 "header foo: bar",
 qr/\[crit\] .* connect\(\) .*? failed/,
 ]
---- no_error_log
-[warn]
 
 
 
@@ -180,12 +171,11 @@ Foo: bar
 ["arg foo: bar",
 qr/\[crit\] .* connect\(\) .*? failed/,
 ]
---- no_error_log
-[warn]
 
 
 
 === TEST 7: ngx.req.get_method() works
+--- no_http2
 --- http_config
     upstream backend {
         server 0.0.0.1;
@@ -208,8 +198,6 @@ Foo: bar
 "method: GET",
 qr/\[crit\] .* connect\(\) .*? failed/,
 ]
---- no_error_log
-[warn]
 
 
 
@@ -235,8 +223,6 @@ print("hello from balancer by lua!")
 '[lua] a.lua:1: hello from balancer by lua! while connecting to upstream,',
 qr{\[crit\] .*? connect\(\) to 0\.0\.0\.1:80 failed .*?, upstream: "http://0\.0\.0\.1:80/t"},
 ]
---- no_error_log
-[warn]
 
 
 
@@ -431,8 +417,6 @@ ctx counter: nil
 '[lua] balancer_by_lua(nginx.conf:27):2: hello from balancer by lua! while connecting to upstream,',
 qr{\[crit\] .*? connect\(\) to 0\.0\.0\.1:80 failed .*?, upstream: "http://0\.0\.0\.1:80/t"},
 ]
---- no_error_log
-[warn]
 
 
 
@@ -589,8 +573,6 @@ upstream sent more data than specified in "Content-Length" header while reading
 --- error_code: 500
 --- error_log eval
  "failed to load inlined Lua code: balancer_by_lua(nginx.conf:27):3: ')' expected (to close '(' at line 2) near '<eof>'",
---- no_error_log
-[warn]
 
 
 
diff --git a/t/140-ssl-c-api.t b/t/140-ssl-c-api.t
index 4c81b4f05a6cb42b3d92d066f1a9df1fb715c5bb..81d8375bb5dc16c8e5436c4b9a58aee9957e5742 100644
--- a/t/140-ssl-c-api.t
+++ b/t/140-ssl-c-api.t
@@ -954,8 +954,8 @@ client certificate subject: emailAddress=agentzh@gmail.com,CN=test.com
 
 --- request
 GET /t
---- response_body
-FAILED:self signed certificate
+--- response_body eval
+qr/FAILED:self[- ]signed certificate/
 
 --- error_log
 client certificate subject: emailAddress=agentzh@gmail.com,CN=test.com
diff --git a/t/143-ssl-session-fetch.t b/t/143-ssl-session-fetch.t
index 2f988ded9c0eaedcb6e3235b53b15dde95a9fa95..8e09a52d644e892b0e6cdcc79cc85207814d5138 100644
--- a/t/143-ssl-session-fetch.t
+++ b/t/143-ssl-session-fetch.t
@@ -7,7 +7,7 @@ use File::Basename;
 
 repeat_each(3);
 
-plan tests => repeat_each() * (blocks() * 6);
+plan tests => repeat_each() * (blocks() * 6) - 3;
 
 $ENV{TEST_NGINX_HTML_DIR} ||= html_dir();
 
@@ -1319,105 +1319,13 @@ connected: 1
 ssl handshake: cdata
 close: 1 nil
 --- no_error_log
-[warn]
 [error]
 [alert]
 [emerg]
 
 
 
-=== TEST 16: ssl_session_fetch_by_lua* always runs when using SSLv3 (SSLv3 does not support session tickets)
---- http_config
-    ssl_session_fetch_by_lua_block { print("ssl_session_fetch_by_lua* is running!") }
-    server {
-        listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl;
-        server_name test.com;
-        ssl_certificate $TEST_NGINX_CERT_DIR/cert/test.crt;
-        ssl_certificate_key $TEST_NGINX_CERT_DIR/cert/test.key;
-        ssl_protocols SSLv3;
-        server_tokens off;
-    }
---- config
-    server_tokens off;
-    lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt;
-    lua_ssl_protocols SSLv3;
-
-    location /t {
-        content_by_lua_block {
-            do
-                local sock = ngx.socket.tcp()
-
-                sock:settimeout(5000)
-
-                local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock")
-                if not ok then
-                    ngx.say("failed to connect: ", err)
-                    return
-                end
-
-                ngx.say("connected: ", ok)
-
-                local sess, err = sock:sslhandshake(package.loaded.session, "test.com", true)
-                if not sess then
-                    ngx.say("failed to do SSL handshake: ", err)
-                    return
-                end
-
-                ngx.say("ssl handshake: ", type(sess))
-
-                package.loaded.session = sess
-
-                local ok, err = sock:close()
-                ngx.say("close: ", ok, " ", err)
-            end  -- do
-            -- collectgarbage()
-        }
-    }
---- request
-GET /t
---- response_body
-connected: 1
-ssl handshake: cdata
-close: 1 nil
---- grep_error_log eval: qr/ssl_session_fetch_by_lua\(nginx\.conf:\d+\):.*?,|\bssl session fetch: connection reusable: \d+|\breusable connection: \d+/
---- grep_error_log_out eval
-# Since nginx version 1.17.9, nginx call ngx_reusable_connection(c, 0)
-# before call ssl callback function
-$Test::Nginx::Util::NginxVersion >= 1.017009 ?
-[
-qr/\A(?:reusable connection: [01]\n)+\z/s,
-qr/^reusable connection: 0
-ssl session fetch: connection reusable: 0
-ssl_session_fetch_by_lua\(nginx\.conf:\d+\):1: ssl_session_fetch_by_lua\* is running!,
-/m,
-qr/^reusable connection: 0
-ssl session fetch: connection reusable: 0
-ssl_session_fetch_by_lua\(nginx\.conf:\d+\):1: ssl_session_fetch_by_lua\* is running!,
-/m,
-]
-:
-[
-qr/\A(?:reusable connection: [01]\n)+\z/s,
-qr/^reusable connection: 1
-ssl session fetch: connection reusable: 1
-reusable connection: 0
-ssl_session_fetch_by_lua\(nginx\.conf:\d+\):1: ssl_session_fetch_by_lua\* is running!,
-/m,
-qr/^reusable connection: 1
-ssl session fetch: connection reusable: 1
-reusable connection: 0
-ssl_session_fetch_by_lua\(nginx\.conf:\d+\):1: ssl_session_fetch_by_lua\* is running!,
-/m,
-]
---- no_error_log
-[error]
-[alert]
-[emerg]
---- skip_eval: 6:$ENV{TEST_NGINX_USE_HTTP3}
-
-
-
-=== TEST 17: ssl_session_fetch_by_lua* can yield when reading early data
+=== TEST 16: ssl_session_fetch_by_lua* can yield when reading early data
 --- skip_openssl: 6: < 1.1.1
 --- http_config
     ssl_session_fetch_by_lua_block {
@@ -1494,7 +1402,7 @@ qr/elapsed in ssl_session_fetch_by_lua\*: 0\.(?:09|1[01])\d+,/,
 
 
 
-=== TEST 18: cosocket (UDP)
+=== TEST 17: cosocket (UDP)
 --- http_config
     ssl_session_fetch_by_lua_block {
         local sock = ngx.socket.udp()
@@ -1589,7 +1497,7 @@ close: 1 nil
 
 
 
-=== TEST 19: uthread (kill)
+=== TEST 18: uthread (kill)
 --- http_config
     ssl_session_fetch_by_lua_block {
         local function f()
@@ -1689,7 +1597,7 @@ uthread: failed to kill: already waited or killed
 
 
 
-=== TEST 20: uthread (wait)
+=== TEST 19: uthread (wait)
 --- http_config
     ssl_session_fetch_by_lua_block {
         local function f()
diff --git a/t/163-signal.t b/t/163-signal.t
index 0ce8fa26133edf38f2e91537813d452774e834f5..a0c2ee8082de4150c82246f6167f79b6bea89d84 100644
--- a/t/163-signal.t
+++ b/t/163-signal.t
@@ -42,7 +42,7 @@ qr/\[notice\] \d+#\d+: exit$/
 --- no_error_log eval
 qr/\[notice\] \d+#\d+: reconfiguring/
 --- curl_error eval
-qr/curl: \(28\) Operation timed out after \d+ milliseconds with 0 bytes received|curl: \(56\) Recv failure: Connection reset by peer|curl: \(55\) sendmsg\(\) returned -1 \(errno 111\)/
+qr/curl: \(28\) Operation timed out after \d+ milliseconds with 0 bytes received|curl: \(56\) Recv failure: Connection reset by peer|curl: \(56\) Failure when receiving data from the peer|curl: \(55\) sendmsg\(\) returned -1 \(errno 111\)/
 
 
 
diff --git a/util/build-without-ssl.sh b/util/build-without-ssl.sh
index c52d5873f6b3c93fd00fffe73d8e80a4d73b8224..2a998e3c796fc205210462859dc3ef0c8d9d84a4 100755
--- a/util/build-without-ssl.sh
+++ b/util/build-without-ssl.sh
@@ -26,10 +26,10 @@ add_fake_shm_module="--add-module=$root/t/data/fake-shm-module"
 
 disable_pcre2=--without-pcre2
 answer=`$root/util/ver-ge "$NGINX_VERSION" 1.25.1`
-if [ "$answer" = "N" ] || [ "$USE_PCRE2" = "Y" ]; then
+if [ "$answer" = "N" ] || [ -n "$PCRE2_VER" ]; then
     disable_pcre2=""
 fi
-if [ "$USE_PCRE2" = "Y" ]; then
+if [ -n "$PCRE2_VER" ]; then
     PCRE_INC=$PCRE2_INC
     PCRE_LIB=$PCRE2_LIB
 fi