Merge remote-tracking branch 'upstream/master' into 11-Dev
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2e6b59f..a2e4d87 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -24,7 +24,7 @@
 endif()
 
 cmake_minimum_required(VERSION 3.20..3.27)
-project(ats VERSION 10.2.0)
+project(ats VERSION 11.0.0)
 
 set(TS_VERSION_MAJOR ${PROJECT_VERSION_MAJOR})
 set(TS_VERSION_MINOR ${PROJECT_VERSION_MINOR})
@@ -744,6 +744,7 @@
 
 add_subdirectory(src/tscpp/api)
 add_subdirectory(src/tsutil)
+add_subdirectory(src/config)
 add_subdirectory(src/tscore)
 add_subdirectory(src/records)
 add_subdirectory(src/iocore)
diff --git a/configs/records.yaml.default.in b/configs/records.yaml.default.in
index 225ae80..67bd8e6 100644
--- a/configs/records.yaml.default.in
+++ b/configs/records.yaml.default.in
@@ -199,7 +199,7 @@
 ##############################################################################
 # SSL Termination. Docs:
 #    https://docs.trafficserver.apache.org/en/latest/admin-guide/files/records.yaml.en.html#client-related-configuration
-#    https://docs.trafficserver.apache.org/en/latest/admin-guide/files/ssl_multicert.config.en.html
+#    https://docs.trafficserver.apache.org/en/latest/admin-guide/files/ssl_multicert.yaml.en.html
 ##############################################################################
       verify:
         server:
diff --git a/configs/ssl_multicert.config.default b/configs/ssl_multicert.config.default
deleted file mode 100644
index 43eb43e..0000000
--- a/configs/ssl_multicert.config.default
+++ /dev/null
@@ -1,66 +0,0 @@
-#
-# ssl_multicert.config
-#
-# Documentation:
-#    https://docs.trafficserver.apache.org/en/latest/admin-guide/files/ssl_multicert.config.en.html
-#
-# Allows a TLS certificate and private key to be tied to a specific
-# hostname or IP address. At load time, the certificate is parsed to
-# extract the subject CN and all the DNS subjectAltNames.  The
-# certificate will be presented for connections requesting any of the
-# hostnames found in the certificate. Wildcard names in the certificates
-# are supported, but only of the form '*.domain.com', ie. where '*'
-# is the leftmost domain component.
-#
-# The certificate file path, CA path and key path specified in
-# records.yaml will be used for all certificates, CAs and keys
-# specified here.
-#
-# Fields:
-#
-# dest_ip=ADDRESS
-#   The IP (v4 or v6) address that the certificate should be presented
-#   on. This is now only used as a fallback in the case that the TLS
-#   ServerNameIndication extension is not supported. If ADDRESS is
-#   '*', the certificate will be used as the default fallback if no
-#   other match can be made.
-#
-#   The address specified here can contain a port specifier, in which
-#   case the corresponding certificate will only match for connections
-#   accepted on the specified port. IPv6 addresses must be enclosed by
-#   square brackets if they have a port, eg, [::1]:80.
-#
-# ssl_key_name=FILENAME
-#   The name of the file containing the private key for this certificate.
-#   If the key is contained in the certificate file, this field can be
-#   omitted.
-#
-# ssl_ca_name=FILENAME
-#   If your certificates have different Certificate Authorities, you
-#   can optionally specify the corresponding file here.
-#
-# ssl_cert_name=FILENAME
-#   The name of the file containing the TLS certificate. This is the
-#   only field that is required to be present.
-#
-# ssl_key_dialog=[builtin|exec:/path/to/program]
-#   Method used to provide a pass phrase for encrypted private keys.
-#   Two options are supported: builtin and exec
-#     builtin - Requests passphrase via stdin/stdout. Useful for debugging.
-#     exec: - Executes a program and uses the stdout output for the pass
-#       phrase.
-#
-# action=[tunnel]
-#   If the tunnel matches this line, traffic server will not participate
-#   in the handshake.  But rather it will blind tunnel the SSL connection.
-#   If the connection is identified by server name, an openSSL patch must
-#   be applied to enable this functionality.  See TS-3006 for details.
-#
-# Examples:
-#   ssl_cert_name=foo.pem
-#   dest_ip=*	ssl_cert_name=bar.pem ssl_key_name=barKey.pem
-#   dest_ip=209.131.48.79	ssl_cert_name=server.pem ssl_key_name=serverKey.pem
-#   dest_ip=10.0.0.1:99 ssl_cert_name=port99.pem
-#   ssl_cert_name=foo.pem ssl_key_dialog="exec:/usr/bin/mypass foo 'ba r'"
-#   ssl_cert_name=foo.pem action=tunnel
-#   ssl_cert_name=wildcardcert.pem ssl_key_name=privkey.pem
diff --git a/configs/ssl_multicert.yaml.default b/configs/ssl_multicert.yaml.default
new file mode 100644
index 0000000..c5eac93
--- /dev/null
+++ b/configs/ssl_multicert.yaml.default
@@ -0,0 +1,91 @@
+#
+# ssl_multicert.yaml
+#
+# Documentation:
+#   https://docs.trafficserver.apache.org/en/latest/admin-guide/files/ssl_multicert.yaml.en.html
+#
+# Allows a TLS certificate and private key to be tied to a specific
+# hostname or IP address. At load time, the certificate is parsed to
+# extract the subject CN and all the DNS subjectAltNames. The
+# certificate will be presented for connections requesting any of the
+# hostnames found in the certificate. Wildcard names in the certificates
+# are supported, but only of the form '*.domain.com', ie. where '*'
+# is the leftmost domain component.
+#
+# The certificate file path, CA path and key path specified in
+# records.yaml will be used for all certificates, CAs and keys
+# specified here.
+#
+# Fields:
+#
+# ssl_cert_name: FILENAME
+#   The name of the file containing the TLS certificate. This is the
+#   only field that is required to be present (unless action is tunnel).
+#
+# dest_ip: ADDRESS
+#   The IP (v4 or v6) address that the certificate should be presented
+#   on. This is now only used as a fallback in the case that the TLS
+#   ServerNameIndication extension is not supported. If ADDRESS is
+#   '*', the certificate will be used as the default fallback if no
+#   other match can be made.
+#
+#   The address specified here can contain a port specifier, in which
+#   case the corresponding certificate will only match for connections
+#   accepted on the specified port. IPv6 addresses must be enclosed by
+#   square brackets if they have a port, eg, [::1]:80.
+#
+# ssl_key_name: FILENAME
+#   The name of the file containing the private key for this certificate.
+#   If the key is contained in the certificate file, this field can be
+#   omitted.
+#
+# ssl_ca_name: FILENAME
+#   If your certificates have different Certificate Authorities, you
+#   can optionally specify the corresponding file here.
+#
+# ssl_ocsp_name: FILENAME
+#   The name of the file containing the OCSP response for stapling.
+#
+# ssl_key_dialog: builtin|exec:/path/to/program
+#   Method used to provide a pass phrase for encrypted private keys.
+#   Two options are supported: builtin and exec
+#     builtin - Requests passphrase via stdin/stdout. Useful for debugging.
+#     exec: - Executes a program and uses the stdout output for the pass
+#       phrase.
+#
+# ssl_ticket_enabled: 0|1
+#   Enable or disable session tickets for this certificate.
+#
+# ssl_ticket_number: NUMBER
+#   Number of session tickets to issue for new TLSv1.3 connections.
+#
+# action: tunnel
+#   If the connection matches this entry, traffic server will not participate
+#   in the handshake. Instead, it will blind tunnel the SSL connection.
+#
+# Example configuration:
+#
+# ssl_multicert:
+#   - ssl_cert_name: server.pem
+#
+#   - ssl_cert_name: bar.pem
+#     ssl_key_name: barKey.pem
+#     dest_ip: "*"
+#
+#   - ssl_cert_name: server.pem
+#     ssl_key_name: serverKey.pem
+#     dest_ip: "209.131.48.79"
+#
+#   - ssl_cert_name: port99.pem
+#     dest_ip: "10.0.0.1:99"
+#
+#   - ssl_cert_name: foo.pem
+#     ssl_key_dialog: "exec:/usr/bin/mypass foo 'ba r'"
+#
+#   - action: tunnel
+#     dest_ip: "192.168.1.1"
+#
+#   - ssl_cert_name: wildcardcert.pem
+#     ssl_key_name: privkey.pem
+
+ssl_multicert: []
diff --git a/doc/admin-guide/files/index.en.rst b/doc/admin-guide/files/index.en.rst
index 1ce15d6..848729f 100644
--- a/doc/admin-guide/files/index.en.rst
+++ b/doc/admin-guide/files/index.en.rst
@@ -34,7 +34,7 @@
    records.yaml.en
    remap.config.en
    splitdns.config.en
-   ssl_multicert.config.en
+   ssl_multicert.yaml.en
    sni.yaml.en
    storage.config.en
    strategies.yaml.en
@@ -72,7 +72,7 @@
 :doc:`splitdns.config.en`
    Configures DNS servers to use under specific conditions.
 
-:doc:`ssl_multicert.config.en`
+:doc:`ssl_multicert.yaml.en`
    Configures |TS| to use different server certificates for SSL termination
    when listening on multiple addresses or when clients employ SNI.
 
diff --git a/doc/admin-guide/files/records.yaml.en.rst b/doc/admin-guide/files/records.yaml.en.rst
index d30a54a..590f291 100644
--- a/doc/admin-guide/files/records.yaml.en.rst
+++ b/doc/admin-guide/files/records.yaml.en.rst
@@ -4015,24 +4015,24 @@
    ===== ======================================================================
 
 
-.. ts:cv:: CONFIG proxy.config.ssl.server.multicert.filename STRING ssl_multicert.config
+.. ts:cv:: CONFIG proxy.config.ssl.server.multicert.filename STRING ssl_multicert.yaml
    :deprecated:
 
-   The location of the :file:`ssl_multicert.config` file, relative
+   The location of the :file:`ssl_multicert.yaml` file, relative
    to the |TS| configuration directory. In the following
    example, if the |TS| configuration directory is
    `/etc/trafficserver`, the |TS| SSL configuration file
    and the corresponding certificates are located in
    `/etc/trafficserver/ssl`::
 
-      CONFIG proxy.config.ssl.server.multicert.filename STRING ssl/ssl_multicert.config
+      CONFIG proxy.config.ssl.server.multicert.filename STRING ssl/ssl_multicert.yaml
       CONFIG proxy.config.ssl.server.cert.path STRING etc/trafficserver/ssl
       CONFIG proxy.config.ssl.server.private_key.path STRING etc/trafficserver/ssl
 
 .. ts:cv:: CONFIG proxy.config.ssl.server.multicert.exit_on_load_fail INT 1
 
    By default (``1``), |TS| will not start unless all the SSL certificates listed in the
-   :file:`ssl_multicert.config` file successfully load.  If false (``0``), SSL certificate
+   :file:`ssl_multicert.yaml` file successfully load.  If false (``0``), SSL certificate
    load failures will not prevent |TS| from starting.
 
 .. ts:cv:: CONFIG proxy.config.ssl.server.cert.path STRING /config
@@ -4041,21 +4041,21 @@
    and validation new SSL sessions. If this is a relative path,
    it is appended to the |TS| installation PREFIX. All
    certificates and certificate chains listed in
-   :file:`ssl_multicert.config` will be loaded relative to this path.
+   :file:`ssl_multicert.yaml` will be loaded relative to this path.
 
 .. ts:cv:: CONFIG proxy.config.ssl.server.private_key.path STRING NULL
 
    The location of the SSL certificate private keys. Change this
    variable only if the private key is not located in the SSL
    certificate file. All private keys listed in
-   :file:`ssl_multicert.config` will be loaded relative to this
+   :file:`ssl_multicert.yaml` will be loaded relative to this
    path.
 
 .. ts:cv:: CONFIG proxy.config.ssl.server.cert_chain.filename STRING NULL
 
    The name of a file containing a global certificate chain that
    should be used with every server certificate. This file is only
-   used if there are certificates defined in :file:`ssl_multicert.config`.
+   used if there are certificates defined in :file:`ssl_multicert.yaml`.
    Unless this is an absolute path, it is loaded relative to the
    path specified by :ts:cv:`proxy.config.ssl.server.cert.path`.
 
@@ -4123,81 +4123,17 @@
   Setting a value less than or equal to ``0`` effectively disables
   SSL session cache for the origin server.
 
-.. ts:cv:: CONFIG proxy.config.ssl.session_cache.mode INT 2
-
-   Sets the SSL session cache mode:
-
-   ===== ======================================================================
-   Value Description
-   ===== ======================================================================
-   ``0`` Disables the session cache entirely.
-   ``1`` Enables the session cache using OpenSSL's implementation.
-   ``2`` Default. Enables the session cache using |TS|'s implementation. This
-         implementation should perform much better than the OpenSSL
-         implementation.
-   ===== ======================================================================
-
-.. ts:cv:: CONFIG proxy.config.ssl.session_cache.enabled INT 2
-
-   .. deprecated:: 10.1.0
-      Use :ts:cv:`proxy.config.ssl.session_cache.mode` instead.
-
-   This configuration exists for historical reasons and is deprecated in favor of
-   :ts:cv:`proxy.config.ssl.session_cache.mode`. It accepts the same values and
-   has identical behavior, so see that documentation for details.
-
-.. ts:cv:: CONFIG proxy.config.ssl.session_cache.timeout INT 0
-
-  This configuration specifies the lifetime of SSL session cache
-  entries in seconds. If it is ``0``, then the SSL library will use
-  a default value, typically 300 seconds. Note: This option has no affect
-  when using the |TS| session cache (option ``2`` in
-  ``proxy.config.ssl.session_cache.mode``)
-
-   See :ref:`admin-performance-timeouts` for more discussion on |TS| timeouts.
-
-.. ts:cv:: CONFIG proxy.config.ssl.session_cache.auto_clear INT 1
-
-  This will set the OpenSSL auto clear flag. Auto clear is enabled by
-  default with ``1`` it can be disabled by changing this setting to ``0``.
-
-.. ts:cv:: CONFIG proxy.config.ssl.session_cache.size INT 102400
-
-  This configuration specifies the maximum number of entries
-  the SSL session cache may contain.
-
-.. ts:cv:: CONFIG proxy.config.ssl.session_cache.num_buckets INT 256
-
-  This configuration specifies the number of buckets to use with the
-  |TS| SSL session cache implementation. The TS implementation
-  is a fixed size hash map where each bucket is protected by a mutex.
-
-.. ts:cv:: CONFIG proxy.config.ssl.session_cache.skip_cache_on_bucket_contention INT 0
-
-   This configuration specifies the behavior of the |TS| SSL session
-   cache implementation during lock contention on each bucket:
-
-   ===== ======================================================================
-   Value Description
-   ===== ======================================================================
-   ``0`` Default. Don't skip session caching when bucket lock is contented.
-   ``1`` Disable the SSL session cache for a connection during lock contention.
-   ===== ======================================================================
-
 .. ts:cv:: CONFIG proxy.config.ssl.server.session_ticket.enable INT 1
 
   Set to 1 to enable Traffic Server to process TLS tickets for TLS session resumption.
 
 .. ts:cv:: CONFIG proxy.config.ssl.server.session_ticket.number INT 2
 
-  This configuration control the number of TLSv1.3 session tickets that are issued.
-  Take into account that setting the value to 0 will disable session caching for TLSv1.3
+  This configuration controls the number of TLSv1.3 session tickets that are issued.
+  Setting the value to 0 will disable session ticket-based session resumption for TLSv1.3
   connections.
 
-  Lowering this setting to ``1`` can be interesting when ``proxy.config.ssl.session_cache.mode`` is enabled because
-  otherwise for every new TLSv1.3 connection two session IDs will be inserted in the session cache.
-  On the other hand, if ``proxy.config.ssl.session_cache.mode``  is disabled, using the default value is recommended.
-  In those scenarios, increasing the number of tickets could be potentially beneficial for clients performing
+  Increasing the number of tickets could be potentially beneficial for clients performing
   multiple requests over concurrent TLS connections as per RFC 8446 clients SHOULDN'T reuse TLS Tickets.
 
   For more information see https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_num_tickets.html
@@ -4628,7 +4564,7 @@
    The directory path of the prefetched OCSP stapling responses. Change this
    variable only if you intend to use and administratively maintain
    prefetched OCSP stapling responses. All stapling responses listed in
-   :file:`ssl_multicert.config` will be loaded relative to this
+   :file:`ssl_multicert.yaml` will be loaded relative to this
    path.
 
 HTTP/2 Configuration
diff --git a/doc/admin-guide/files/ssl_multicert.config.en.rst b/doc/admin-guide/files/ssl_multicert.yaml.en.rst
similarity index 76%
rename from doc/admin-guide/files/ssl_multicert.config.en.rst
rename to doc/admin-guide/files/ssl_multicert.yaml.en.rst
index d78bc75..2c27d7e 100644
--- a/doc/admin-guide/files/ssl_multicert.config.en.rst
+++ b/doc/admin-guide/files/ssl_multicert.yaml.en.rst
@@ -15,13 +15,13 @@
   specific language governing permissions and limitations
   under the License.
 
-====================
-ssl_multicert.config
-====================
+==================
+ssl_multicert.yaml
+==================
 
-.. configfile:: ssl_multicert.config
+.. configfile:: ssl_multicert.yaml
 
-The :file:`ssl_multicert.config` file lets you configure Traffic
+The :file:`ssl_multicert.yaml` file lets you configure Traffic
 Server to use multiple SSL server certificates to terminate the SSL
 sessions. If you have a Traffic Server system with more than one
 IP address assigned to it, then you can assign a different SSL
@@ -36,17 +36,17 @@
 of the form `*.domain.com`, ie. where `*` is the leftmost domain
 component.
 
-Changes to :file:`ssl_multicert.config` can be applied to a running
+Changes to :file:`ssl_multicert.yaml` can be applied to a running
 Traffic Server using :option:`traffic_ctl config reload`.
 
 Format
 ======
 
-Each :file:`ssl_multicert.config` line consists of a sequence of
-`key=value` fields that specify how Traffic Server should use a
-particular SSL certificate.
+The :file:`ssl_multicert.yaml` file uses YAML format with a top-level
+``ssl_multicert`` key containing a sequence of certificate entries.
+Each entry is a mapping with the following keys:
 
-ssl_cert_name=FILENAME[,FILENAME ...]
+ssl_cert_name: FILENAME[,FILENAME ...]
   The name of the file containing the TLS certificate. *FILENAME*
   is located relative to the directory specified by the
   :ts:cv:`proxy.config.ssl.server.cert.path` configuration variable.
@@ -68,9 +68,10 @@
   You can also configure multiple leaf certificates in a same chain
   with OpenSSL 1.0.1.
 
-  This is the only field that is required to be present.
+  This is the only field that is required to be present (unless
+  ``action`` is set to ``tunnel``).
 
-dest_ip=ADDRESS (optional)
+dest_ip: ADDRESS (optional)
   The IP (v4 or v6) address that the certificate should be presented
   on. This is now only used as a fallback in the case that the TLS
   ServerNameIndication extension is not supported. If *ADDRESS* is
@@ -81,19 +82,19 @@
   IPv6 addresses must be enclosed by square brackets if they have
   a port, eg, [::1]:80. Care should be taken to make each ADDRESS unique.
 
-ssl_key_name=FILENAME (optional)
+ssl_key_name: FILENAME (optional)
   The name of the file containing the private key for this certificate.
   If the key is contained in the certificate file, this field can
   be omitted, otherwise *FILENAME* is resolved relative to the
   :ts:cv:`proxy.config.ssl.server.private_key.path` configuration variable.
 
-ssl_ca_name=FILENAME (optional)
+ssl_ca_name: FILENAME (optional)
   If the certificate is issued by an authority that is not in the
   system CA bundle, additional certificates may be needed to validate
   the certificate chain. *FILENAME* is resolved relative to the
   :ts:cv:`proxy.config.ssl.CA.cert.path` configuration variable.
 
-ssl_ocsp_name=FILENAME (optional)
+ssl_ocsp_name: FILENAME (optional)
   The name of the file containing the prefetched OCSP stapling response
   for this certificate. This field can be omitted to let trafficserver
   fetch OCSP responses dynamically. Otherwise, when included, the administrator is
@@ -101,16 +102,16 @@
   relative to the :ts:cv:`proxy.config.ssl.ocsp.response.path`
   configuration variable.
 
-ssl_ticket_enabled=1|0 (optional)
+ssl_ticket_enabled: 1|0 (optional)
   Enable RFC 5077 stateless TLS session tickets. To support this,
   OpenSSL should be upgraded to version 0.9.8f or higher. This
   option must be set to `0` to disable session ticket support.
 
-ssl_ticket_number=INTEGER (optional)
+ssl_ticket_number: INTEGER (optional)
   Specifies the number of TLSv1.3 session tickets that are issued.
   This defaults to 2 (the OpenSSL default)
 
-ssl_key_dialog=builtin|"exec:/path/to/program [args]" (optional)
+ssl_key_dialog: builtin|"exec:/path/to/program [args]" (optional)
   Method used to provide a pass phrase for encrypted private keys.  If the
   pass phrase is incorrect, SSL negotiation for this dest_ip will fail for
   clients who attempt to connect.
@@ -128,6 +129,10 @@
       program runs a security check to ensure that the system is not
       compromised by an attacker before providing the pass phrase.
 
+action: tunnel (optional)
+  If set to ``tunnel``, Traffic Server will not participate in the
+  TLS handshake and will blind tunnel the connection instead.
+
 Certificate Selection
 =====================
 
@@ -163,19 +168,27 @@
 Since the private key is included in the certificate files, no
 private key name is specified.
 
-::
+.. code-block:: yaml
 
-    dest_ip=111.11.11.1 ssl_cert_name=server.pem
-    dest_ip=11.1.1.1 ssl_cert_name=server1.pem
-    dest_ip=* ssl_cert_name=default.pem
+    ssl_multicert:
+      - ssl_cert_name: server.pem
+        dest_ip: "111.11.11.1"
+
+      - ssl_cert_name: server1.pem
+        dest_ip: "11.1.1.1"
+
+      - ssl_cert_name: default.pem
+        dest_ip: "*"
 
 The following example configures Traffic Server to use the ECDSA
 certificate chain ``ecdsa.pem`` or RSA certificate chain ``rsa.pem``
 for all requests.
 
-::
+.. code-block:: yaml
 
-    dest_ip=* ssl_cert_name=ecdsa.pem,rsa.pem
+    ssl_multicert:
+      - ssl_cert_name: ecdsa.pem,rsa.pem
+        dest_ip: "*"
 
 The following example configures Traffic Server to use the ECDSA
 certificate chain ``ecdsa.pem`` or RSA certificate chain ``rsa.pem``
@@ -183,35 +196,46 @@
 Note that the number of files in ssl_key_name must match the files in ssl_cert_name,
 and they should be presented in the same order.
 
-::
+.. code-block:: yaml
 
-    dest_ip=* ssl_cert_name=ecdsa_pub.pem,rsa_pub.pem ssl_key_name=ecdsa_private.pem,rsa_private.pem
+    ssl_multicert:
+      - ssl_cert_name: ecdsa_pub.pem,rsa_pub.pem
+        ssl_key_name: ecdsa_private.pem,rsa_private.pem
+        dest_ip: "*"
 
 The following example configures Traffic Server to use the SSL
 certificate ``server.pem`` and the private key ``serverKey.pem``
 for all requests to port 8443 on IP address 111.11.11.1. The
 ``general.pem`` certificate is used for server name matches.
 
-::
+.. code-block:: yaml
 
-     dest_ip=111.11.11.1:8443 ssl_cert_name=server.pem ssl_key_name=serverKey.pem ssl_cert_name=general.pem
+    ssl_multicert:
+      - ssl_cert_name: server.pem,general.pem
+        ssl_key_name: serverKey.pem
+        dest_ip: "111.11.11.1:8443"
 
 The following example configures Traffic Server to use the SSL
 certificate ``server.pem`` for all requests to the IP address
-111.11.11.1. Session tickets are enabled with a persistent ticket
-key.
+111.11.11.1. Session tickets are enabled.
 
-::
+.. code-block:: yaml
 
-    dest_ip=111.11.11.1 ssl_cert_name=server.pem ssl_ticket_enabled=1 ticket_key_name=ticket.key
+    ssl_multicert:
+      - ssl_cert_name: server.pem
+        dest_ip: "111.11.11.1"
+        ssl_ticket_enabled: 1
 
 The following example configures Traffic Server to use the SSL
 certificate ``server.pem`` and disable session tickets for all
 requests to the IP address 111.11.11.1.
 
-::
+.. code-block:: yaml
 
-    dest_ip=111.11.11.1 ssl_cert_name=server.pem ssl_ticket_enabled=0
+    ssl_multicert:
+      - ssl_cert_name: server.pem
+        dest_ip: "111.11.11.1"
+        ssl_ticket_enabled: 0
 
 The following examples configure Traffic Server to use the SSL
 certificate ``server.pem`` which includes an encrypted private key.
@@ -220,7 +244,27 @@
 and (ba r) in the second example, the program (mypass) will return the
 pass phrase to decrypt the keys.
 
-::
+.. code-block:: yaml
 
-    ssl_cert_name=server1.pem ssl_key_dialog="exec:/usr/bin/mypass foo"
-    ssl_cert_name=server2.pem ssl_key_dialog="exec:/usr/bin/mypass foo 'ba r'"
+    ssl_multicert:
+      - ssl_cert_name: server1.pem
+        ssl_key_dialog: "exec:/usr/bin/mypass foo"
+
+      - ssl_cert_name: server2.pem
+        ssl_key_dialog: "exec:/usr/bin/mypass foo 'ba r'"
+
+Migration from ssl_multicert.config
+===================================
+
+The ``traffic_ctl`` command can be used to convert pre-11.x ``ssl_multicert.config``
+files to the new YAML format:
+
+.. code-block:: bash
+
+    traffic_ctl config convert ssl_multicert ssl_multicert.config ssl_multicert.yaml
+
+To output to stdout instead of a file, use ``-`` as the output path:
+
+.. code-block:: bash
+
+    traffic_ctl config convert ssl_multicert ssl_multicert.config -
diff --git a/doc/admin-guide/logging/formatting.en.rst b/doc/admin-guide/logging/formatting.en.rst
index b85e640..83da79e 100644
--- a/doc/admin-guide/logging/formatting.en.rst
+++ b/doc/admin-guide/logging/formatting.en.rst
@@ -624,7 +624,6 @@
 .. _cscert:
 .. _cqssl:
 .. _cqssr:
-.. _cqssrt:
 .. _cqssv:
 .. _cqssc:
 .. _cqssu:
@@ -646,15 +645,9 @@
                       handshake. 0 otherwise.
 cqssl  Client Request SSL client request status indicates if this client
                       connection is over SSL.
-cqssr  Client Request SSL session resumption status; indicates whether the
-                      current request was resumed from a previous SSL session
-                      and avoided a full TLS handshake. Resumption may have
-                      been via a server side session cache or via a TLS session
-                      ticket, see cqssrt_ for the resumption type.
-cqssrt Client Request SSL resumption type; indicates the type of TLS session
-                      resumption used for this request. 0 for no resumption,
-                      1 for server session cache resumption, 2 for TLS session
-                      ticket resumption.
+cqssr  Client Request SSL session ticket reused status; indicates if the current
+                      request hit the SSL session ticket and avoided a full SSL
+                      handshake.
 cqssv  Client Request SSL version used to communicate with the client.
 cqssc  Client Request SSL Cipher used by |TS| to communicate with the client.
 cqssu  Client Request SSL Elliptic Curve used by |TS| to communicate with the
diff --git a/doc/admin-guide/performance/index.en.rst b/doc/admin-guide/performance/index.en.rst
index 461c6a9..33dccc4 100644
--- a/doc/admin-guide/performance/index.en.rst
+++ b/doc/admin-guide/performance/index.en.rst
@@ -405,9 +405,6 @@
 the remote OCSP responders, in seconds, with
 :ts:cv:`proxy.config.ssl.ocsp.request_timeout`.
 
-Lastly, you can control the number of seconds for which SSL sessions will be
-cached in |TS| using :ts:cv:`proxy.config.ssl.session_cache.timeout`.
-
 .. code-block:: yaml
 
    records:
@@ -416,8 +413,6 @@
        ocsp:
          cache_timeout: 3600
          request_timeout: 10
-       session_cache:
-         timeout: 0
 
 
 Transaction Activity Timeouts
@@ -528,8 +523,6 @@
 ~~~~~~~~~~~~~~~~~~~~
 
 :ts:cv:`proxy.config.ssl.max_record_size`
-:ts:cv:`proxy.config.ssl.session_cache.mode`
-:ts:cv:`proxy.config.ssl.session_cache.size`
 
 Thread Types
 ------------
diff --git a/doc/admin-guide/security/index.en.rst b/doc/admin-guide/security/index.en.rst
index 829392e..cec71bc 100644
--- a/doc/admin-guide/security/index.en.rst
+++ b/doc/admin-guide/security/index.en.rst
@@ -121,7 +121,7 @@
            private_key:
              path: /opt/ts/etc/ssl/keys
 
-#. Add an entry to :file:`ssl_multicert.config` for each certificate and key
+#. Add an entry to :file:`ssl_multicert.yaml` for each certificate and key
    which your |TS| system will be using to terminate SSL connections
    with clients. ::
 
@@ -302,7 +302,7 @@
 
 #. *Optional*: Delete the last ticket key from the ticket key file.
 
-#. Touch :file:`ssl_multicert.config` to indicate that the SSL configuration is stale.
+#. Touch :file:`ssl_multicert.yaml` to indicate that the SSL configuration is stale.
 
 #. Run the command :option:`traffic_ctl config reload` to apply the new ticket key.
 
@@ -335,7 +335,7 @@
                 CA Issuers - URI:http://cacerts.digicert.com/DigiCertSHA2SecureServerCA.crt
 
 |TS| can also use prefetched OCSP stapling responses if ssl_ocsp_name parameter
-is used in :file:`ssl_multicert.config`. Take into account that when using prefetched
+is used in :file:`ssl_multicert.yaml`. Take into account that when using prefetched
 OCSP stapling responses, |TS| will not refresh them and it should be done
 externally. This can be done using OpenSSL::
 
diff --git a/doc/admin-guide/security/mtls.en.rst b/doc/admin-guide/security/mtls.en.rst
index 6a85045..e602d3d 100644
--- a/doc/admin-guide/security/mtls.en.rst
+++ b/doc/admin-guide/security/mtls.en.rst
@@ -161,6 +161,6 @@
 
     traffic_ctl config reload
 
-If the contents of the certificate files change but the names of the files do not, you may need to touch ssl_multicert.config
+If the contents of the certificate files change but the names of the files do not, you may need to touch ssl_multicert.yaml
 (for server certs) and sni.yaml  (for client certs).
 
diff --git a/doc/admin-guide/tools/converting-records-to-yaml.en.rst b/doc/admin-guide/tools/converting-records-to-yaml.en.rst
index bb57ed2..dcc7af4 100644
--- a/doc/admin-guide/tools/converting-records-to-yaml.en.rst
+++ b/doc/admin-guide/tools/converting-records-to-yaml.en.rst
@@ -150,21 +150,20 @@
    $ python3 convert2yaml.py -f records.config -o records.yaml
    [████████████████████████████████████████] 494/494
 
-   ┌■ 8 Renamed records:
+   ┌■ 7 Renamed records:
    └┬──» #1 : proxy.config.output.logfile -> proxy.config.output.logfile.name
     ├──» #2 : proxy.config.exec_thread.autoconfig -> proxy.config.exec_thread.autoconfig.enabled
     ├──» #3 : proxy.config.hostdb -> proxy.config.hostdb.enabled
     ├──» #4 : proxy.config.tunnel.prewarm -> proxy.config.tunnel.prewarm.enabled
     ├──» #5 : proxy.config.ssl.TLSv1_3 -> proxy.config.ssl.TLSv1_3.enabled
     ├──» #6 : proxy.config.ssl.client.TLSv1_3 -> proxy.config.ssl.client.TLSv1_3.enabled
-    ├──» #7 : proxy.config.ssl.origin_session_cache -> proxy.config.ssl.origin_session_cache.enabled
-    └──» #8 : proxy.config.ssl.session_cache -> proxy.config.ssl.session_cache.mode
+    └──» #7 : proxy.config.ssl.origin_session_cache -> proxy.config.ssl.origin_session_cache.enabled
 
 
 There are a few things to note here:
 
 Line 2. A total of ``494`` from ``494`` records were converted.
-Line 4. A total of ``8`` records were renamed.
+Line 4. A total of ``7`` records were renamed.
 
 Example 2
 ---------
diff --git a/doc/appendices/command-line/traffic_ctl.en.rst b/doc/appendices/command-line/traffic_ctl.en.rst
index d116401..d8eea55 100644
--- a/doc/appendices/command-line/traffic_ctl.en.rst
+++ b/doc/appendices/command-line/traffic_ctl.en.rst
@@ -365,6 +365,35 @@
    Display information about the registered files in |TS|. This includes the full file path, config record name, parent config (if any)
    if needs root access and if the file is required in |TS|.
 
+.. program:: traffic_ctl config
+.. option:: ssl-multicert show [--yaml | --json]
+
+   Display the current ``ssl_multicert.yaml`` configuration. By default, output is in YAML format.
+   Use ``--json`` or ``-j`` to output in JSON format.
+
+   .. option:: --yaml, -y
+
+      Output in YAML format (default).
+
+   .. option:: --json, -j
+
+      Output in JSON format.
+
+   Example:
+
+   .. code-block:: bash
+
+      $ traffic_ctl config ssl-multicert show
+      ssl_multicert:
+        - ssl_cert_name: server.pem
+          dest_ip: "*"
+          ssl_key_name: server.key
+
+   .. code-block:: bash
+
+      $ traffic_ctl config ssl-multicert show --json
+      {"ssl_multicert": [{"ssl_cert_name": "server.pem", "dest_ip": "*", "ssl_key_name": "server.key"}]}
+
 .. _traffic-control-command-metric:
 
 traffic_ctl metric
diff --git a/doc/developer-guide/api/functions/TSSslContext.en.rst b/doc/developer-guide/api/functions/TSSslContext.en.rst
index 0837365..22f5ca9 100644
--- a/doc/developer-guide/api/functions/TSSslContext.en.rst
+++ b/doc/developer-guide/api/functions/TSSslContext.en.rst
@@ -38,11 +38,11 @@
 ===========
 
 :func:`TSSslContextFindByName` searches for a SSL server context
-created from :file:`ssl_multicert.config`, matching against the
+created from :file:`ssl_multicert.yaml`, matching against the
 server :arg:`name`.
 
 :func:`TSSslContextFindByAddr` searches for a SSL server context
-created from :file:`ssl_multicert.config` matching against the server
+created from :file:`ssl_multicert.yaml` matching against the server
 :arg:`address`.
 
 
diff --git a/doc/developer-guide/api/functions/TSSslServerCertUpdate.en.rst b/doc/developer-guide/api/functions/TSSslServerCertUpdate.en.rst
index 6ee1f65..15847d9 100644
--- a/doc/developer-guide/api/functions/TSSslServerCertUpdate.en.rst
+++ b/doc/developer-guide/api/functions/TSSslServerCertUpdate.en.rst
@@ -36,7 +36,7 @@
 ===========
 
 :func:`TSSslServerCertUpdate` updates existing server certificates configured in
-:file:`ssl_multicert.config` based on the common name in :arg:`cert_path`. if :arg:`key_path` is set
+:file:`ssl_multicert.yaml` based on the common name in :arg:`cert_path`. if :arg:`key_path` is set
 to nullptr, the function will use :arg:`cert_path` for both certificate and private key.
 :func:`TSSslServerCertUpdate` returns `TS_SUCCESS` only if there exists such a mapping,
 :arg:`cert_path` is a valid cert, and the context is updated to use that cert.
diff --git a/doc/developer-guide/api/functions/TSSslSession.en.rst b/doc/developer-guide/api/functions/TSSslSession.en.rst
index 07a29d6..2a2ed62 100644
--- a/doc/developer-guide/api/functions/TSSslSession.en.rst
+++ b/doc/developer-guide/api/functions/TSSslSession.en.rst
@@ -18,8 +18,8 @@
 .. include:: ../../../common.defs
 .. default-domain:: cpp
 
-TSSslSession
-************
+TSSslTicketKeyUpdate
+********************
 
 Synopsis
 ========
@@ -28,47 +28,20 @@
 
     #include <ts/ts.h>
 
-.. function:: TSSslSession TSSslSessionGet(const TSSslSessionID * sessionid)
-.. function:: int TSSslSessionGetBuffer(const TSSslSessionID * sessionid, char * buffer, int * len_ptr)
-.. function:: TSReturnCode TSSslSessionInsert(const TSSslSessionID * sessionid, TSSslSession addSession, TSSslConnection ssl_conn)
-.. function:: TSReturnCode TSSslSessionRemove(const TSSslSessionID * sessionid)
-.. function:: void TSSslTicketKeyUpdate(char * ticketData, int ticketDataLength)
+.. function:: TSReturnCode TSSslTicketKeyUpdate(char * ticketData, int ticketDataLength)
 
 Description
 ===========
 
-These functions work with the internal ATS session cache.  These functions are only useful if the ATS internal
-session cache is enabled by setting :ts:cv:`proxy.config.ssl.session_cache.mode` has been set to 2.
+.. note::
 
-These functions tend to be used with the :enumerator:`TS_SSL_SESSION_HOOK`.
+   The session ID-based session cache and its associated APIs (``TSSslSessionGet``, ``TSSslSessionGetBuffer``,
+   ``TSSslSessionInsert``, ``TSSslSessionRemove``) were removed in ATS 11.x.
+   TLS session resumption is now only supported via session tickets.
 
-The functions work with the :type:`TSSslSessionID` object to identify sessions to retrieve, insert, or delete.
+:func:`TSSslTicketKeyUpdate` updates the running ATS process to use a new set of Session Ticket Encryption keys.
+This behaves the same way as updating the session ticket encrypt key file with new data and reloading the
+current ATS process. However, this API does not require writing session ticket encryption keys to disk.
 
-The functions also work with the :type:`TSSslSession` object which can be cast to a pointer to the OpenSSL SSL_SESSION object.
-
-These functions perform the appropriate locking on the session cache to avoid errors.
-
-The :func:`TSSslSessionGet` and :func:`TSSslSessionGetBuffer` functions retrieve the :type:`TSSslSession` object that is identified by the
-:type:`TSSslSessionID` object.  If there is no matching session object, :func:`TSSslSessionGet` returns nullptr and :func:`TSSslSessionGetBuffer`
-returns 0.
-
-:func:`TSSslSessionGetBuffer` returns the session information serialized in a buffer that can be shared between processes.
-When the function is called len_ptr should point to the amount of space
-available in the buffer parameter.  The function returns the amount of data really needed to encode the session.  len_ptr is
-updated with the amount of data actually stored in the buffer.
-:func:`TSSslSessionGetBuffer` will not overrun the provided buffer, but the caller should ensure that the data's size was not larger
-than the buffer by comparing the returned value with the value of len_ptr. If the returned value is larger than the buffer size,
-then the session data did not fit in the buffer and the session data stored in the buffer output variable should not be used.
-
-
-:func:`TSSslSessionInsert` inserts the session specified by the addSession parameter into the ATS session cache under the sessionid key.
-If there is already an entry in the cache for the session id key, it is first removed before the new entry is added.
-
-:func:`TSSslSessionRemove` removes the session entry from the session cache that is keyed by sessionid.
-
-:func:`TSSslTicketKeyUpdate` updates the running ATS process to use a new set of Session Ticket Encryption keys.  This behaves the same way as
-updating the session ticket encrypt key file with new data and reloading the current ATS process.  However, this API does not
-require writing session ticket encryption keys to disk.
-
-If both the ticket key files and :func:`TSSslTicketKeyUpdate` are used to update session ticket encryption keys, ATS will use the
-most recent update regardless if whether it was made by file and configuration reload or API.
+If both the ticket key files and :func:`TSSslTicketKeyUpdate` are used to update session ticket encryption keys,
+ATS will use the most recent update regardless of whether it was made by file and configuration reload or API.
diff --git a/doc/developer-guide/plugins/hooks-and-transactions/ssl-hooks.en.rst b/doc/developer-guide/plugins/hooks-and-transactions/ssl-hooks.en.rst
index e3cdd5a..65fd199 100644
--- a/doc/developer-guide/plugins/hooks-and-transactions/ssl-hooks.en.rst
+++ b/doc/developer-guide/plugins/hooks-and-transactions/ssl-hooks.en.rst
@@ -84,7 +84,7 @@
 This hook is called if the client provides SNI information in the SSL handshake. If called it will
 always be called after TS_VCONN_START_HOOK.
 
-The Traffic Server core first evaluates the settings in the ssl_multicert.config file based on the
+The Traffic Server core first evaluates the settings in the ssl_multicert.yaml file based on the
 server name. Then the core SNI callback executes the plugin registered SNI callback code. The plugin
 callback can access the servername by calling the OpenSSL function SSL_get_servername().
 
diff --git a/doc/developer-guide/plugins/hooks-and-transactions/ssl-session-api.en.rst b/doc/developer-guide/plugins/hooks-and-transactions/ssl-session-api.en.rst
index f85ed86..51641d7 100644
--- a/doc/developer-guide/plugins/hooks-and-transactions/ssl-session-api.en.rst
+++ b/doc/developer-guide/plugins/hooks-and-transactions/ssl-session-api.en.rst
@@ -21,62 +21,28 @@
 
 .. default-domain:: cpp
 
-TLS Session Plugin API
-**********************
+TLS Session Ticket Key Plugin API
+**********************************
 
-These interfaces enable a plugin to hook into operations on the ATS TLS session cache.  ATS also provides API's
-to enable the plugin to update the session cache based on outside information, e.g. peer servers.
+This interface enables a plugin to update the session ticket encryption keys used for TLS session resumption.
 
-.. enumerator:: TS_SSL_SESSION_HOOK
+.. note::
 
-This hook is invoked when a change has been made to the ATS session cache or a session has been accessed
-from ATS via OpenSSL.  These hooks are only activated if the ATS implementation of the session cache is in
-use.  This means :ts:cv:`proxy.config.ssl.session_cache.mode` has been set to 2.
-
-The hook callback has the following signature
-
-.. function:: int SSL_session_callback(TSCont contp, TSEvent event, void * edata)
-
-The edata parameter is a pointer to a :type:`TSSslSessionID`.
-
-This callback in synchronous since the underlying OpenSSL callback is unable to pause processing.
-
-The following events can be sent to this callback
-
-.. c:enumerator:: TS_EVENT_SSL_SESSION_NEW
-
-   Sent after a new session has been inserted into the SSL session cache.  The plugin can call :func:`TSSslSessionGet` to retrieve the actual session object.  The plugin could communicate information about the new session to other processes or update additional logging or statistics.
-
-.. c:enumerator:: TS_EVENT_SSL_SESSION_GET
-
-   Sent after a session has been fetched from the SSL session cache by a client request.  The plugin could update additional logging and statistics.
-
-.. c:enumerator:: TS_EVENT_SSL_SESSION_REMOVE
-
-   Sent after a session has been removed from the SSL session cache.  The plugin could communication information about the session removal to other processes or update additional logging and statistics.
+   The session ID-based session cache and its associated APIs (``TSSslSessionGet``, ``TSSslSessionGetBuffer``,
+   ``TSSslSessionInsert``, ``TSSslSessionRemove``, and ``TS_SSL_SESSION_HOOK``) were removed in ATS 11.x.
+   TLS session resumption is now only supported via session tickets.
 
 Utility Functions
-******************
+*****************
 
-A number of API functions will likely be used with this hook.
-
-* :func:`TSSslSessionGet`
-* :func:`TSSslSessionGetBuffer`
-* :func:`TSSslSessionInsert`
-* :func:`TSSslSessionRemove`
 * :func:`TSSslTicketKeyUpdate`
 
 Example Use Case
 ****************
 
-Consider deploying a set of ATS servers as a farm behind a layer 4 load balancer.  The load balancer does not
-guarantee that all the requests from a single client are directed to the same ATS box.  Therefore, to maximize TLS session
-reuse, the servers should share session state via some external communication library like redis or rabbitmq.
+Consider deploying a set of ATS servers as a farm behind a layer 4 load balancer. To enable TLS session
+ticket-based resumption across all servers, they need to share the same session ticket encryption keys.
 
-To do this, they write a plugin that sets the :enumerator:`TS_SSL_SESSION_HOOK`.  When the hook is triggered, the plugin function sends the
-updated session state to the other ATS servers via the communication library.
-
-The plugin also has thread that listens for updates and calls :func:`TSSslSessionInsert` and :func:`TSSslSessionRemove` to update the local session cache accordingly.
-
-The plugin can also engage in a protocol to periodically update the session ticket encryption key and communicate the new key to its
-peers.  The plugin calls :func:`TSSslTicketKeyUpdate` to update the local ATS process with the newest keys and the last N keys.
+A plugin can engage in a protocol to periodically update the session ticket encryption key and communicate
+the new key to its peers. The plugin calls :func:`TSSslTicketKeyUpdate` to update the local ATS process
+with the newest keys and the last N keys.
diff --git a/doc/getting-started/index.en.rst b/doc/getting-started/index.en.rst
index c9e8026..83f9160 100644
--- a/doc/getting-started/index.en.rst
+++ b/doc/getting-started/index.en.rst
@@ -342,11 +342,15 @@
     map https://www.acme.com/ http://localhost:8080/
     map https://static.acme.com/ https://origin-static.acme.com/
 
-This will require installing a certificate, and adding a line to
-:file:`ssl_multicert.config`. Assuming the cert has the static.acme.com alternate
-name, and that cert should be presented by default::
+This will require installing a certificate, and adding an entry to
+:file:`ssl_multicert.yaml`. Assuming the cert has the static.acme.com alternate
+name, and that cert should be presented by default:
 
-    dest_ip=* ssl_cert_name=/path/to/secret/privatekey/acme.rsa
+.. code-block:: yaml
+
+    ssl_multicert:
+      - ssl_cert_name: /path/to/secret/privatekey/acme.rsa
+        dest_ip: "*"
 
 Further information about configuring |TS| for TLS can be found :ref:`admin-ssl-termination`
 section of the documentation.
@@ -405,9 +409,12 @@
 
     /cache/trafficserver 500G
 
-:file:`ssl_multicert.config`::
+:file:`ssl_multicert.yaml`:
 
-    ssl_cert_name=/path/to/secret/acme.rsa
+.. code-block:: yaml
+
+    ssl_multicert:
+      - ssl_cert_name: /path/to/secret/acme.rsa
 
 Configuring A Forward Proxy
 ---------------------------
diff --git a/doc/manpages.cmake.in.py b/doc/manpages.cmake.in.py
index 7be62e7..09a289b 100644
--- a/doc/manpages.cmake.in.py
+++ b/doc/manpages.cmake.in.py
@@ -54,8 +54,8 @@
     ('admin-guide/files/sni.yaml.en', 'sni.yaml', u'Traffic Server sni rules configuration file', None, '5'),
     ('admin-guide/files/splitdns.config.en', 'splitdns.config', u'Traffic Server split DNS configuration file', None, '5'),
     (
-        'admin-guide/files/ssl_multicert.config.en', 'ssl_multicert.config', u'Traffic Server SSL certificate configuration file',
-        None, '5'),
+        'admin-guide/files/ssl_multicert.yaml.en', 'ssl_multicert.yaml', u'Traffic Server SSL certificate configuration file', None,
+        '5'),
     ('admin-guide/files/storage.config.en', 'storage.config', u'Traffic Server cache storage configuration file', None, '5'),
     ('admin-guide/files/strategies.yaml.en', 'strategies.yaml', u'Traffic Server cache hierarchy configuration file', None, '5'),
     ('admin-guide/files/volume.config.en', 'volume.config', u'Traffic Server cache volume configuration file', None, '5'),
diff --git a/doc/manpages.py b/doc/manpages.py
index 6ad2d24..f1b5562 100644
--- a/doc/manpages.py
+++ b/doc/manpages.py
@@ -52,8 +52,8 @@
     ('admin-guide/files/sni.yaml.en', 'sni.yaml', u'Traffic Server sni rules configuration file', None, '5'),
     ('admin-guide/files/splitdns.config.en', 'splitdns.config', u'Traffic Server split DNS configuration file', None, '5'),
     (
-        'admin-guide/files/ssl_multicert.config.en', 'ssl_multicert.config', u'Traffic Server SSL certificate configuration file',
-        None, '5'),
+        'admin-guide/files/ssl_multicert.yaml.en', 'ssl_multicert.yaml', u'Traffic Server SSL certificate configuration file', None,
+        '5'),
     ('admin-guide/files/storage.config.en', 'storage.config', u'Traffic Server cache storage configuration file', None, '5'),
     ('admin-guide/files/strategies.yaml.en', 'strategies.yaml', u'Traffic Server cache hierarchy configuration file', None, '5'),
     ('admin-guide/files/volume.config.en', 'volume.config', u'Traffic Server cache volume configuration file', None, '5'),
diff --git a/doc/release-notes/upgrading.en.rst b/doc/release-notes/upgrading.en.rst
index 90df999..4ce2609 100644
--- a/doc/release-notes/upgrading.en.rst
+++ b/doc/release-notes/upgrading.en.rst
@@ -172,7 +172,6 @@
 - The records.yaml entry ``proxy.config.exec_thread.autoconfig`` has been renamed to :ts:cv:`proxy.config.exec_thread.autoconfig.enabled`.
 - The records.yaml entry ``proxy.config.tunnel.prewarm`` has been renamed to :ts:cv:`proxy.config.tunnel.prewarm.enabled`.
 - The records.yaml entry ``proxy.config.ssl.origin_session_cache`` has been renamed to :ts:cv:`proxy.config.ssl.origin_session_cache.enabled`.
-- The records.yaml entry ``proxy.config.ssl.session_cache`` has been renamed to :ts:cv:`proxy.config.ssl.session_cache.mode`.
 - The records.yaml entry ``proxy.config.ssl.TLSv1_3`` has been renamed to :ts:cv:`proxy.config.ssl.TLSv1_3.enabled`.
 - The records.yaml entry ``proxy.config.ssl.client.TLSv1_3`` has been renamed to :ts:cv:`proxy.config.ssl.client.TLSv1_3.enabled`.
 - The records.yaml entry :ts:cv:`proxy.config.allocator.iobuf_chunk_sizes` has been added
@@ -182,6 +181,18 @@
   system.
 - The records.yaml entry ``proxy.config.plugin.compiler_path`` has been added to specify an optional compiler tool path for compiling plugins.
 
+The ``ssl_multicert.config`` file has been replaced with
+:file:`ssl_multicert.yaml`. The new file uses YAML format with a top-level
+``ssl_multicert`` key containing a sequence of certificate entries. Use
+``traffic_ctl config convert ssl_multicert`` to convert existing
+configuration files:
+
+.. code-block:: bash
+
+    traffic_ctl config convert ssl_multicert ssl_multicert.config ssl_multicert.yaml
+
+See :file:`ssl_multicert.yaml` for the new format documentation.
+
 The following changes have been made to the :file:`sni.yaml` file:
 
 - ``disable_h2`` has been removed. Use ``http2`` with :code:`off` instead.
diff --git a/doc/release-notes/whats-new.en.rst b/doc/release-notes/whats-new.en.rst
index 0c64321..9e8f813 100644
--- a/doc/release-notes/whats-new.en.rst
+++ b/doc/release-notes/whats-new.en.rst
@@ -108,17 +108,8 @@
 
 * Added :ts:cv:`proxy.config.http.negative_revalidating_list` to configure the
   list of status codes that apply to the negative revalidating feature
-* Added :ts:cv:`proxy.config.ssl.session_cache.mode` to control TLS session caching.
-  This is intended to replace the legacy :ts:cv:`proxy.config.ssl.session_cache.enabled` and
-  ``proxy.config.ssl.session_cache.value`` configurations. The
-  :ts:cv:`proxy.config.ssl.session_cache.enabled` setting was documented but
-  never implemented, while ``proxy.config.ssl.session_cache.value`` was
-  implemented but not documented. The new :ts:cv:`proxy.config.ssl.session_cache.mode`
-  functions just like the legacy ``proxy.config.ssl.session_cache.value`` did
-  in the ealier 10.0 release. The :ts:cv:`proxy.config.ssl.session_cache.mode`
-  setting provides a clear and consistent interface going forward.  For backward
-  compatibility, ``.enabled`` is now implemented, but both ``.enabled`` and
-  ``.value`` will be removed in ATS 11.x.
+* The ``ssl_multicert.config`` file has been replaced with :file:`ssl_multicert.yaml`.
+  Use ``traffic_ctl config convert ssl_multicert`` to convert existing configuration files.
 
 
 
diff --git a/include/config/config_result.h b/include/config/config_result.h
new file mode 100644
index 0000000..74283de
--- /dev/null
+++ b/include/config/config_result.h
@@ -0,0 +1,77 @@
+/** @file
+
+  Generic configuration parsing result type.
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+*/
+
+#pragma once
+
+#include "swoc/Errata.h"
+
+namespace config
+{
+
+/**
+ * Result of a configuration parse operation.
+ *
+ * This template is the standard return type for all configuration parsers in
+ * the config library. It bundles a parsed configuration value with an errata
+ * that may contain warnings or errors encountered during parsing.
+ *
+ * Design rationale:
+ * - Parsers can return partial results (value populated) even when warnings
+ *   are present, allowing callers to decide how to handle degraded configs.
+ * - The ok() method checks whether parsing succeeded without errors, but
+ *   callers should also inspect the errata for warnings.
+ * - This type is reused across all configuration file types (ssl_multicert,
+ *   etc.) to provide a consistent API.
+ *
+ * Example usage:
+ * @code
+ *   config::SSLMultiCertParser parser;
+ *   auto result = parser.parse("/path/to/ssl_multicert.yaml");
+ *   if (!result.ok()) {
+ *       // Handle error
+ *       return;
+ *   }
+ *   for (const auto& entry : result.value) {
+ *       // Use parsed entries
+ *   }
+ * @endcode
+ *
+ * @tparam T The configuration type (e.g., SSLMultiCertConfig).
+ */
+template <typename T> struct ConfigResult {
+  T            value;  ///< The parsed configuration value.
+  swoc::Errata errata; ///< Errors or warnings from parsing.
+
+  /**
+   * Check if parsing succeeded without errors.
+   *
+   * @return true if no errors occurred, false otherwise.
+   */
+  bool
+  ok() const
+  {
+    return errata.is_ok();
+  }
+};
+
+} // namespace config
diff --git a/include/config/ssl_multicert.h b/include/config/ssl_multicert.h
new file mode 100644
index 0000000..b7984eb
--- /dev/null
+++ b/include/config/ssl_multicert.h
@@ -0,0 +1,128 @@
+/** @file
+
+  SSL Multi-Certificate configuration parsing and marshalling.
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+*/
+
+#pragma once
+
+#include <optional>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "config/config_result.h"
+
+namespace config
+{
+
+/**
+ * Represents a single certificate entry in ssl_multicert configuration.
+ */
+struct SSLMultiCertEntry {
+  std::string        ssl_cert_name;      ///< Certificate file name (required unless action is tunnel).
+  std::string        dest_ip{"*"};       ///< IP address to match (default "*").
+  std::string        ssl_key_name;       ///< Private key file name (optional).
+  std::string        ssl_ca_name;        ///< CA certificate file name (optional).
+  std::string        ssl_ocsp_name;      ///< OCSP response file name (optional).
+  std::string        ssl_key_dialog;     ///< Passphrase dialog method (optional).
+  std::string        dest_fqdn;          ///< Destination FQDN (optional).
+  std::string        action;             ///< Action (e.g., "tunnel").
+  std::optional<int> ssl_ticket_enabled; ///< Session ticket enabled (optional).
+  std::optional<int> ssl_ticket_number;  ///< Number of session tickets (optional).
+};
+
+/// A configuration is a vector of certificate entries.
+using SSLMultiCertConfig = std::vector<SSLMultiCertEntry>;
+
+/**
+ * Parser for ssl_multicert configuration files.
+ *
+ * Supports both YAML (.yaml) and legacy (.config) formats with automatic
+ * format detection.
+ */
+class SSLMultiCertParser
+{
+public:
+  /**
+   * Parse an ssl_multicert configuration file.
+   *
+   * The format is auto-detected based on file extension and content.
+   *
+   * @param[in] filename Path to the configuration file.
+   * @return ConfigResult containing the parsed entries or errors.
+   */
+  ConfigResult<SSLMultiCertConfig> parse(std::string const &filename);
+
+private:
+  enum class Format { YAML, Legacy };
+
+  /**
+   * Detect the configuration format from content and filename.
+   *
+   * @param[in] content The configuration content.
+   * @param[in] filename The filename (used for extension-based detection).
+   * @return The detected format.
+   */
+  Format detect_format(std::string_view content, std::string const &filename);
+
+  /**
+   * Parse YAML-formatted configuration content.
+   *
+   * @param[in] content The YAML content.
+   * @return ConfigResult containing the parsed entries or errors.
+   */
+  ConfigResult<SSLMultiCertConfig> parse_yaml(std::string_view content);
+
+  /**
+   * Parse legacy (.config) formatted configuration content.
+   *
+   * @param[in] content The legacy config content.
+   * @return ConfigResult containing the parsed entries or errors.
+   */
+  ConfigResult<SSLMultiCertConfig> parse_legacy(std::string_view content);
+};
+
+/**
+ * Marshaller for ssl_multicert configuration.
+ *
+ * Serializes configuration to YAML or JSON format.
+ */
+class SSLMultiCertMarshaller
+{
+public:
+  /**
+   * Serialize configuration to YAML format.
+   *
+   * @param[in] config The configuration to serialize.
+   * @return YAML string representation.
+   */
+  std::string to_yaml(SSLMultiCertConfig const &config);
+
+  /**
+   * Serialize configuration to JSON format.
+   *
+   * @param[in] config The configuration to serialize.
+   * @return JSON string representation.
+   */
+  std::string to_json(SSLMultiCertConfig const &config);
+};
+
+} // namespace config
diff --git a/include/iocore/net/SSLMultiCertConfigLoader.h b/include/iocore/net/SSLMultiCertConfigLoader.h
index 42187b8..b2b7fc2 100644
--- a/include/iocore/net/SSLMultiCertConfigLoader.h
+++ b/include/iocore/net/SSLMultiCertConfigLoader.h
@@ -39,7 +39,7 @@
 struct SSLLoadingContext;
 
 /**
-    @brief Load SSL certificates from ssl_multicert.config and setup SSLCertLookup for SSLCertificateConfig
+    @brief Load SSL certificates from ssl_multicert.yaml and setup SSLCertLookup for SSLCertificateConfig
  */
 class SSLMultiCertConfigLoader
 {
diff --git a/include/iocore/net/TLSSessionResumptionSupport.h b/include/iocore/net/TLSSessionResumptionSupport.h
index 80c25d9..38eb5d4 100644
--- a/include/iocore/net/TLSSessionResumptionSupport.h
+++ b/include/iocore/net/TLSSessionResumptionSupport.h
@@ -77,19 +77,6 @@
   // ---------------------------------------------------------------------------
   // TLS Session Resumption Support Via Server Session Caching
   // ---------------------------------------------------------------------------
-
-  /** Retrieves a cached SSL session from the session cache.
-   *
-   * This function is used to retrieve a cached SSL session from the session cache.
-   *
-   * @param[in]  ssl         The SSL connection object.
-   * @param[in]  id          The session ID to lookup.
-   * @param[in]  len         The length of the session ID.
-   * @param[out] copy        Pointer to an integer indicating if the session ID should be copied.
-   * @return                A pointer to the cached SSL session, or nullptr if not found.
-   */
-  SSL_SESSION *getSession(SSL *ssl, const unsigned char *id, int len, int *copy);
-
   /**
    * @brief Retrieves a cached SSL session from the origin session cache.
    *
diff --git a/include/proxy/logging/LogAccess.h b/include/proxy/logging/LogAccess.h
index 7cdf8ed..4ab8fc0 100644
--- a/include/proxy/logging/LogAccess.h
+++ b/include/proxy/logging/LogAccess.h
@@ -149,7 +149,6 @@
   int marshal_client_req_tcp_reused(char *);         // INT
   int marshal_client_req_is_ssl(char *);             // INT
   int marshal_client_req_ssl_reused(char *);         // INT
-  int marshal_client_ssl_resumption_type(char *);    // INT
   int marshal_client_req_is_internal(char *);        // INT
   int marshal_client_req_mptcp_state(char *);        // INT
   int marshal_client_security_protocol(char *);      // STR
diff --git a/include/ts/ts.h b/include/ts/ts.h
index d62d5d8..00f2c70 100644
--- a/include/ts/ts.h
+++ b/include/ts/ts.h
@@ -1334,11 +1334,6 @@
 int         TSVConnProvidedSslCert(TSVConn sslp);
 const char *TSVConnSslSniGet(TSVConn sslp, int *length);
 
-TSSslSession TSSslSessionGet(const TSSslSessionID *session_id);
-int          TSSslSessionGetBuffer(const TSSslSessionID *session_id, char *buffer, int *len_ptr);
-TSReturnCode TSSslSessionInsert(const TSSslSessionID *session_id, TSSslSession add_session, TSSslConnection ssl_conn);
-TSReturnCode TSSslSessionRemove(const TSSslSessionID *session_id);
-
 /* --------------------------------------------------------------------------
    HTTP transactions */
 void      TSHttpTxnHookAdd(TSHttpTxn txnp, TSHttpHookID id, TSCont contp);
diff --git a/include/tscore/Filenames.h b/include/tscore/Filenames.h
index 90ae048..716f9e5 100644
--- a/include/tscore/Filenames.h
+++ b/include/tscore/Filenames.h
@@ -39,7 +39,7 @@
   constexpr const char *SOCKS         = "socks.config";
   constexpr const char *PARENT        = "parent.config";
   constexpr const char *REMAP         = "remap.config";
-  constexpr const char *SSL_MULTICERT = "ssl_multicert.config";
+  constexpr const char *SSL_MULTICERT = "ssl_multicert.yaml";
   constexpr const char *SPLITDNS      = "splitdns.config";
   constexpr const char *SNI           = "sni.yaml";
   constexpr const char *JSONRPC       = "jsonrpc.yaml";
diff --git a/src/api/InkAPI.cc b/src/api/InkAPI.cc
index 462d15c..5a3af11 100644
--- a/src/api/InkAPI.cc
+++ b/src/api/InkAPI.cc
@@ -136,8 +136,6 @@
 /* From proxy/http/HttpProxyServerMain.c: */
 extern bool ssl_register_protocol(const char *, Continuation *);
 
-extern SSLSessionCache *session_cache; // declared extern in P_SSLConfig.h
-
 // External converters.
 extern MgmtConverter const &HttpDownServerCacheTimeConv;
 
@@ -8431,61 +8429,6 @@
   return TS_SUCCESS;
 }
 
-TSSslSession
-TSSslSessionGet(const TSSslSessionID *session_id)
-{
-  SSL_SESSION *session = nullptr;
-  if (session_id && session_cache) {
-    session_cache->getSession(reinterpret_cast<const SSLSessionID &>(*session_id), &session, nullptr);
-  }
-  return reinterpret_cast<TSSslSession>(session);
-}
-
-int
-TSSslSessionGetBuffer(const TSSslSessionID *session_id, char *buffer, int *len_ptr)
-{
-  int true_len = 0;
-  // Don't get if there is no session id or the cache is not yet set up
-  if (session_id && session_cache && len_ptr) {
-    true_len = session_cache->getSessionBuffer(reinterpret_cast<const SSLSessionID &>(*session_id), buffer, *len_ptr);
-  }
-  return true_len;
-}
-
-TSReturnCode
-TSSslSessionInsert(const TSSslSessionID *session_id, TSSslSession add_session, TSSslConnection ssl_conn)
-{
-  // Don't insert if there is no session id or the cache is not yet set up
-  if (session_id && session_cache) {
-    if (dbg_ctl_ssl_session_cache_insert.on()) {
-      const SSLSessionID *sid = reinterpret_cast<const SSLSessionID *>(session_id);
-      char                buf[sid->len * 2 + 1];
-      sid->toString(buf, sizeof(buf));
-      DbgPrint(dbg_ctl_ssl_session_cache_insert, "TSSslSessionInsert: Inserting session '%s' ", buf);
-    }
-    SSL_SESSION *session = reinterpret_cast<SSL_SESSION *>(add_session);
-    SSL         *ssl     = reinterpret_cast<SSL *>(ssl_conn);
-    session_cache->insertSession(reinterpret_cast<const SSLSessionID &>(*session_id), session, ssl);
-    // insertSession returns void, assume all went well
-    return TS_SUCCESS;
-  } else {
-    return TS_ERROR;
-  }
-}
-
-TSReturnCode
-TSSslSessionRemove(const TSSslSessionID *session_id)
-{
-  // Don't remove if there is no session id or the cache is not yet set up
-  if (session_id && session_cache) {
-    session_cache->removeSession(reinterpret_cast<const SSLSessionID &>(*session_id));
-    // removeSession returns void, assume all went well
-    return TS_SUCCESS;
-  } else {
-    return TS_ERROR;
-  }
-}
-
 // APIs for managing and using UUIDs.
 TSUuid
 TSUuidCreate()
diff --git a/src/config/CMakeLists.txt b/src/config/CMakeLists.txt
new file mode 100644
index 0000000..141a7f1
--- /dev/null
+++ b/src/config/CMakeLists.txt
@@ -0,0 +1,51 @@
+#######################
+#
+#  Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+#  agreements.  See the NOTICE file distributed with this work for additional information regarding
+#  copyright ownership.  The ASF licenses this file to you under the Apache License, Version 2.0
+#  (the "License"); you may not use this file except in compliance with the License.  You may obtain
+#  a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software distributed under the License
+#  is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+#  or implied. See the License for the specific language governing permissions and limitations under
+#  the License.
+#
+#######################
+
+set(CONFIG_PUBLIC_HEADERS ${PROJECT_SOURCE_DIR}/include/config/config_result.h
+                          ${PROJECT_SOURCE_DIR}/include/config/ssl_multicert.h
+)
+
+add_library(tsconfig ssl_multicert.cc)
+
+add_library(ts::config ALIAS tsconfig)
+
+set_target_properties(tsconfig PROPERTIES POSITION_INDEPENDENT_CODE TRUE PUBLIC_HEADER "${CONFIG_PUBLIC_HEADERS}")
+
+target_include_directories(tsconfig PUBLIC ${PROJECT_SOURCE_DIR}/include)
+
+target_link_libraries(tsconfig PUBLIC libswoc::libswoc yaml-cpp::yaml-cpp)
+
+if(BUILD_SHARED_LIBS)
+  install(
+    TARGETS tsconfig
+    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+    PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/config
+  )
+else()
+  install(FILES ${CONFIG_PUBLIC_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/config)
+endif()
+
+clang_tidy_check(tsconfig)
+
+if(BUILD_TESTING)
+  add_executable(test_tsconfig unit_tests/test_ssl_multicert.cc)
+
+  target_link_libraries(test_tsconfig PRIVATE tsconfig Catch2::Catch2WithMain)
+
+  add_catch2_test(NAME test_tsconfig COMMAND $<TARGET_FILE:test_tsconfig>)
+endif()
diff --git a/src/config/README.md b/src/config/README.md
new file mode 100644
index 0000000..9360062
--- /dev/null
+++ b/src/config/README.md
@@ -0,0 +1,122 @@
+# Configuration Parsing Library
+
+This directory contains a shared configuration parsing and marshalling framework
+used by various ATS components including `traffic_server` and `traffic_ctl`.
+
+## Architecture Overview
+
+Each configuration file type (ssl_multicert, etc.) follows the same pattern:
+
+```
+include/config/<config_name>.h   - Header with data types, parser, marshaller
+src/config/<config_name>.cc      - Implementation
+```
+
+### Components
+
+For each configuration type, there are three main components:
+
+1. **Entry struct** - A POD (plain old data) type representing a single
+   configuration entry (e.g., `SSLMultiCertEntry`).
+
+2. **Parser class** - Reads configuration files and returns parsed entries.
+   Supports both YAML and legacy formats with automatic format detection.
+
+3. **Marshaller class** - Serializes configuration to YAML or JSON format.
+
+### Example: ssl_multicert
+
+```
+include/config/ssl_multicert.h
+├── SSLMultiCertEntry      - Single certificate entry
+├── SSLMultiCertConfig     - Vector of entries (type alias)
+├── ConfigResult<T>        - Parse result with error handling
+├── SSLMultiCertParser     - Parser for .yaml and .config formats
+└── SSLMultiCertMarshaller - Serializer to YAML/JSON
+```
+
+## Usage
+
+### Parsing a Configuration File
+
+```cpp
+#include "config/ssl_multicert.h"
+
+config::SSLMultiCertParser parser;
+auto result = parser.parse("/path/to/ssl_multicert.yaml");
+
+if (!result.ok()) {
+    // Handle error - result.errata contains error details
+    return;
+}
+
+for (const auto& entry : result.value) {
+    // Use entry.ssl_cert_name, entry.dest_ip, etc.
+}
+```
+
+### Marshalling to YAML/JSON
+
+```cpp
+config::SSLMultiCertMarshaller marshaller;
+std::string yaml = marshaller.to_yaml(result.value);
+std::string json = marshaller.to_json(result.value);
+```
+
+## Format Detection
+
+The parser automatically detects the configuration format:
+
+1. **By file extension**: `.yaml`/`.yml` → YAML, `.config` → Legacy
+2. **By content inspection**: Looks for `ssl_multicert:` (YAML) vs `key=value` (Legacy)
+
+## Adding a New Configuration Type
+
+To add support for a new configuration (maybe, `logging.yaml`):
+
+1. Create `include/config/logging.h`:
+   - Define `LoggingEntry` struct with fields matching the config
+   - Define `LoggingConfig = std::vector<LoggingEntry>`
+   - Declare `LoggingParser` and `LoggingMarshaller` classes
+
+2. Create `src/config/logging.cc`:
+   - Implement YAML parsing using yaml-cpp
+   - Implement legacy format parsing if applicable
+   - Implement YAML and JSON marshalling
+
+3. Update `src/config/CMakeLists.txt`:
+   - Add `logging.cc` to the library sources
+   - Add header to `CONFIG_PUBLIC_HEADERS`
+
+4. Integrate with consumers (traffic_ctl, traffic_server, etc.)
+
+## Error Handling
+
+All parsers return `ConfigResult<T>` which contains:
+
+- `value` - The parsed configuration (valid even on partial failure)
+- `errata` - Error/warning information using `swoc::Errata`
+- `ok()` - Returns true if no errors occurred
+
+```cpp
+if (!result.ok()) {
+    if (!result.errata.empty()) {
+        std::cerr << result.errata.front().text() << std::endl;
+    }
+}
+```
+
+## Dependencies
+
+- **yaml-cpp** - YAML parsing
+- **libswoc** - Error handling (`swoc::Errata`)
+
+## Running Unit Tests
+
+Build with testing enabled and run:
+
+```bash
+cmake -B build -DBUILD_TESTING=ON ...
+cmake --build build --target test_tsconfig
+ctest --test-dir build -R test_tsconfig
+```
diff --git a/src/config/ssl_multicert.cc b/src/config/ssl_multicert.cc
new file mode 100644
index 0000000..5dfdf48
--- /dev/null
+++ b/src/config/ssl_multicert.cc
@@ -0,0 +1,374 @@
+/** @file
+
+  SSL Multi-Certificate configuration parsing and marshalling implementation.
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+*/
+
+#include "config/ssl_multicert.h"
+
+#include <cerrno>
+#include <cctype>
+#include <exception>
+#include <set>
+
+#include <yaml-cpp/yaml.h>
+
+#include "swoc/swoc_file.h"
+#include "swoc/TextView.h"
+#include "tsutil/ts_diag_levels.h"
+
+namespace
+{
+
+constexpr swoc::Errata::Severity ERRATA_NOTE_SEV{static_cast<swoc::Errata::severity_type>(DL_Note)};
+constexpr swoc::Errata::Severity ERRATA_WARN_SEV{static_cast<swoc::Errata::severity_type>(DL_Warning)};
+constexpr swoc::Errata::Severity ERRATA_ERROR_SEV{static_cast<swoc::Errata::severity_type>(DL_Error)};
+
+// YAML key names.
+constexpr char KEY_SSL_CERT_NAME[]      = "ssl_cert_name";
+constexpr char KEY_DEST_IP[]            = "dest_ip";
+constexpr char KEY_SSL_KEY_NAME[]       = "ssl_key_name";
+constexpr char KEY_SSL_CA_NAME[]        = "ssl_ca_name";
+constexpr char KEY_SSL_OCSP_NAME[]      = "ssl_ocsp_name";
+constexpr char KEY_SSL_KEY_DIALOG[]     = "ssl_key_dialog";
+constexpr char KEY_DEST_FQDN[]          = "dest_fqdn";
+constexpr char KEY_SSL_TICKET_ENABLED[] = "ssl_ticket_enabled";
+constexpr char KEY_SSL_TICKET_NUMBER[]  = "ssl_ticket_number";
+constexpr char KEY_ACTION[]             = "action";
+constexpr char KEY_SSL_MULTICERT[]      = "ssl_multicert";
+
+std::set<std::string> const valid_keys = {
+  KEY_SSL_CERT_NAME,  KEY_DEST_IP,   KEY_SSL_KEY_NAME,       KEY_SSL_CA_NAME,       KEY_SSL_OCSP_NAME,
+  KEY_SSL_KEY_DIALOG, KEY_DEST_FQDN, KEY_SSL_TICKET_ENABLED, KEY_SSL_TICKET_NUMBER, KEY_ACTION,
+};
+
+/**
+ * Parse a line in legacy key=value format, handling quoted values.
+ *
+ * Tokenizes on whitespace but respects quoted strings. Each token should be
+ * key=value format.
+ */
+std::vector<std::pair<std::string, std::string>>
+parse_legacy_line(swoc::TextView line)
+{
+  std::vector<std::pair<std::string, std::string>> result;
+
+  while (!line.ltrim_if(isspace).empty()) {
+    swoc::TextView key = line.split_prefix_at('=');
+    if (key.empty()) {
+      // No '=' found, skip this malformed token by consuming to next whitespace.
+      line.take_prefix_if(isspace);
+      continue;
+    }
+    key.trim_if(isspace);
+
+    swoc::TextView value;
+    if (!line.empty() && (line.front() == '"' || line.front() == '\'')) {
+      // Quoted value: extract until closing quote.
+      char const quote = line.front();
+      line.remove_prefix(1);
+      value = line.take_prefix_at(quote);
+    } else {
+      // Unquoted value: extract until whitespace.
+      value = line.take_prefix_if(isspace);
+    }
+    value.trim_if(isspace);
+
+    if (!key.empty()) {
+      result.emplace_back(std::string{key}, std::string{value});
+    }
+  }
+
+  return result;
+}
+
+/// Emit a single SSLMultiCertEntry to a YAML::Emitter.
+void
+emit_entry(YAML::Emitter &emitter, config::SSLMultiCertEntry const &entry)
+{
+  emitter << YAML::BeginMap;
+
+  auto write_field = [&](char const *key, std::string const &value) {
+    if (!value.empty()) {
+      emitter << YAML::Key << key << YAML::Value << value;
+    }
+  };
+
+  auto write_int_field = [&](char const *key, std::optional<int> const &value) {
+    if (value.has_value()) {
+      emitter << YAML::Key << key << YAML::Value << value.value();
+    }
+  };
+
+  write_field(KEY_SSL_CERT_NAME, entry.ssl_cert_name);
+  write_field(KEY_DEST_IP, entry.dest_ip);
+  write_field(KEY_SSL_KEY_NAME, entry.ssl_key_name);
+  write_field(KEY_SSL_CA_NAME, entry.ssl_ca_name);
+  write_field(KEY_SSL_OCSP_NAME, entry.ssl_ocsp_name);
+  write_field(KEY_SSL_KEY_DIALOG, entry.ssl_key_dialog);
+  write_field(KEY_DEST_FQDN, entry.dest_fqdn);
+  write_field(KEY_ACTION, entry.action);
+  write_int_field(KEY_SSL_TICKET_ENABLED, entry.ssl_ticket_enabled);
+  write_int_field(KEY_SSL_TICKET_NUMBER, entry.ssl_ticket_number);
+
+  emitter << YAML::EndMap;
+}
+
+} // namespace
+
+namespace YAML
+{
+template <> struct convert<config::SSLMultiCertEntry> {
+  static bool
+  decode(Node const &node, config::SSLMultiCertEntry &entry)
+  {
+    if (node[KEY_SSL_CERT_NAME]) {
+      entry.ssl_cert_name = node[KEY_SSL_CERT_NAME].as<std::string>();
+    }
+
+    if (node[KEY_DEST_IP]) {
+      entry.dest_ip = node[KEY_DEST_IP].as<std::string>();
+    }
+
+    if (node[KEY_SSL_KEY_NAME]) {
+      entry.ssl_key_name = node[KEY_SSL_KEY_NAME].as<std::string>();
+    }
+
+    if (node[KEY_SSL_CA_NAME]) {
+      entry.ssl_ca_name = node[KEY_SSL_CA_NAME].as<std::string>();
+    }
+
+    if (node[KEY_SSL_OCSP_NAME]) {
+      entry.ssl_ocsp_name = node[KEY_SSL_OCSP_NAME].as<std::string>();
+    }
+
+    if (node[KEY_SSL_KEY_DIALOG]) {
+      entry.ssl_key_dialog = node[KEY_SSL_KEY_DIALOG].as<std::string>();
+    }
+
+    if (node[KEY_DEST_FQDN]) {
+      entry.dest_fqdn = node[KEY_DEST_FQDN].as<std::string>();
+    }
+
+    if (node[KEY_SSL_TICKET_ENABLED]) {
+      entry.ssl_ticket_enabled = node[KEY_SSL_TICKET_ENABLED].as<int>();
+    }
+
+    if (node[KEY_SSL_TICKET_NUMBER]) {
+      entry.ssl_ticket_number = node[KEY_SSL_TICKET_NUMBER].as<int>();
+    }
+
+    if (node[KEY_ACTION]) {
+      entry.action = node[KEY_ACTION].as<std::string>();
+    }
+
+    return true;
+  }
+};
+} // namespace YAML
+
+namespace config
+{
+
+ConfigResult<SSLMultiCertConfig>
+SSLMultiCertParser::parse(std::string const &filename)
+{
+  std::error_code ec;
+  std::string     content = swoc::file::load(filename, ec);
+  if (ec) {
+    // Missing ssl_multicert.* is an acceptable runtime state.
+    if (ec.value() == ENOENT) {
+      return {{}, swoc::Errata(ERRATA_WARN_SEV, "Cannot open SSL certificate configuration \"{}\" - {}", filename, ec)};
+    }
+    return {{}, swoc::Errata(ERRATA_ERROR_SEV, "Failed to read SSL certificate configuration from \"{}\" - {}", filename, ec)};
+  }
+
+  if (content.empty()) {
+    return {{}, {}};
+  }
+
+  Format const format = detect_format(content, filename);
+  if (format == Format::YAML) {
+    return parse_yaml(content);
+  }
+  return parse_legacy(content);
+}
+
+SSLMultiCertParser::Format
+SSLMultiCertParser::detect_format(std::string_view content, std::string const &filename)
+{
+  swoc::TextView const fn{filename};
+
+  // Check file extension first.
+  if (fn.ends_with(".yaml") || fn.ends_with(".yml")) {
+    return Format::YAML;
+  }
+  if (fn.ends_with(".config")) {
+    return Format::Legacy;
+  }
+
+  // Fall back to content inspection.
+  if (content.find("ssl_multicert:") != std::string_view::npos) {
+    return Format::YAML;
+  }
+
+  // Legacy format uses key=value.
+  if (content.find('=') != std::string_view::npos) {
+    return Format::Legacy;
+  }
+
+  // Default to YAML as that's the preferred format.
+  return Format::YAML;
+}
+
+ConfigResult<SSLMultiCertConfig>
+SSLMultiCertParser::parse_yaml(std::string_view content)
+{
+  SSLMultiCertConfig    result;
+  swoc::Errata          errata;
+  std::set<std::string> unknown_keys;
+
+  try {
+    YAML::Node config = YAML::Load(std::string(content));
+    if (config.IsNull()) {
+      return {result, std::move(errata)};
+    }
+
+    if (!config[KEY_SSL_MULTICERT]) {
+      return {result, swoc::Errata("expected a toplevel 'ssl_multicert' node")};
+    }
+
+    YAML::Node entries = config[KEY_SSL_MULTICERT];
+    if (!entries.IsSequence()) {
+      return {result, swoc::Errata("expected 'ssl_multicert' to be a sequence")};
+    }
+
+    for (auto const &entry_node : entries) {
+      auto const mark = entry_node.Mark();
+      if (!entry_node.IsMap()) {
+        return {result, swoc::Errata(ERRATA_ERROR_SEV, "Expected ssl_multicert entries to be maps at line {}, column {}", mark.line,
+                                     mark.column)};
+      }
+
+      for (auto const &field : entry_node) {
+        std::string key = field.first.as<std::string>();
+        if (valid_keys.find(key) == valid_keys.end() && unknown_keys.insert(key).second) {
+          errata.note(ERRATA_NOTE_SEV, "Ignoring unknown ssl_multicert key '{}' at line {}, column {}", key, mark.line,
+                      mark.column);
+        }
+      }
+
+      result.push_back(entry_node.as<SSLMultiCertEntry>());
+    }
+  } catch (std::exception const &ex) {
+    return {result, swoc::Errata("YAML parse error: {}", ex.what())};
+  }
+
+  return {result, std::move(errata)};
+}
+
+ConfigResult<SSLMultiCertConfig>
+SSLMultiCertParser::parse_legacy(std::string_view content)
+{
+  SSLMultiCertConfig    result;
+  swoc::Errata          errata;
+  std::set<std::string> unknown_keys;
+  swoc::TextView        src{content};
+
+  while (!src.empty()) {
+    swoc::TextView line = src.take_prefix_at('\n');
+    line.trim_if(isspace);
+
+    // Skip empty lines and comments.
+    if (line.empty() || line.front() == '#') {
+      continue;
+    }
+
+    auto const kv_pairs = parse_legacy_line(line);
+    if (kv_pairs.empty()) {
+      continue;
+    }
+
+    SSLMultiCertEntry entry;
+
+    for (auto const &[key, value] : kv_pairs) {
+      if (key == KEY_SSL_CERT_NAME) {
+        entry.ssl_cert_name = value;
+      } else if (key == KEY_DEST_IP) {
+        entry.dest_ip = value;
+      } else if (key == KEY_SSL_KEY_NAME) {
+        entry.ssl_key_name = value;
+      } else if (key == KEY_SSL_CA_NAME) {
+        entry.ssl_ca_name = value;
+      } else if (key == KEY_SSL_OCSP_NAME) {
+        entry.ssl_ocsp_name = value;
+      } else if (key == KEY_SSL_KEY_DIALOG) {
+        entry.ssl_key_dialog = value;
+      } else if (key == KEY_DEST_FQDN) {
+        entry.dest_fqdn = value;
+      } else if (key == KEY_ACTION) {
+        entry.action = value;
+      } else if (key == KEY_SSL_TICKET_ENABLED) {
+        entry.ssl_ticket_enabled = swoc::svtoi(value);
+      } else if (key == KEY_SSL_TICKET_NUMBER) {
+        entry.ssl_ticket_number = swoc::svtoi(value);
+      } else if (unknown_keys.insert(key).second) {
+        errata.note(ERRATA_NOTE_SEV, "Ignoring unknown ssl_multicert key '{}' in legacy format", key);
+      }
+    }
+
+    result.push_back(std::move(entry));
+  }
+
+  return {result, std::move(errata)};
+}
+
+std::string
+SSLMultiCertMarshaller::to_yaml(SSLMultiCertConfig const &config)
+{
+  YAML::Emitter yaml;
+  yaml << YAML::BeginMap;
+  yaml << YAML::Key << KEY_SSL_MULTICERT << YAML::Value << YAML::BeginSeq;
+
+  for (auto const &entry : config) {
+    emit_entry(yaml, entry);
+  }
+
+  yaml << YAML::EndSeq << YAML::EndMap;
+  return yaml.c_str();
+}
+
+std::string
+SSLMultiCertMarshaller::to_json(SSLMultiCertConfig const &config)
+{
+  YAML::Emitter json;
+  json << YAML::DoubleQuoted << YAML::Flow;
+  json << YAML::BeginMap;
+  json << YAML::Key << KEY_SSL_MULTICERT << YAML::Value << YAML::BeginSeq;
+
+  for (auto const &entry : config) {
+    emit_entry(json, entry);
+  }
+
+  json << YAML::EndSeq << YAML::EndMap;
+  return json.c_str();
+}
+
+} // namespace config
diff --git a/src/config/unit_tests/test_ssl_multicert.cc b/src/config/unit_tests/test_ssl_multicert.cc
new file mode 100644
index 0000000..7baae95
--- /dev/null
+++ b/src/config/unit_tests/test_ssl_multicert.cc
@@ -0,0 +1,482 @@
+/** @file
+
+  Unit tests for ssl_multicert configuration parsing and marshalling.
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+*/
+
+#include "config/ssl_multicert.h"
+
+#include <filesystem>
+#include <fstream>
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/generators/catch_generators.hpp>
+
+using namespace config;
+
+namespace
+{
+// Helper to create a temporary file with content.
+class TempFile
+{
+public:
+  TempFile(std::string const &filename, std::string const &content)
+  {
+    _path = std::filesystem::temp_directory_path() / filename;
+    std::ofstream ofs(_path);
+    ofs << content;
+  }
+
+  ~TempFile() { std::filesystem::remove(_path); }
+
+  std::string
+  path() const
+  {
+    return _path.string();
+  }
+
+private:
+  std::filesystem::path _path;
+};
+
+// Helper to parse content via temp file.
+ConfigResult<SSLMultiCertConfig>
+parse_content(std::string const &content, std::string const &filename)
+{
+  TempFile           file(filename, content);
+  SSLMultiCertParser parser;
+  return parser.parse(file.path());
+}
+} // namespace
+
+// Sample legacy config content.
+static constexpr char LEGACY_CONFIG[] = R"(# Comment line
+ssl_cert_name=server.pem ssl_key_name=server.key dest_ip=*
+ssl_cert_name=another.pem dest_ip="[::1]:8443" ssl_ticket_enabled=1
+ssl_cert_name=quoted.pem ssl_key_dialog="exec:/usr/bin/getpass arg1 'arg 2'"
+)";
+
+// Sample YAML config content.
+static constexpr char YAML_CONFIG[] = R"(ssl_multicert:
+  - ssl_cert_name: server.pem
+    ssl_key_name: server.key
+    dest_ip: "*"
+  - ssl_cert_name: another.pem
+    dest_ip: "[::1]:8443"
+    ssl_ticket_enabled: 1
+  - ssl_cert_name: quoted.pem
+    ssl_key_dialog: "exec:/usr/bin/getpass arg1 'arg 2'"
+)";
+
+TEST_CASE("SSLMultiCertParser parses legacy config format", "[ssl_multicert][parser][legacy]")
+{
+  auto result = parse_content(LEGACY_CONFIG, "ssl_multicert.config");
+
+  REQUIRE(result.ok());
+  REQUIRE(result.value.size() == 3);
+
+  SECTION("First entry")
+  {
+    auto const &entry = result.value[0];
+    CHECK(entry.ssl_cert_name == "server.pem");
+    CHECK(entry.ssl_key_name == "server.key");
+    CHECK(entry.dest_ip == "*");
+    CHECK_FALSE(entry.ssl_ticket_enabled.has_value());
+  }
+
+  SECTION("Second entry with IPv6 and ticket enabled")
+  {
+    auto const &entry = result.value[1];
+    CHECK(entry.ssl_cert_name == "another.pem");
+    CHECK(entry.dest_ip == "[::1]:8443");
+    REQUIRE(entry.ssl_ticket_enabled.has_value());
+    CHECK(entry.ssl_ticket_enabled.value() == 1);
+  }
+
+  SECTION("Third entry with quoted dialog")
+  {
+    auto const &entry = result.value[2];
+    CHECK(entry.ssl_cert_name == "quoted.pem");
+    CHECK(entry.ssl_key_dialog == "exec:/usr/bin/getpass arg1 'arg 2'");
+  }
+}
+
+TEST_CASE("SSLMultiCertParser parses YAML config format", "[ssl_multicert][parser][yaml]")
+{
+  auto result = parse_content(YAML_CONFIG, "ssl_multicert.yaml");
+
+  REQUIRE(result.ok());
+  REQUIRE(result.value.size() == 3);
+
+  SECTION("First entry")
+  {
+    auto const &entry = result.value[0];
+    CHECK(entry.ssl_cert_name == "server.pem");
+    CHECK(entry.ssl_key_name == "server.key");
+    CHECK(entry.dest_ip == "*");
+  }
+
+  SECTION("Second entry with IPv6 and ticket enabled")
+  {
+    auto const &entry = result.value[1];
+    CHECK(entry.ssl_cert_name == "another.pem");
+    CHECK(entry.dest_ip == "[::1]:8443");
+    REQUIRE(entry.ssl_ticket_enabled.has_value());
+    CHECK(entry.ssl_ticket_enabled.value() == 1);
+  }
+
+  SECTION("Third entry with quoted dialog")
+  {
+    auto const &entry = result.value[2];
+    CHECK(entry.ssl_cert_name == "quoted.pem");
+    CHECK(entry.ssl_key_dialog == "exec:/usr/bin/getpass arg1 'arg 2'");
+  }
+}
+
+TEST_CASE("SSLMultiCertParser auto-detects format from filename", "[ssl_multicert][parser][detection]")
+{
+  auto [description, content, filename] = GENERATE(table<char const *, char const *, char const *>({
+    {"YAML from .yaml extension",          YAML_CONFIG,   "config.yaml"         },
+    {"YAML from .yml extension",           YAML_CONFIG,   "config.yml"          },
+    {"legacy from .config extension",      LEGACY_CONFIG, "ssl_multicert.config"},
+    {"YAML from content (no extension)",   YAML_CONFIG,   "config"              },
+    {"legacy from content (no extension)", LEGACY_CONFIG, "config"              },
+  }));
+
+  CAPTURE(description, filename);
+
+  auto result = parse_content(content, filename);
+  REQUIRE(result.ok());
+  CHECK(result.value.size() == 3);
+}
+
+TEST_CASE("SSLMultiCertParser returns empty config for empty input", "[ssl_multicert][parser][edge]")
+{
+  auto [description, content, filename] = GENERATE(table<char const *, char const *, char const *>({
+    {"empty YAML content",          "",                                      "config.yaml"  },
+    {"empty legacy content",        "",                                      "config.config"},
+    {"comments only in legacy",     "# Just a comment\n# Another comment\n", "config.config"},
+    {"whitespace only",             "   \n\t\n   ",                          "config.config"},
+    {"empty ssl_multicert in YAML", "ssl_multicert: []\n",                   "config.yaml"  },
+  }));
+
+  CAPTURE(description);
+
+  auto result = parse_content(content, filename);
+  REQUIRE(result.ok());
+  CHECK(result.value.empty());
+}
+
+TEST_CASE("SSLMultiCertParser returns error for invalid input", "[ssl_multicert][parser][edge]")
+{
+  auto [description, content, filename] = GENERATE(table<char const *, char const *, char const *>({
+    {"invalid YAML syntax",       "ssl_multicert: [not: valid: yaml", "config.yaml"},
+    {"missing ssl_multicert key", "other_key:\n  - value: 1\n",       "config.yaml"},
+  }));
+
+  CAPTURE(description);
+
+  auto result = parse_content(content, filename);
+  CHECK_FALSE(result.ok());
+}
+
+TEST_CASE("SSLMultiCertParser handles all YAML entry fields", "[ssl_multicert][parser][fields]")
+{
+  static constexpr char FULL_YAML[] = R"(ssl_multicert:
+  - ssl_cert_name: cert.pem
+    dest_ip: "192.168.1.1"
+    ssl_key_name: key.pem
+    ssl_ca_name: ca.pem
+    ssl_ocsp_name: ocsp.der
+    ssl_key_dialog: "builtin"
+    dest_fqdn: "example.com"
+    action: tunnel
+    ssl_ticket_enabled: 1
+    ssl_ticket_number: 5
+)";
+
+  auto result = parse_content(FULL_YAML, "config.yaml");
+  REQUIRE(result.ok());
+  REQUIRE(result.value.size() == 1);
+
+  auto const &entry = result.value[0];
+  CHECK(entry.ssl_cert_name == "cert.pem");
+  CHECK(entry.dest_ip == "192.168.1.1");
+  CHECK(entry.ssl_key_name == "key.pem");
+  CHECK(entry.ssl_ca_name == "ca.pem");
+  CHECK(entry.ssl_ocsp_name == "ocsp.der");
+  CHECK(entry.ssl_key_dialog == "builtin");
+  CHECK(entry.dest_fqdn == "example.com");
+  CHECK(entry.action == "tunnel");
+  REQUIRE(entry.ssl_ticket_enabled.has_value());
+  CHECK(entry.ssl_ticket_enabled.value() == 1);
+  REQUIRE(entry.ssl_ticket_number.has_value());
+  CHECK(entry.ssl_ticket_number.value() == 5);
+}
+
+TEST_CASE("SSLMultiCertMarshaller produces valid YAML", "[ssl_multicert][marshaller][yaml]")
+{
+  SSLMultiCertConfig config;
+
+  SSLMultiCertEntry entry1;
+  entry1.ssl_cert_name = "server.pem";
+  entry1.dest_ip       = "*";
+  entry1.ssl_key_name  = "server.key";
+  config.push_back(entry1);
+
+  SSLMultiCertEntry entry2;
+  entry2.ssl_cert_name      = "another.pem";
+  entry2.dest_ip            = "[::1]:8443";
+  entry2.ssl_ticket_enabled = 1;
+  config.push_back(entry2);
+
+  SSLMultiCertMarshaller marshaller;
+  std::string            yaml = marshaller.to_yaml(config);
+
+  SECTION("YAML contains expected structure")
+  {
+    CHECK(yaml.find("ssl_multicert:") != std::string::npos);
+    CHECK(yaml.find("ssl_cert_name: server.pem") != std::string::npos);
+    CHECK(yaml.find("ssl_key_name: server.key") != std::string::npos);
+    CHECK(yaml.find("ssl_cert_name: another.pem") != std::string::npos);
+    CHECK(yaml.find("ssl_ticket_enabled: 1") != std::string::npos);
+  }
+
+  SECTION("YAML can be re-parsed")
+  {
+    auto result = parse_content(yaml, "config.yaml");
+    REQUIRE(result.ok());
+    REQUIRE(result.value.size() == 2);
+    CHECK(result.value[0].ssl_cert_name == "server.pem");
+    CHECK(result.value[1].ssl_cert_name == "another.pem");
+  }
+}
+
+TEST_CASE("SSLMultiCertMarshaller produces valid JSON", "[ssl_multicert][marshaller][json]")
+{
+  SSLMultiCertConfig config;
+
+  SSLMultiCertEntry entry1;
+  entry1.ssl_cert_name = "server.pem";
+  entry1.dest_ip       = "*";
+  config.push_back(entry1);
+
+  SSLMultiCertEntry entry2;
+  entry2.ssl_cert_name      = "another.pem";
+  entry2.dest_ip            = "[::1]:8443";
+  entry2.ssl_ticket_enabled = 1;
+  entry2.ssl_ticket_number  = 5;
+  config.push_back(entry2);
+
+  SSLMultiCertMarshaller marshaller;
+  std::string            json = marshaller.to_json(config);
+
+  CHECK(json.find("\"ssl_multicert\"") != std::string::npos);
+  CHECK(json.find("\"ssl_cert_name\": \"server.pem\"") != std::string::npos);
+  CHECK(json.find("\"ssl_cert_name\": \"another.pem\"") != std::string::npos);
+  CHECK(json.find("\"ssl_ticket_enabled\": 1") != std::string::npos);
+  CHECK(json.find("\"ssl_ticket_number\": 5") != std::string::npos);
+  CHECK(json.find('[') != std::string::npos);
+  CHECK(json.find(']') != std::string::npos);
+}
+
+TEST_CASE("SSLMultiCertMarshaller handles special characters", "[ssl_multicert][marshaller][escaping]")
+{
+  SSLMultiCertConfig config;
+
+  SSLMultiCertEntry entry;
+  entry.ssl_cert_name  = "server.pem";
+  entry.dest_ip        = "*";
+  entry.ssl_key_dialog = "exec:/path/to/script \"with quotes\"";
+  config.push_back(entry);
+
+  SSLMultiCertMarshaller marshaller;
+
+  SECTION("YAML output contains the field and can be re-parsed")
+  {
+    std::string yaml = marshaller.to_yaml(config);
+    CHECK(yaml.find("ssl_key_dialog:") != std::string::npos);
+
+    // Verify round-trip preserves the value.
+    auto result = parse_content(yaml, "test.yaml");
+    REQUIRE(result.ok());
+    REQUIRE(result.value.size() == 1);
+    CHECK(result.value[0].ssl_key_dialog == "exec:/path/to/script \"with quotes\"");
+  }
+
+  SECTION("JSON escapes quotes")
+  {
+    std::string json = marshaller.to_json(config);
+    CHECK(json.find("\\\"with quotes\\\"") != std::string::npos);
+  }
+}
+
+TEST_CASE("Round-trip: legacy -> parse -> yaml -> parse", "[ssl_multicert][roundtrip]")
+{
+  SSLMultiCertMarshaller marshaller;
+
+  // Parse legacy format.
+  auto legacy_result = parse_content(LEGACY_CONFIG, "ssl_multicert.config");
+  REQUIRE(legacy_result.ok());
+
+  // Marshal to YAML.
+  std::string yaml = marshaller.to_yaml(legacy_result.value);
+
+  // Re-parse YAML.
+  auto yaml_result = parse_content(yaml, "ssl_multicert.yaml");
+  REQUIRE(yaml_result.ok());
+
+  // Verify same number of entries.
+  REQUIRE(legacy_result.value.size() == yaml_result.value.size());
+
+  // Verify entries match.
+  for (size_t i = 0; i < legacy_result.value.size(); ++i) {
+    CHECK(legacy_result.value[i].ssl_cert_name == yaml_result.value[i].ssl_cert_name);
+    CHECK(legacy_result.value[i].ssl_key_name == yaml_result.value[i].ssl_key_name);
+    CHECK(legacy_result.value[i].ssl_key_dialog == yaml_result.value[i].ssl_key_dialog);
+  }
+}
+
+TEST_CASE("SSLMultiCertParser::parse returns error for missing file", "[ssl_multicert][parser][file]")
+{
+  SSLMultiCertParser parser;
+
+  auto result = parser.parse("/nonexistent/path/to/ssl_multicert.yaml");
+  CHECK_FALSE(result.ok());
+}
+
+// ============================================================================
+// Legacy format edge cases (parameterized)
+// ============================================================================
+
+TEST_CASE("Legacy parser handles whitespace variations", "[ssl_multicert][parser][legacy][whitespace]")
+{
+  auto [description, config, expected_cert, expected_key] = GENERATE(table<char const *, char const *, char const *, char const *>({
+    {"multiple spaces between pairs", "ssl_cert_name=a.pem    ssl_key_name=a.key",   "a.pem", "a.key"},
+    {"tabs between pairs",            "ssl_cert_name=a.pem\tssl_key_name=a.key",     "a.pem", "a.key"},
+    {"leading whitespace",            "   ssl_cert_name=a.pem ssl_key_name=a.key",   "a.pem", "a.key"},
+    {"trailing whitespace",           "ssl_cert_name=a.pem ssl_key_name=a.key   ",   "a.pem", "a.key"},
+    {"leading tabs",                  "\t\tssl_cert_name=a.pem ssl_key_name=a.key",  "a.pem", "a.key"},
+    {"mixed leading whitespace",      "  \t ssl_cert_name=a.pem ssl_key_name=a.key", "a.pem", "a.key"},
+  }));
+
+  CAPTURE(description, config);
+
+  auto result = parse_content(config, "test.config");
+  REQUIRE(result.ok());
+  REQUIRE(result.value.size() == 1);
+  CHECK(result.value[0].ssl_cert_name == expected_cert);
+  CHECK(result.value[0].ssl_key_name == expected_key);
+}
+
+TEST_CASE("Legacy parser handles quoted values", "[ssl_multicert][parser][legacy][quotes]")
+{
+  auto [description, config, expected_field, expected_value] =
+    GENERATE(table<char const *, char const *, char const *, char const *>({
+      {"double-quoted with spaces",   R"(ssl_cert_name="path with spaces.pem")",                      "ssl_cert_name",  "path with spaces.pem"},
+      {"single-quoted with spaces",   R"(ssl_cert_name='path with spaces.pem')",                      "ssl_cert_name",  "path with spaces.pem"},
+      {"quoted followed by unquoted", R"(ssl_key_dialog="exec:/bin/script arg" ssl_cert_name=c.pem)", "ssl_key_dialog",
+       "exec:/bin/script arg"                                                                                                                 },
+      {"IPv6 in quotes",              R"(dest_ip="[::1]:443" ssl_cert_name=cert.pem)",                "dest_ip",        "[::1]:443"           },
+      {"equals inside quotes",        R"(ssl_cert_name="value=with=equals")",                         "ssl_cert_name",  "value=with=equals"   },
+  }));
+
+  CAPTURE(description, config);
+
+  auto result = parse_content(config, "test.config");
+  REQUIRE(result.ok());
+  REQUIRE(result.value.size() == 1);
+
+  auto const &entry = result.value[0];
+  if (expected_field == std::string("ssl_cert_name")) {
+    CHECK(entry.ssl_cert_name == expected_value);
+  } else if (expected_field == std::string("ssl_key_dialog")) {
+    CHECK(entry.ssl_key_dialog == expected_value);
+  } else if (expected_field == std::string("dest_ip")) {
+    CHECK(entry.dest_ip == expected_value);
+  }
+}
+
+TEST_CASE("Legacy parser handles multiline content", "[ssl_multicert][parser][legacy][multiline]")
+{
+  auto [description, config, expected_count] = GENERATE(table<char const *, char const *, size_t>({
+    {"three entries",             "ssl_cert_name=first.pem\nssl_cert_name=second.pem\nssl_cert_name=third.pem", 3},
+    {"with comments and blanks",  "# Header\nssl_cert_name=first.pem\n\n# Comment\nssl_cert_name=second.pem\n", 2},
+    {"Windows CRLF line endings", "ssl_cert_name=first.pem\r\nssl_cert_name=second.pem\r\n",                    2},
+    {"single line no newline",    "ssl_cert_name=only.pem",                                                     1},
+    {"single line with newline",  "ssl_cert_name=only.pem\n",                                                   1},
+  }));
+
+  CAPTURE(description);
+
+  auto result = parse_content(config, "test.config");
+  REQUIRE(result.ok());
+  CHECK(result.value.size() == expected_count);
+}
+
+TEST_CASE("Legacy parser handles all field types", "[ssl_multicert][parser][legacy][fields]")
+{
+  static constexpr char FULL_LEGACY[] = "ssl_cert_name=cert.pem dest_ip=192.168.1.1 ssl_key_name=key.pem ssl_ca_name=ca.pem "
+                                        "ssl_ocsp_name=ocsp.der ssl_key_dialog=builtin dest_fqdn=example.com action=tunnel "
+                                        "ssl_ticket_enabled=1 ssl_ticket_number=5";
+
+  auto result = parse_content(FULL_LEGACY, "test.config");
+  REQUIRE(result.ok());
+  REQUIRE(result.value.size() == 1);
+
+  auto const &entry = result.value[0];
+  CHECK(entry.ssl_cert_name == "cert.pem");
+  CHECK(entry.dest_ip == "192.168.1.1");
+  CHECK(entry.ssl_key_name == "key.pem");
+  CHECK(entry.ssl_ca_name == "ca.pem");
+  CHECK(entry.ssl_ocsp_name == "ocsp.der");
+  CHECK(entry.ssl_key_dialog == "builtin");
+  CHECK(entry.dest_fqdn == "example.com");
+  CHECK(entry.action == "tunnel");
+  REQUIRE(entry.ssl_ticket_enabled.has_value());
+  CHECK(entry.ssl_ticket_enabled.value() == 1);
+  REQUIRE(entry.ssl_ticket_number.has_value());
+  CHECK(entry.ssl_ticket_number.value() == 5);
+}
+
+TEST_CASE("Legacy parser handles dual certificates", "[ssl_multicert][parser][legacy][dual-cert]")
+{
+  auto result =
+    parse_content("ssl_cert_name=server-ec.pem,server-rsa.pem ssl_key_name=server-ec.key,server-rsa.key", "test.config");
+  REQUIRE(result.ok());
+  REQUIRE(result.value.size() == 1);
+  CHECK(result.value[0].ssl_cert_name == "server-ec.pem,server-rsa.pem");
+  CHECK(result.value[0].ssl_key_name == "server-ec.key,server-rsa.key");
+}
+
+TEST_CASE("Legacy parser skips malformed entries", "[ssl_multicert][parser][legacy][malformed]")
+{
+  auto [description, config, expected_count] = GENERATE(table<char const *, char const *, size_t>({
+    {"line without equals",        "ssl_cert_name=valid.pem\nmalformed_no_equals\nssl_cert_name=another.pem", 2},
+    {"blank line between entries", "ssl_cert_name=first.pem\n\nssl_cert_name=second.pem",                     2},
+    {"comment before valid entry", "# only comment\nssl_cert_name=valid.pem",                                 1},
+  }));
+
+  CAPTURE(description);
+
+  auto result = parse_content(config, "test.config");
+  REQUIRE(result.ok());
+  CHECK(result.value.size() == expected_count);
+}
diff --git a/src/iocore/net/CMakeLists.txt b/src/iocore/net/CMakeLists.txt
index a416ec1..16563e1 100644
--- a/src/iocore/net/CMakeLists.txt
+++ b/src/iocore/net/CMakeLists.txt
@@ -108,7 +108,7 @@
          OpenSSL::Crypto
          OpenSSL::SSL
          ts::tsapibackend
-  PRIVATE ts::tsutil yaml-cpp::yaml-cpp
+  PRIVATE ts::config ts::tsutil yaml-cpp::yaml-cpp
 )
 
 # Is this necessary?
diff --git a/src/iocore/net/P_SSLCertLookup.h b/src/iocore/net/P_SSLCertLookup.h
index da31f58..b40baa7 100644
--- a/src/iocore/net/P_SSLCertLookup.h
+++ b/src/iocore/net/P_SSLCertLookup.h
@@ -46,7 +46,7 @@
 };
 
 /**
-   @brief Gather user provided settings from ssl_multicert.config in to this single struct
+   @brief Gather user provided settings from ssl_multicert.yaml in to this single struct
  */
 struct SSLMultiCertConfigParams {
   SSLMultiCertConfigParams() : opt(SSLCertContextOption::OPT_NONE)
diff --git a/src/iocore/net/P_SSLConfig.h b/src/iocore/net/P_SSLConfig.h
index 813d8e8..9aea6dc 100644
--- a/src/iocore/net/P_SSLConfig.h
+++ b/src/iocore/net/P_SSLConfig.h
@@ -53,12 +53,6 @@
 using load_ssl_file_func = void (*)(const char *);
 
 struct SSLConfigParams : public ConfigInfo {
-  enum SSL_SESSION_CACHE_MODE {
-    SSL_SESSION_CACHE_MODE_OFF                 = 0,
-    SSL_SESSION_CACHE_MODE_SERVER_OPENSSL_IMPL = 1,
-    SSL_SESSION_CACHE_MODE_SERVER_ATS_IMPL     = 2
-  };
-
   SSLConfigParams();
   ~SSLConfigParams() override;
 
@@ -76,12 +70,6 @@
   int   verify_depth;
   int   ssl_origin_session_cache;
   int   ssl_origin_session_cache_size;
-  int   ssl_session_cache; // SSL_SESSION_CACHE_MODE
-  int   ssl_session_cache_size;
-  int   ssl_session_cache_num_buckets;
-  int   ssl_session_cache_skip_on_contention;
-  int   ssl_session_cache_timeout;
-  int   ssl_session_cache_auto_clear;
 
   char                   *clientCertPath;
   char                   *clientCertPathOnly;
@@ -132,9 +120,6 @@
 
   static int    origin_session_cache;
   static size_t origin_session_cache_size;
-  static size_t session_cache_number_buckets;
-  static size_t session_cache_max_bucket_size;
-  static bool   session_cache_skip_on_lock_contention;
 
   static swoc::IPRangeSet *proxy_protocol_ip_addrs;
 
@@ -256,5 +241,4 @@
   static int configid;
 };
 
-extern SSLSessionCache       *session_cache;
 extern SSLOriginSessionCache *origin_sess_cache;
diff --git a/src/iocore/net/SSLConfig.cc b/src/iocore/net/SSLConfig.cc
index a4b1e39..15ab3d7 100644
--- a/src/iocore/net/SSLConfig.cc
+++ b/src/iocore/net/SSLConfig.cc
@@ -50,29 +50,26 @@
 #include <cmath>
 #include <unordered_map>
 
-int                SSLConfig::config_index                                = 0;
-int                SSLConfig::configids[]                                 = {0, 0};
-int                SSLCertificateConfig::configid                         = 0;
-int                SSLTicketKeyConfig::configid                           = 0;
-int                SSLConfigParams::ssl_maxrecord                         = 0;
-int                SSLConfigParams::ssl_misc_max_iobuffer_size_index      = 8;
-bool               SSLConfigParams::ssl_allow_client_renegotiation        = false;
-bool               SSLConfigParams::ssl_ocsp_enabled                      = false;
-int                SSLConfigParams::ssl_ocsp_cache_timeout                = 3600;
-bool               SSLConfigParams::ssl_ocsp_request_mode                 = false;
-int                SSLConfigParams::ssl_ocsp_request_timeout              = 10;
-int                SSLConfigParams::ssl_ocsp_update_period                = 60;
-char              *SSLConfigParams::ssl_ocsp_user_agent                   = nullptr;
-int                SSLConfigParams::ssl_handshake_timeout_in              = 0;
-int                SSLConfigParams::origin_session_cache                  = 1;
-size_t             SSLConfigParams::origin_session_cache_size             = 10240;
-size_t             SSLConfigParams::session_cache_number_buckets          = 1024;
-bool               SSLConfigParams::session_cache_skip_on_lock_contention = false;
-size_t             SSLConfigParams::session_cache_max_bucket_size         = 100;
-init_ssl_ctx_func  SSLConfigParams::init_ssl_ctx_cb                       = nullptr;
-load_ssl_file_func SSLConfigParams::load_ssl_file_cb                      = nullptr;
-swoc::IPRangeSet  *SSLConfigParams::proxy_protocol_ip_addrs               = nullptr;
-bool               SSLConfigParams::ssl_ktls_enabled                      = false;
+int                SSLConfig::config_index                           = 0;
+int                SSLConfig::configids[]                            = {0, 0};
+int                SSLCertificateConfig::configid                    = 0;
+int                SSLTicketKeyConfig::configid                      = 0;
+int                SSLConfigParams::ssl_maxrecord                    = 0;
+int                SSLConfigParams::ssl_misc_max_iobuffer_size_index = 8;
+bool               SSLConfigParams::ssl_allow_client_renegotiation   = false;
+bool               SSLConfigParams::ssl_ocsp_enabled                 = false;
+int                SSLConfigParams::ssl_ocsp_cache_timeout           = 3600;
+bool               SSLConfigParams::ssl_ocsp_request_mode            = false;
+int                SSLConfigParams::ssl_ocsp_request_timeout         = 10;
+int                SSLConfigParams::ssl_ocsp_update_period           = 60;
+char              *SSLConfigParams::ssl_ocsp_user_agent              = nullptr;
+int                SSLConfigParams::ssl_handshake_timeout_in         = 0;
+int                SSLConfigParams::origin_session_cache             = 1;
+size_t             SSLConfigParams::origin_session_cache_size        = 10240;
+init_ssl_ctx_func  SSLConfigParams::init_ssl_ctx_cb                  = nullptr;
+load_ssl_file_func SSLConfigParams::load_ssl_file_cb                 = nullptr;
+swoc::IPRangeSet  *SSLConfigParams::proxy_protocol_ip_addrs          = nullptr;
+bool               SSLConfigParams::ssl_ktls_enabled                 = false;
 
 const uint32_t EARLY_DATA_DEFAULT_SIZE                         = 16384;
 uint32_t       SSLConfigParams::server_max_early_data          = 0;
@@ -92,87 +89,6 @@
 
 } // end anonymous namespace
 
-/** Determines the SSL session cache configuration value using a priority-based selection scheme.
- *
- * This function resolves the SSL session cache configuration by evaluating multiple potential
- * configuration sources and selecting the one with the highest priority. The priority calculation
- * combines two factors:
- *
- * Configuration Name Priority (base priority):
- * - `proxy.config.ssl.session_cache.mode`: 3 (highest preference)
- * - `proxy.config.ssl.session_cache.value`: 2 (medium preference)
- * - `proxy.config.ssl.session_cache.enabled`: 1 (lowest preference)
- *
- * Configuration Source Priority (added to base priority):
- * - Environment variable (`REC_SOURCE_ENV`): +0x30 (highest precedence)
- * - Explicit configuration (`REC_SOURCE_EXPLICIT`): +0x20 (config file, API)
- * - Plugin default (`REC_SOURCE_PLUGIN`): +0x10 (plugin changed the default value via TSMgmtIntCreate)
- * - Built-in default (`REC_SOURCE_DEFAULT`): +0x00 (lowest precedence)
- *
- * Priority Calculation:
- * `total_priority = base_priority + source_priority`
- *
- * Examples:
- * - `mode` set via environment variable: 3 + 0x30 = 0x33 (highest possible)
- * - `mode` set explicitly in config: 3 + 0x20 = 0x23
- * - `value` set via environment variable: 2 + 0x30 = 0x32
- * - `enabled` set explicitly in config: 1 + 0x20 = 0x21
- *
- * The configuration with the highest total priority is selected. This ensures that:
- * 1. Environment variables always override other sources.
- * 2. Among configurations from the same source, `mode` > `value` > `enabled`.
- * 3. Explicit configuration overrides plugin defaults and built-in defaults.
- *
- * @return The SSL session cache mode value.
- */
-static int
-get_ssl_session_cache_config()
-{
-  //
-  // TODO: in 11.x, we can simply remove this function and use only proxy.config.ssl.session_cache.mode.
-  //
-
-  struct ConfigOption {
-    const char *name;     ///< Configuration parameter name (e.g., "proxy.config.ssl.session_cache.mode").
-    int         value;    ///< The configured value if explicitly set.
-    int         priority; ///< The inherit priority of the config name, higher is more preferred.
-  };
-
-  /// The priority of the source. Higher is more preferred.
-  std::unordered_map<int, int> source_priorities = {
-    {REC_SOURCE_ENV,      0x30},
-    {REC_SOURCE_EXPLICIT, 0x20},
-    {REC_SOURCE_PLUGIN,   0x10},
-    {REC_SOURCE_DEFAULT,  0x0 },
-    {REC_SOURCE_NULL,     0x0 }, // For completeness, no record should have this set.
-  };
-
-  std::array<ConfigOption, 3> configs = {
-    {
-     {"proxy.config.ssl.session_cache.mode", 0, 0x3},
-     {"proxy.config.ssl.session_cache.value", 0, 0x2},
-     {"proxy.config.ssl.session_cache.enabled", 0, 0x1},
-     }
-  };
-
-  // Loop over the config names, updating their priority score per their source.
-  auto *highest_priority_config = &configs[0];
-  for (auto &config : configs) {
-    RecSourceT source;
-    if (RecGetRecordSource(config.name, &source) == REC_ERR_OKAY) {
-      config.priority += source_priorities[source];
-      config.value     = RecGetRecordInt(config.name).value_or(0);
-      if (config.priority > highest_priority_config->priority) {
-        highest_priority_config = &config;
-      }
-    } else {
-      // We need to update our logic here if any of these configs are removed.
-      ink_release_assert(false);
-    }
-  }
-  return highest_priority_config->value;
-}
-
 SSLConfigParams::SSLConfigParams()
 {
   ink_mutex_init(&ctxMapLock);
@@ -209,14 +125,7 @@
   verifyServerProperties                               = YamlSNIConfig::Property::NONE;
   ssl_ctx_options                                      = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
   ssl_client_ctx_options                               = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
-  ssl_session_cache                                    = SSL_SESSION_CACHE_MODE_SERVER_ATS_IMPL;
-  ssl_session_cache_size                               = 1024 * 100;
-  ssl_session_cache_num_buckets = 1024; // Sessions per bucket is ceil(ssl_session_cache_size / ssl_session_cache_num_buckets)
-  ssl_session_cache_skip_on_contention = 0;
-  ssl_session_cache_timeout            = 0;
-  ssl_session_cache_auto_clear         = 1;
-  configExitOnLoadError                = 1;
-  clientCertExitOnLoadError            = 0;
+  configExitOnLoadError                                = 1;
 }
 
 void
@@ -541,25 +450,9 @@
   // SSL session cache configurations
   ssl_origin_session_cache      = RecGetRecordInt("proxy.config.ssl.origin_session_cache.enabled").value_or(0);
   ssl_origin_session_cache_size = RecGetRecordInt("proxy.config.ssl.origin_session_cache.size").value_or(0);
-  ssl_session_cache             = get_ssl_session_cache_config();
-
-  ssl_session_cache_size        = RecGetRecordInt("proxy.config.ssl.session_cache.size").value_or(0);
-  ssl_session_cache_num_buckets = RecGetRecordInt("proxy.config.ssl.session_cache.num_buckets").value_or(0);
-  ssl_session_cache_skip_on_contention =
-    RecGetRecordInt("proxy.config.ssl.session_cache.skip_cache_on_bucket_contention").value_or(0);
-  ssl_session_cache_timeout    = RecGetRecordInt("proxy.config.ssl.session_cache.timeout").value_or(0);
-  ssl_session_cache_auto_clear = RecGetRecordInt("proxy.config.ssl.session_cache.auto_clear").value_or(0);
 
   SSLConfigParams::origin_session_cache      = ssl_origin_session_cache;
   SSLConfigParams::origin_session_cache_size = ssl_origin_session_cache_size;
-  SSLConfigParams::session_cache_max_bucket_size =
-    static_cast<size_t>(ceil(static_cast<double>(ssl_session_cache_size) / ssl_session_cache_num_buckets));
-  SSLConfigParams::session_cache_skip_on_lock_contention = ssl_session_cache_skip_on_contention;
-  SSLConfigParams::session_cache_number_buckets          = ssl_session_cache_num_buckets;
-
-  if (ssl_session_cache == SSL_SESSION_CACHE_MODE_SERVER_ATS_IMPL) {
-    session_cache = new SSLSessionCache();
-  }
 
   if (ssl_origin_session_cache == 1 && ssl_origin_session_cache_size > 0) {
     origin_sess_cache = new SSLOriginSessionCache();
diff --git a/src/iocore/net/SSLSessionCache.cc b/src/iocore/net/SSLSessionCache.cc
index 9dcccd5..9f6c52f 100644
--- a/src/iocore/net/SSLSessionCache.cc
+++ b/src/iocore/net/SSLSessionCache.cc
@@ -29,302 +29,11 @@
 #include <memory>
 #include <shared_mutex>
 
-#define SSLSESSIONCACHE_STRINGIFY0(x) #x
-#define SSLSESSIONCACHE_STRINGIFY(x)  SSLSESSIONCACHE_STRINGIFY0(x)
-#define SSLSESSIONCACHE_LINENO        SSLSESSIONCACHE_STRINGIFY(__LINE__)
-
-#ifdef DEBUG
-#define PRINT_BUCKET(x) this->print(x " at " __FILE__ ":" SSLSESSIONCACHE_LINENO);
-#else
-#define PRINT_BUCKET(x)
-#endif
-
 namespace
 {
 DbgCtl dbg_ctl_ssl_origin_session_cache{"ssl.origin_session_cache"};
-DbgCtl dbg_ctl_ssl_session_cache{"ssl.session_cache"};
-DbgCtl dbg_ctl_ssl_session_cache_bucket{"ssl.session_cache.bucket"};
-DbgCtl dbg_ctl_ssl_session_cache_get{"ssl.session_cache.get"};
-DbgCtl dbg_ctl_ssl_session_cache_insert{"ssl.session_cache.insert"};
-DbgCtl dbg_ctl_ssl_session_cache_remove{"ssl.session_cache.remove"};
-
 } // end anonymous namespace
 
-/* Session Cache */
-SSLSessionCache::SSLSessionCache() : nbuckets(SSLConfigParams::session_cache_number_buckets)
-{
-  Dbg(dbg_ctl_ssl_session_cache, "Created new ssl session cache %p with %zu buckets each with size max size %zu", this, nbuckets,
-      SSLConfigParams::session_cache_max_bucket_size);
-
-  session_bucket = new SSLSessionBucket[nbuckets];
-}
-
-SSLSessionCache::~SSLSessionCache()
-{
-  delete[] session_bucket;
-}
-
-int
-SSLSessionCache::getSessionBuffer(const SSLSessionID &sid, char *buffer, int &len) const
-{
-  uint64_t          hash          = sid.hash();
-  uint64_t          target_bucket = hash % nbuckets;
-  SSLSessionBucket *bucket        = &session_bucket[target_bucket];
-
-  return bucket->getSessionBuffer(sid, buffer, len);
-}
-
-bool
-SSLSessionCache::getSession(const SSLSessionID &sid, SSL_SESSION **sess, ssl_session_cache_exdata **data) const
-{
-  uint64_t          hash          = sid.hash();
-  uint64_t          target_bucket = hash % nbuckets;
-  SSLSessionBucket *bucket        = &session_bucket[target_bucket];
-
-  if (dbg_ctl_ssl_session_cache.on()) {
-    char buf[sid.len * 2 + 1];
-    sid.toString(buf, sizeof(buf));
-    DbgPrint(dbg_ctl_ssl_session_cache_get, "SessionCache looking in bucket %" PRId64 " (%p) for session '%s' (hash: %" PRIX64 ").",
-             target_bucket, bucket, buf, hash);
-  }
-
-  return bucket->getSession(sid, sess, data);
-}
-
-void
-SSLSessionCache::removeSession(const SSLSessionID &sid)
-{
-  uint64_t          hash          = sid.hash();
-  uint64_t          target_bucket = hash % nbuckets;
-  SSLSessionBucket *bucket        = &session_bucket[target_bucket];
-
-  if (dbg_ctl_ssl_session_cache_remove.on()) {
-    char buf[sid.len * 2 + 1];
-    sid.toString(buf, sizeof(buf));
-    DbgPrint(dbg_ctl_ssl_session_cache_remove,
-             "SessionCache using bucket %" PRId64 " (%p): Removing session '%s' (hash: %" PRIX64 ").", target_bucket, bucket, buf,
-             hash);
-  }
-  Metrics::Counter::increment(ssl_rsb.session_cache_eviction);
-
-  bucket->removeSession(sid);
-}
-
-void
-SSLSessionCache::insertSession(const SSLSessionID &sid, SSL_SESSION *sess, SSL *ssl)
-{
-  uint64_t          hash          = sid.hash();
-  uint64_t          target_bucket = hash % nbuckets;
-  SSLSessionBucket *bucket        = &session_bucket[target_bucket];
-
-  if (dbg_ctl_ssl_session_cache_insert.on()) {
-    char buf[sid.len * 2 + 1];
-    sid.toString(buf, sizeof(buf));
-    DbgPrint(dbg_ctl_ssl_session_cache_insert,
-             "SessionCache using bucket %" PRId64 " (%p): Inserting session '%s' (hash: %" PRIX64 ").", target_bucket, bucket, buf,
-             hash);
-  }
-
-  bucket->insertSession(sid, sess, ssl);
-}
-
-void
-SSLSessionBucket::insertSession(const SSLSessionID &id, SSL_SESSION *sess, SSL *ssl)
-{
-  std::shared_lock r_lock(mutex, std::try_to_lock);
-  if (!r_lock.owns_lock()) {
-    Metrics::Counter::increment(ssl_rsb.session_cache_lock_contention);
-    if (SSLConfigParams::session_cache_skip_on_lock_contention) {
-      return;
-    }
-    r_lock.lock();
-  }
-
-  // Don't insert if it is already there
-  if (bucket_map.find(id) != bucket_map.end()) {
-    return;
-  }
-
-  r_lock.unlock();
-
-  size_t len = i2d_SSL_SESSION(sess, nullptr); // make sure we're not going to need more than SSL_MAX_SESSION_SIZE bytes
-  /* do not cache a session that's too big. */
-  if (len > static_cast<size_t>(SSL_MAX_SESSION_SIZE)) {
-    Dbg(dbg_ctl_ssl_session_cache, "Unable to save SSL session because size of %zd exceeds the max of %d", len,
-        SSL_MAX_SESSION_SIZE);
-    return;
-  }
-
-  if (dbg_ctl_ssl_session_cache.on()) {
-    char buf[id.len * 2 + 1];
-    id.toString(buf, sizeof(buf));
-    DbgPrint(dbg_ctl_ssl_session_cache, "Inserting session '%s' to bucket %p.", buf, this);
-  }
-
-  Ptr<IOBufferData> buf;
-  Ptr<IOBufferData> buf_exdata;
-  size_t            len_exdata = sizeof(ssl_session_cache_exdata);
-  buf                          = new_IOBufferData(buffer_size_to_index(len, MAX_BUFFER_SIZE_INDEX), MEMALIGNED);
-  ink_release_assert(static_cast<size_t>(buf->block_size()) >= len);
-  unsigned char *loc = reinterpret_cast<unsigned char *>(buf->data());
-  i2d_SSL_SESSION(sess, &loc);
-  buf_exdata = new_IOBufferData(buffer_size_to_index(len, MAX_BUFFER_SIZE_INDEX), MEMALIGNED);
-  ink_release_assert(static_cast<size_t>(buf_exdata->block_size()) >= len_exdata);
-  ssl_session_cache_exdata *exdata = reinterpret_cast<ssl_session_cache_exdata *>(buf_exdata->data());
-  // This could be moved to a function in charge of populating exdata
-  exdata->curve = (ssl == nullptr) ? 0 : SSLGetCurveNID(ssl);
-
-  if (ssl == nullptr) {
-    exdata->group_name[0] = '\0';
-  } else {
-    std::string_view group_name = SSLGetGroupName(ssl);
-    ink_release_assert(group_name.size() < sizeof(exdata->group_name));
-    memcpy(exdata->group_name, group_name.data(), group_name.size());
-    exdata->group_name[group_name.size()] = '\0';
-  }
-
-  std::unique_ptr<SSLSession> ssl_session(new SSLSession(id, buf, len, buf_exdata));
-
-  std::unique_lock w_lock(mutex, std::try_to_lock);
-  if (!w_lock.owns_lock()) {
-    Metrics::Counter::increment(ssl_rsb.session_cache_lock_contention);
-    if (SSLConfigParams::session_cache_skip_on_lock_contention) {
-      return;
-    }
-    w_lock.lock();
-  }
-
-  PRINT_BUCKET("insertSession before")
-  if (bucket_map.size() >= SSLConfigParams::session_cache_max_bucket_size) {
-    Metrics::Counter::increment(ssl_rsb.session_cache_eviction);
-    removeOldestSession(w_lock);
-  }
-
-  /* do the actual insert */
-  auto node = ssl_session.release();
-  bucket_que.enqueue(node);
-  bucket_map[id] = node;
-
-  PRINT_BUCKET("insertSession after")
-}
-
-int
-SSLSessionBucket::getSessionBuffer(const SSLSessionID &id, char *buffer, int &len)
-{
-  int              true_len = 0;
-  std::shared_lock lock(mutex, std::try_to_lock);
-  if (!lock.owns_lock()) {
-    Metrics::Counter::increment(ssl_rsb.session_cache_lock_contention);
-    if (SSLConfigParams::session_cache_skip_on_lock_contention) {
-      return true_len;
-    }
-    lock.lock();
-  }
-
-  auto entry = bucket_map.find(id);
-  if (buffer && entry != bucket_map.end()) {
-    true_len                 = entry->second->len_asn1_data;
-    const unsigned char *loc = reinterpret_cast<const unsigned char *>(entry->second->asn1_data->data());
-    if (true_len < len) {
-      len = true_len;
-    }
-    memcpy(buffer, loc, len);
-    return true_len;
-  }
-  return 0;
-}
-
-bool
-SSLSessionBucket::getSession(const SSLSessionID &id, SSL_SESSION **sess, ssl_session_cache_exdata **data)
-{
-  char buf[id.len * 2 + 1];
-  buf[0] = '\0'; // just to be safe.
-  if (dbg_ctl_ssl_session_cache.on()) {
-    id.toString(buf, sizeof(buf));
-  }
-
-  Dbg(dbg_ctl_ssl_session_cache, "Looking for session with id '%s' in bucket %p", buf, this);
-
-  std::shared_lock lock(mutex, std::try_to_lock);
-  if (!lock.owns_lock()) {
-    Metrics::Counter::increment(ssl_rsb.session_cache_lock_contention);
-    if (SSLConfigParams::session_cache_skip_on_lock_contention) {
-      return false;
-    }
-    lock.lock();
-  }
-
-  PRINT_BUCKET("getSession")
-
-  auto entry = bucket_map.find(id);
-  if (entry == bucket_map.end()) {
-    Dbg(dbg_ctl_ssl_session_cache, "Session with id '%s' not found in bucket %p.", buf, this);
-    return false;
-  }
-  const unsigned char *loc = reinterpret_cast<const unsigned char *>(entry->second->asn1_data->data());
-  *sess                    = d2i_SSL_SESSION(nullptr, &loc, entry->second->len_asn1_data);
-  if (data != nullptr) {
-    ssl_session_cache_exdata *exdata = reinterpret_cast<ssl_session_cache_exdata *>(entry->second->extra_data->data());
-    *data                            = exdata;
-  }
-  return true;
-}
-
-void inline SSLSessionBucket::print(const char *ref_str) const
-{
-  /* NOTE: This method assumes you're already holding the bucket lock */
-  if (!dbg_ctl_ssl_session_cache_bucket.on()) {
-    return;
-  }
-
-  fprintf(stderr, "-------------- BUCKET %p (%s) ----------------\n", this, ref_str);
-  fprintf(stderr, "Current Size: %ld, Max Size: %zd\n", bucket_map.size(), SSLConfigParams::session_cache_max_bucket_size);
-  fprintf(stderr, "Bucket: \n");
-
-  for (auto &x : bucket_map) {
-    char s_buf[2 * x.second->session_id.len + 1];
-    x.second->session_id.toString(s_buf, sizeof(s_buf));
-    fprintf(stderr, "  %s\n", s_buf);
-  }
-}
-
-void inline SSLSessionBucket::removeOldestSession(const std::unique_lock<ts::shared_mutex> &lock)
-{
-  // Caller must hold the bucket shared_mutex with unique_lock.
-  ink_assert(lock.owns_lock());
-
-  PRINT_BUCKET("removeOldestSession before")
-
-  while (bucket_que.head && bucket_que.size >= static_cast<int>(SSLConfigParams::session_cache_max_bucket_size)) {
-    auto node = bucket_que.pop();
-    bucket_map.erase(node->session_id);
-    delete node;
-  }
-
-  PRINT_BUCKET("removeOldestSession after")
-}
-
-void
-SSLSessionBucket::removeSession(const SSLSessionID &id)
-{
-  // We can't bail on contention here because this session MUST be removed.
-  std::unique_lock lock(mutex);
-
-  PRINT_BUCKET("removeSession before")
-
-  auto entry = bucket_map.find(id);
-  if (entry != bucket_map.end()) {
-    auto node = entry->second;
-    bucket_que.remove(node);
-    bucket_map.erase(entry);
-    delete node;
-  }
-
-  PRINT_BUCKET("removeSession after")
-
-  return;
-}
-
 // Custom deleter for shared origin sessions
 void
 SSLSessDeleter(SSL_SESSION *_p)
@@ -332,11 +41,6 @@
   SSL_SESSION_free(_p);
 }
 
-/* Session Bucket */
-SSLSessionBucket::SSLSessionBucket() {}
-
-SSLSessionBucket::~SSLSessionBucket() {}
-
 SSLOriginSessionCache::SSLOriginSessionCache() {}
 
 SSLOriginSessionCache::~SSLOriginSessionCache() {}
diff --git a/src/iocore/net/SSLSessionCache.h b/src/iocore/net/SSLSessionCache.h
index 769c20a..ff78e8f 100644
--- a/src/iocore/net/SSLSessionCache.h
+++ b/src/iocore/net/SSLSessionCache.h
@@ -51,155 +51,6 @@
   char group_name[SSL_MAX_GROUP_NAME_SIZE] = {'\0'};
 };
 
-inline void
-hash_combine(uint64_t &seed, uint64_t hash)
-{
-  // using boost's version of hash combine, substituting magic number with a 64bit version
-  // https://www.boost.org/doc/libs/1_43_0/doc/html/hash/reference.html#boost.hash_combine
-  seed ^= hash + 0x9E3779B97F4A7C15 + (seed << 6) + (seed >> 2);
-}
-
-struct SSLSessionID : public TSSslSessionID {
-  SSLSessionID(const unsigned char *s, size_t l)
-  {
-    len = l;
-    ink_release_assert(l <= sizeof(bytes));
-    memcpy(bytes, s, l);
-    hash();
-  }
-
-  SSLSessionID(const SSLSessionID &other)
-  {
-    if (other.len) {
-      memcpy(bytes, other.bytes, other.len);
-    }
-
-    len = other.len;
-    hash();
-  }
-
-  bool
-  operator<(const SSLSessionID &other) const
-  {
-    if (len != other.len) {
-      return len < other.len;
-    }
-
-    return (memcmp(bytes, other.bytes, len) < 0);
-  }
-
-  SSLSessionID &
-  operator=(const SSLSessionID &other)
-  {
-    if (other.len) {
-      memcpy(bytes, other.bytes, other.len);
-    }
-
-    len = other.len;
-    return *this;
-  }
-
-  bool
-  operator==(const SSLSessionID &other) const
-  {
-    if (len != other.len) {
-      return false;
-    }
-
-    // memcmp returns 0 on equal
-    return (memcmp(bytes, other.bytes, len) == 0);
-  }
-
-  const char *
-  toString(char *buf, size_t buflen) const
-  {
-    char *cur_pos = buf;
-    for (size_t i = 0; i < len && buflen > 0; ++i) {
-      if (buflen > 2) { // we have enough space for 3 bytes, 2 hex and 1 null terminator
-        snprintf(cur_pos, 3 /* including a null terminator */, "%02hhX", static_cast<unsigned char>(bytes[i]));
-        cur_pos += 2;
-        buflen  -= 2;
-      } else { // not enough space for any more hex bytes, just null terminate
-        *cur_pos = '\0';
-        break;
-      }
-    }
-    return buf;
-  }
-
-  uint64_t
-  hash() const
-  {
-    // because the session ids should be uniformly random, we can treat the bits as a hash value
-    // however we need to combine them if the length is longer than 64bits
-    if (len >= sizeof(uint64_t)) {
-      uint64_t seed = 0;
-      for (uint64_t i = 0; i < len; i += sizeof(uint64_t)) {
-        hash_combine(seed, static_cast<uint64_t>(bytes[i]));
-      }
-      return seed;
-    } else if (len) {
-      return static_cast<uint64_t>(bytes[0]);
-    } else {
-      return 0;
-    }
-  }
-};
-
-class SSLSession
-{
-public:
-  SSLSessionID      session_id;
-  Ptr<IOBufferData> asn1_data; /* this is the ASN1 representation of the SSL_CTX */
-  size_t            len_asn1_data;
-  Ptr<IOBufferData> extra_data;
-
-  SSLSession(const SSLSessionID &id, const Ptr<IOBufferData> &ssl_asn1_data, size_t len_asn1, Ptr<IOBufferData> &exdata)
-    : session_id(id), asn1_data(ssl_asn1_data), len_asn1_data(len_asn1), extra_data(exdata)
-  {
-  }
-
-  LINK(SSLSession, link);
-};
-
-class SSLSessionBucket
-{
-public:
-  SSLSessionBucket();
-  ~SSLSessionBucket();
-  void insertSession(const SSLSessionID &sid, SSL_SESSION *sess, SSL *ssl);
-  bool getSession(const SSLSessionID &sid, SSL_SESSION **sess, ssl_session_cache_exdata **data);
-  int  getSessionBuffer(const SSLSessionID &sid, char *buffer, int &len);
-  void removeSession(const SSLSessionID &sid);
-
-private:
-  /* these method must be used while hold the lock */
-  void print(const char *) const;
-  void removeOldestSession(const std::unique_lock<ts::shared_mutex> &lock);
-
-  mutable ts::shared_mutex             mutex;
-  CountQueue<SSLSession>               bucket_que;
-  std::map<SSLSessionID, SSLSession *> bucket_map;
-};
-
-class SSLSessionCache
-{
-public:
-  bool getSession(const SSLSessionID &sid, SSL_SESSION **sess, ssl_session_cache_exdata **data) const;
-  int  getSessionBuffer(const SSLSessionID &sid, char *buffer, int &len) const;
-  void insertSession(const SSLSessionID &sid, SSL_SESSION *sess, SSL *ssl);
-  void removeSession(const SSLSessionID &sid);
-  SSLSessionCache();
-  ~SSLSessionCache();
-
-  SSLSessionCache(const SSLSessionCache &)            = delete;
-  SSLSessionCache &operator=(const SSLSessionCache &) = delete;
-
-private:
-  SSLSessionBucket *session_bucket = nullptr;
-  size_t            nbuckets;
-};
-
 class SSLOriginSession
 {
 public:
diff --git a/src/iocore/net/SSLStats.cc b/src/iocore/net/SSLStats.cc
index fdec94b..e6bfdcb 100644
--- a/src/iocore/net/SSLStats.cc
+++ b/src/iocore/net/SSLStats.cc
@@ -125,7 +125,6 @@
 SSLPeriodicMetricsUpdate()
 {
   SSLCertificateConfig::scoped_config certLookup;
-  SSLConfig::scoped_config            sslConfig;
 
   int64_t sessions = 0;
   int64_t hits     = 0;
@@ -133,43 +132,6 @@
   int64_t timeouts = 0;
 
   Dbg(dbg_ctl_ssl, "Starting to update the new session metrics");
-
-  // Check if we're using the ATS session cache implementation rather than the
-  // OpenSSL internal cache.
-  bool const using_ats_session_cache =
-    sslConfig && sslConfig->ssl_session_cache == SSLConfigParams::SSL_SESSION_CACHE_MODE_SERVER_ATS_IMPL;
-
-  if (using_ats_session_cache) {
-    // Most of the SSL_CTX_sess_*() metrics are inclusive of OpenSSL's
-    // "internal" cache *and* the ATS "external" cache. The exception is the
-    // SSL_CTX_sess_misses() metric, which curiously only counts OpenSSL
-    // internal misses. Therefore, to make that metric accurate for the
-    // situation where ATS manages sessions via its own cache, which is the
-    // default configuration (see proxy.config.ssl.session_cache.value), we
-    // have to add in the misses we've counted in the
-    // TLSSessionResumptionSupport.cc callback hooks.
-
-    // We count timeouts as misses in TLSSessionResumptionSupport.cc for
-    // session_cache_miss, whereas OpenSSL tracks them separately and our
-    // user_agent_session_miss follows suit.
-    int64_t session_cache_timeouts = 0;
-    if (ssl_rsb.session_cache_timeout) {
-      session_cache_timeouts = Metrics::Counter::load(ssl_rsb.session_cache_timeout);
-    }
-#if defined(OPENSSL_IS_BORINGSSL)
-    // On BoringSSL, all SSL_CTX_sess_*() functions always return 0 for the ATS
-    // external cache, making them unusable for monitoring. We currently address
-    // hits and misses because they are the most relevant metrics for session
-    // cache performance monitoring and should be treated as a pair.
-    if (ssl_rsb.session_cache_hit) {
-      hits = Metrics::Counter::load(ssl_rsb.session_cache_hit);
-    }
-#endif
-    if (ssl_rsb.session_cache_miss) {
-      misses  = Metrics::Counter::load(ssl_rsb.session_cache_miss);
-      misses -= (session_cache_timeouts > misses) ? 0 : session_cache_timeouts;
-    }
-  }
   if (certLookup) {
     const unsigned ctxCount = certLookup->count();
     for (size_t i = 0; i < ctxCount; i++) {
@@ -240,7 +202,6 @@
   ssl_rsb.session_cache_lock_contention      = Metrics::Counter::createPtr("proxy.process.ssl.ssl_session_cache_lock_contention");
   ssl_rsb.session_cache_miss                 = Metrics::Counter::createPtr("proxy.process.ssl.ssl_session_cache_miss");
   ssl_rsb.session_cache_new_session          = Metrics::Counter::createPtr("proxy.process.ssl.ssl_session_cache_new_session");
-  ssl_rsb.session_cache_timeout              = Metrics::Counter::createPtr("proxy.process.ssl.ssl_session_cache_timeout");
   ssl_rsb.total_attempts_handshake_count_in  = Metrics::Counter::createPtr("proxy.process.ssl.total_attempts_handshake_count_in");
   ssl_rsb.total_attempts_handshake_count_out = Metrics::Counter::createPtr("proxy.process.ssl.total_attempts_handshake_count_out");
   ssl_rsb.total_dyn_def_tls_record_count     = Metrics::Counter::createPtr("proxy.process.ssl.default_record_size_count");
diff --git a/src/iocore/net/SSLStats.h b/src/iocore/net/SSLStats.h
index 82b8444..152abf3 100644
--- a/src/iocore/net/SSLStats.h
+++ b/src/iocore/net/SSLStats.h
@@ -62,7 +62,6 @@
   Metrics::Counter::AtomicType *session_cache_hit                              = nullptr;
   Metrics::Counter::AtomicType *session_cache_lock_contention                  = nullptr;
   Metrics::Counter::AtomicType *session_cache_miss                             = nullptr;
-  Metrics::Counter::AtomicType *session_cache_timeout                          = nullptr;
   Metrics::Counter::AtomicType *session_cache_new_session                      = nullptr;
   Metrics::Counter::AtomicType *sni_name_set_failure                           = nullptr;
   Metrics::Counter::AtomicType *total_attempts_handshake_count_in              = nullptr;
@@ -100,11 +99,11 @@
   Metrics::Counter::AtomicType *user_agent_version_too_low                     = nullptr;
   Metrics::Counter::AtomicType *user_agent_wrong_version                       = nullptr;
 
-  // Note: The following user_agent_session_* metrics are implemented as Gauge types
-  // even though they semantically represent cumulative counters. This is because
-  // they are periodically synchronized from external counter sources (OpenSSL's
-  // built-in session cache or ATS's session cache) and need to be "set" to specific
-  // values rather than incremented. From a monitoring perspective, these should be
+  // Note: The following user_agent_session_* metrics are implemented as Gauge
+  // types even though they semantically represent cumulative counters. This is
+  // because they are periodically synchronized from external counter sources
+  // (OpenSSL's built-in session cache) and need to be "set" to specific values
+  // rather than incremented. From a monitoring perspective, these should be
   // treated as counters for calculating rates.
   Metrics::Gauge::AtomicType *user_agent_session_hit     = nullptr;
   Metrics::Gauge::AtomicType *user_agent_session_miss    = nullptr;
diff --git a/src/iocore/net/SSLUtils.cc b/src/iocore/net/SSLUtils.cc
index 0b7bc98..b5bcfe9 100644
--- a/src/iocore/net/SSLUtils.cc
+++ b/src/iocore/net/SSLUtils.cc
@@ -32,6 +32,7 @@
 #include "SSLDynlock.h" // IWYU pragma: keep - for ssl_dyn_*
 
 #include "iocore/net/SSLMultiCertConfigLoader.h"
+#include "config/ssl_multicert.h"
 #include "iocore/net/SSLAPIHooks.h"
 #include "iocore/net/SSLDiags.h"
 #include "iocore/net/TLSSessionResumptionSupport.h"
@@ -74,19 +75,7 @@
 
 using namespace std::literals;
 
-// ssl_multicert.config field names:
-static constexpr std::string_view SSL_IP_TAG("dest_ip"sv);
-static constexpr std::string_view SSL_CERT_TAG("ssl_cert_name"sv);
-static constexpr std::string_view SSL_PRIVATE_KEY_TAG("ssl_key_name"sv);
-static constexpr std::string_view SSL_OCSP_RESPONSE_TAG("ssl_ocsp_name"sv);
-static constexpr std::string_view SSL_CA_TAG("ssl_ca_name"sv);
-static constexpr std::string_view SSL_ACTION_TAG("action"sv);
-static constexpr std::string_view SSL_ACTION_TUNNEL_TAG("tunnel"sv);
-static constexpr std::string_view SSL_SESSION_TICKET_ENABLED("ssl_ticket_enabled"sv);
-static constexpr std::string_view SSL_SESSION_TICKET_NUMBER("ssl_ticket_number"sv);
-static constexpr std::string_view SSL_KEY_DIALOG("ssl_key_dialog"sv);
-static constexpr std::string_view SSL_SERVERNAME("dest_fqdn"sv);
-static constexpr char             SSL_CERT_SEPARATE_DELIM = ',';
+static constexpr char SSL_CERT_SEPARATE_DELIM = ',';
 
 #ifndef evp_md_func
 #ifdef OPENSSL_NO_SHA256
@@ -96,8 +85,6 @@
 #endif
 #endif
 
-SSLSessionCache *session_cache; // declared extern in P_SSLConfig.h
-
 static int ssl_vc_index = -1;
 
 static ink_mutex *mutex_buf            = nullptr;
@@ -182,92 +169,6 @@
   return SSL_CTX_add_extra_chain_cert_bio(ctx, bio.get());
 }
 
-static SSL_SESSION *
-#if defined(LIBRESSL_VERSION_NUMBER)
-ssl_get_cached_session(SSL *ssl, unsigned char *id, int len, int *copy)
-#else
-ssl_get_cached_session(SSL *ssl, const unsigned char *id, int len, int *copy)
-#endif
-{
-  TLSSessionResumptionSupport *srs = TLSSessionResumptionSupport::getInstance(ssl);
-
-  ink_assert(srs);
-  if (srs) {
-    return srs->getSession(ssl, id, len, copy);
-  }
-
-  return nullptr;
-}
-
-static int
-ssl_new_cached_session(SSL *ssl, SSL_SESSION *sess)
-{
-#ifdef TLS1_3_VERSION
-  if (SSL_SESSION_get_protocol_version(sess) == TLS1_3_VERSION) {
-    return 0;
-  }
-#endif
-
-  unsigned int         len = 0;
-  const unsigned char *id  = SSL_SESSION_get_id(sess, &len);
-
-  SSLSessionID sid(id, len);
-
-  if (diags()->on()) {
-    static DbgCtl dbg_ctl("ssl_session_cache.insert");
-    if (dbg_ctl.tag_on()) {
-      char printable_buf[(len * 2) + 1];
-
-      sid.toString(printable_buf, sizeof(printable_buf));
-      DbgPrint(dbg_ctl, "ssl_new_cached_session session '%s' and context %p", printable_buf, SSL_get_SSL_CTX(ssl));
-    }
-  }
-
-  Metrics::Counter::increment(ssl_rsb.session_cache_new_session);
-  session_cache->insertSession(sid, sess, ssl);
-
-  // Call hook after new session is created
-  APIHook *hook = SSLAPIHooks::instance()->get(TSSslHookInternalID(TS_SSL_SESSION_HOOK));
-  while (hook) {
-    hook->invoke(TS_EVENT_SSL_SESSION_NEW, &sid);
-    hook = hook->m_link.next;
-  }
-
-  return 0;
-}
-
-static void
-ssl_rm_cached_session(SSL_CTX * /* ctx ATS_UNUSED */, SSL_SESSION *sess)
-{
-#ifdef TLS1_3_VERSION
-  if (SSL_SESSION_get_protocol_version(sess) == TLS1_3_VERSION) {
-    return;
-  }
-#endif
-
-  unsigned int         len = 0;
-  const unsigned char *id  = SSL_SESSION_get_id(sess, &len);
-  SSLSessionID         sid(id, len);
-
-  // Call hook before session is removed
-  APIHook *hook = SSLAPIHooks::instance()->get(TSSslHookInternalID(TS_SSL_SESSION_HOOK));
-  while (hook) {
-    hook->invoke(TS_EVENT_SSL_SESSION_REMOVE, &sid);
-    hook = hook->m_link.next;
-  }
-
-  if (diags()->on()) {
-    static DbgCtl dbg_ctl("ssl_session_cache.remove");
-    if (dbg_ctl.tag_on()) {
-      char printable_buf[(len * 2) + 1];
-      sid.toString(printable_buf, sizeof(printable_buf));
-      DbgPrint(dbg_ctl, "ssl_rm_cached_session cached session '%s'", printable_buf);
-    }
-  }
-
-  session_cache->removeSession(sid);
-}
-
 // Callback function for verifying client certificate
 static int
 ssl_verify_client_callback(int preverify_ok, X509_STORE_CTX *ctx)
@@ -402,12 +303,15 @@
     }
 
     // Reset the ticket callback if needed
-    SSL_CTX *ctx = SSL_get_SSL_CTX(ssl);
+    SSL_CTX                        *ctx                  = SSL_get_SSL_CTX(ssl);
+    shared_SSLMultiCertConfigParams sslMultiCertSettings = std::make_shared<SSLMultiCertConfigParams>();
+    if (sslMultiCertSettings->session_ticket_enabled != 0) {
 #ifdef HAVE_SSL_CTX_SET_TLSEXT_TICKET_KEY_EVP_CB
-    SSL_CTX_set_tlsext_ticket_key_evp_cb(ctx, ssl_callback_session_ticket);
+      SSL_CTX_set_tlsext_ticket_key_evp_cb(ctx, ssl_callback_session_ticket);
 #else
-    SSL_CTX_set_tlsext_ticket_key_cb(ctx, ssl_callback_session_ticket);
+      SSL_CTX_set_tlsext_ticket_key_cb(ctx, ssl_callback_session_ticket);
 #endif
+    }
   }
 #endif
 
@@ -600,9 +504,9 @@
     Metrics::Counter::increment(ssl_rsb.total_ticket_keys_renewed);
   }
 
-// Setting the callback can only fail if OpenSSL does not recognize the
-// SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB constant. we set the callback first
-// so that we don't leave a ticket_key pointer attached if it fails.
+  // Setting the callback can only fail if OpenSSL does not recognize the
+  // SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB constant. we set the callback first
+  // so that we don't leave a ticket_key pointer attached if it fails.
 #ifdef HAVE_SSL_CTX_SET_TLSEXT_TICKET_KEY_EVP_CB
   if (SSL_CTX_set_tlsext_ticket_key_evp_cb(ctx, ssl_callback_session_ticket) == 0) {
 #else
@@ -1242,9 +1146,7 @@
       SSL_CTX_set_max_proto_version(ctx, ver);
     }
 
-    if (!this->_setup_session_cache(ctx)) {
-      goto fail;
-    }
+    SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF | SSL_SESS_CACHE_NO_INTERNAL);
 
 #ifdef SSL_MODE_RELEASE_BUFFERS
     Dbg(dbg_ctl_ssl_load, "enabling SSL_MODE_RELEASE_BUFFERS");
@@ -1347,46 +1249,8 @@
 }
 
 bool
-SSLMultiCertConfigLoader::_setup_session_cache(SSL_CTX *ctx)
+SSLMultiCertConfigLoader::_setup_session_cache(SSL_CTX * /* ctx ATS_UNUSED */)
 {
-  const SSLConfigParams *params = this->_params;
-
-  Dbg(dbg_ctl_ssl_session_cache,
-      "ssl context=%p: using session cache options, enabled=%d, size=%d, num_buckets=%d, "
-      "skip_on_contention=%d, timeout=%d, auto_clear=%d",
-      ctx, params->ssl_session_cache, params->ssl_session_cache_size, params->ssl_session_cache_num_buckets,
-      params->ssl_session_cache_skip_on_contention, params->ssl_session_cache_timeout, params->ssl_session_cache_auto_clear);
-
-  if (params->ssl_session_cache_timeout) {
-    SSL_CTX_set_timeout(ctx, params->ssl_session_cache_timeout);
-  }
-
-  int additional_cache_flags  = 0;
-  additional_cache_flags     |= (params->ssl_session_cache_auto_clear == 0) ? SSL_SESS_CACHE_NO_AUTO_CLEAR : 0;
-
-  switch (params->ssl_session_cache) {
-  case SSLConfigParams::SSL_SESSION_CACHE_MODE_OFF:
-    Dbg(dbg_ctl_ssl_session_cache, "disabling SSL session cache");
-
-    SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF | SSL_SESS_CACHE_NO_INTERNAL);
-    break;
-  case SSLConfigParams::SSL_SESSION_CACHE_MODE_SERVER_OPENSSL_IMPL:
-    Dbg(dbg_ctl_ssl_session_cache, "enabling SSL session cache with OpenSSL implementation");
-
-    SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_SERVER | additional_cache_flags);
-    SSL_CTX_sess_set_cache_size(ctx, params->ssl_session_cache_size);
-    break;
-  case SSLConfigParams::SSL_SESSION_CACHE_MODE_SERVER_ATS_IMPL: {
-    Dbg(dbg_ctl_ssl_session_cache, "enabling SSL session cache with ATS implementation");
-    /* Add all the OpenSSL callbacks */
-    SSL_CTX_sess_set_new_cb(ctx, ssl_new_cached_session);
-    SSL_CTX_sess_set_remove_cb(ctx, ssl_rm_cached_session);
-    SSL_CTX_sess_set_get_cb(ctx, ssl_get_cached_session);
-
-    SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_SERVER | SSL_SESS_CACHE_NO_INTERNAL | additional_cache_flags);
-    break;
-  }
-  }
   return true;
 }
 
@@ -1408,7 +1272,7 @@
     } else if (strcmp(sslMultCertSettings->dialog, "builtin") == 0) {
       passwd_cb = ssl_private_key_passphrase_callback_builtin;
     } else { // unknown config
-      SSLError("unknown %s configuration value '%s'", SSL_KEY_DIALOG.data(), (const char *)sslMultCertSettings->dialog);
+      SSLError("unknown ssl_key_dialog configuration value '%s'", (const char *)sslMultCertSettings->dialog);
       return false;
     }
     SSL_CTX_set_default_passwd_cb(ctx, passwd_cb);
@@ -1424,7 +1288,7 @@
   // serverCACertFilename if that is not nullptr.  Otherwise, it uses the hashed
   // symlinks in serverCACertPath.
   //
-  // if ssl_ca_name is NOT configured for this cert in ssl_multicert.config
+  // if ssl_ca_name is NOT configured for this cert in ssl_multicert.yaml
   //     AND
   // if proxy.config.ssl.CA.cert.filename and proxy.config.ssl.CA.cert.path
   //     are configured
@@ -1844,144 +1708,76 @@
   return ctx.get();
 }
 
-static bool
-ssl_extract_certificate(const matcher_line *line_info, SSLMultiCertConfigParams *sslMultCertSettings)
-{
-  for (int i = 0; i < MATCHER_MAX_TOKENS; ++i) {
-    const char *label;
-    const char *value;
-
-    label = line_info->line[0][i];
-    value = line_info->line[1][i];
-
-    if (label == nullptr) {
-      continue;
-    }
-    Dbg(dbg_ctl_ssl_load, "Extracting certificate label: %s, value: %s", label, value);
-
-    if (strcasecmp(label, SSL_IP_TAG) == 0) {
-      sslMultCertSettings->addr = ats_strdup(value);
-    }
-
-    if (strcasecmp(label, SSL_CERT_TAG) == 0) {
-      sslMultCertSettings->cert = ats_strdup(value);
-    }
-
-    if (strcasecmp(label, SSL_CA_TAG) == 0) {
-      sslMultCertSettings->ca = ats_strdup(value);
-    }
-
-    if (strcasecmp(label, SSL_PRIVATE_KEY_TAG) == 0) {
-      sslMultCertSettings->key = ats_strdup(value);
-    }
-
-    if (strcasecmp(label, SSL_OCSP_RESPONSE_TAG) == 0) {
-      sslMultCertSettings->ocsp_response = ats_strdup(value);
-    }
-
-    if (strcasecmp(label, SSL_SESSION_TICKET_ENABLED) == 0) {
-      sslMultCertSettings->session_ticket_enabled = atoi(value);
-    }
-
-    if (strcasecmp(label, SSL_SESSION_TICKET_NUMBER) == 0) {
-      sslMultCertSettings->session_ticket_number = atoi(value);
-    }
-
-    if (strcasecmp(label, SSL_KEY_DIALOG) == 0) {
-      sslMultCertSettings->dialog = ats_strdup(value);
-    }
-
-    if (strcasecmp(label, SSL_SERVERNAME) == 0) {
-      sslMultCertSettings->servername = ats_strdup(value);
-    }
-
-    if (strcasecmp(label, SSL_ACTION_TAG) == 0) {
-      if (strcasecmp(SSL_ACTION_TUNNEL_TAG, value) == 0) {
-        sslMultCertSettings->opt = SSLCertContextOption::OPT_TUNNEL;
-      } else {
-        Error("Unrecognized action for %s", SSL_ACTION_TAG.data());
-        return false;
-      }
-    }
-  }
-  // TS-4679:  It is ok to be missing the cert.  At least if the action is set to tunnel
-  if (sslMultCertSettings->cert) {
-    SimpleTokenizer cert_tok(sslMultCertSettings->cert, SSL_CERT_SEPARATE_DELIM);
-    const char     *first_cert = cert_tok.getNext();
-    if (first_cert) {
-      sslMultCertSettings->first_cert = ats_strdup(first_cert);
-    }
-  }
-
-  return true;
-}
-
 swoc::Errata
 SSLMultiCertConfigLoader::load(SSLCertLookup *lookup)
 {
   const SSLConfigParams *params = this->_params;
 
-  char        *tok_state = nullptr;
-  char        *line      = nullptr;
-  unsigned     line_num  = 0;
-  matcher_line line_info;
-
-  const matcher_tags sslCertTags = {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, false};
-
   Note("(%s) %s loading ...", this->_debug_tag(), ts::filename::SSL_MULTICERT);
 
-  std::error_code ec;
-  std::string     content{swoc::file::load(swoc::file::path{params->configFilePath}, ec)};
-  if (ec) {
-    switch (ec.value()) {
-    case ENOENT:
-      // missing config file is an acceptable runtime state
-      return swoc::Errata(ERRATA_WARN, "Cannot open SSL certificate configuration \"{}\" - {}", params->configFilePath, ec);
-    default:
-      return swoc::Errata(ERRATA_ERROR, "Failed to read SSL certificate configuration from \"{}\" - {}", params->configFilePath,
-                          ec);
-    }
-  }
-
   // Optionally elevate/allow file access to read root-only
   // certificates. The destructor will drop privilege for us.
   uint32_t elevate_setting = 0;
   elevate_setting          = RecGetRecordInt("proxy.config.ssl.cert.load_elevated").value_or(0);
   ElevateAccess elevate_access(elevate_setting ? ElevateAccess::FILE_PRIVILEGE : 0);
 
-  line = tokLine(content.data(), &tok_state);
+  // Guard against nullptr configFilePath which can happen if records aren't initialized.
+  if (params->configFilePath == nullptr) {
+    return swoc::Errata(ERRATA_WARN, "No SSL certificate configuration file path configured");
+  }
+
+  config::SSLMultiCertParser                       parser;
+  config::ConfigResult<config::SSLMultiCertConfig> parse_result = parser.parse(params->configFilePath);
+  if (!parse_result.ok()) {
+    return std::move(parse_result.errata);
+  }
+
   swoc::Errata errata(ERRATA_NOTE);
-  while (line != nullptr) {
-    line_num++;
+  int          item_num = 0;
 
-    // Skip all blank spaces at beginning of line.
-    while (*line && isspace(*line)) {
-      line++;
+  for (const auto &item : parse_result.value) {
+    item_num++;
+    shared_SSLMultiCertConfigParams sslMultiCertSettings = std::make_shared<SSLMultiCertConfigParams>();
+
+    if (!item.ssl_cert_name.empty()) {
+      sslMultiCertSettings->cert = ats_strdup(item.ssl_cert_name.c_str());
+    }
+    if (!item.dest_ip.empty()) {
+      sslMultiCertSettings->addr = ats_strdup(item.dest_ip.c_str());
+    }
+    if (!item.ssl_key_name.empty()) {
+      sslMultiCertSettings->key = ats_strdup(item.ssl_key_name.c_str());
+    }
+    if (!item.ssl_ca_name.empty()) {
+      sslMultiCertSettings->ca = ats_strdup(item.ssl_ca_name.c_str());
+    }
+    if (!item.ssl_ocsp_name.empty()) {
+      sslMultiCertSettings->ocsp_response = ats_strdup(item.ssl_ocsp_name.c_str());
+    }
+    if (!item.ssl_key_dialog.empty()) {
+      sslMultiCertSettings->dialog = ats_strdup(item.ssl_key_dialog.c_str());
+    }
+    if (!item.dest_fqdn.empty()) {
+      sslMultiCertSettings->servername = ats_strdup(item.dest_fqdn.c_str());
+    }
+    if (item.ssl_ticket_enabled.has_value()) {
+      sslMultiCertSettings->session_ticket_enabled = item.ssl_ticket_enabled.value();
+    }
+    if (item.ssl_ticket_number.has_value()) {
+      sslMultiCertSettings->session_ticket_number = item.ssl_ticket_number.value();
+    }
+    if (item.action == "tunnel") {
+      sslMultiCertSettings->opt = SSLCertContextOption::OPT_TUNNEL;
     }
 
-    if (*line != '\0' && *line != '#') {
-      shared_SSLMultiCertConfigParams sslMultiCertSettings = std::make_shared<SSLMultiCertConfigParams>();
-      const char                     *errPtr;
-
-      errPtr = parseConfigLine(line, &line_info, &sslCertTags);
-      Dbg(dbg_ctl_ssl_load, "currently parsing %s at line %d from config file: %s", line, line_num, params->configFilePath);
-      if (errPtr != nullptr) {
-        Warning("%s: discarding %s entry at line %d: %s", __func__, params->configFilePath, line_num, errPtr);
-      } else {
-        if (ssl_extract_certificate(&line_info, sslMultiCertSettings.get())) {
-          // There must be a certificate specified unless the tunnel action is set
-          if (sslMultiCertSettings->cert || sslMultiCertSettings->opt != SSLCertContextOption::OPT_TUNNEL) {
-            if (!this->_store_ssl_ctx(lookup, sslMultiCertSettings)) {
-              errata.note(ERRATA_ERROR, "Failed to load certificate on line {}", line_num);
-            }
-          } else {
-            errata.note(ERRATA_WARN, "No ssl_cert_name specified and no tunnel action set on line {}", line_num);
-          }
-        }
+    // There must be a certificate specified unless the tunnel action is set.
+    if (sslMultiCertSettings->cert || sslMultiCertSettings->opt == SSLCertContextOption::OPT_TUNNEL) {
+      if (!this->_store_ssl_ctx(lookup, sslMultiCertSettings)) {
+        errata.note(ERRATA_ERROR, "Failed to load certificate at item {}", item_num);
       }
+    } else {
+      errata.note(ERRATA_WARN, "No ssl_cert_name specified and no tunnel action set at item {}", item_num);
     }
-
-    line = tokLine(nullptr, &tok_state);
   }
 
   // We *must* have a default context even if it can't possibly work. The default context is used to
diff --git a/src/iocore/net/TLSSessionResumptionSupport.cc b/src/iocore/net/TLSSessionResumptionSupport.cc
index 1fac030..1bd93bc 100644
--- a/src/iocore/net/TLSSessionResumptionSupport.cc
+++ b/src/iocore/net/TLSSessionResumptionSupport.cc
@@ -164,56 +164,6 @@
   return this->_sslGroupName;
 }
 
-SSL_SESSION *
-TLSSessionResumptionSupport::getSession(SSL *ssl, const unsigned char *id, int len, int *copy)
-{
-  SSLSessionID sid(id, len);
-
-  *copy = 0;
-  if (diags()->on()) {
-    static DbgCtl dbg_ctl("ssl.session_cache.get");
-    if (dbg_ctl.tag_on()) {
-      char printable_buf[(len * 2) + 1];
-      sid.toString(printable_buf, sizeof(printable_buf));
-      DbgPrint(dbg_ctl, "ssl_get_cached_session cached session '%s' context %p", printable_buf, SSL_get_SSL_CTX(ssl));
-    }
-  }
-
-  APIHook *hook = SSLAPIHooks::instance()->get(TSSslHookInternalID(TS_SSL_SESSION_HOOK));
-  while (hook) {
-    hook->invoke(TS_EVENT_SSL_SESSION_GET, &sid);
-    hook = hook->m_link.next;
-  }
-
-  SSL_SESSION              *session = nullptr;
-  ssl_session_cache_exdata *exdata  = nullptr;
-  if (session_cache->getSession(sid, &session, &exdata)) {
-    ink_assert(session);
-    ink_assert(exdata);
-
-    // Double check the timeout
-    if (is_ssl_session_timed_out(session)) {
-      Metrics::Counter::increment(ssl_rsb.session_cache_miss);
-      Metrics::Counter::increment(ssl_rsb.session_cache_timeout);
-// Due to bug in openssl, the timeout is checked, but only removed
-// from the openssl built-in hash table.  The external remove cb is not called
-#if 0 // This is currently eliminated, since it breaks things in odd ways (see TS-3710)
-      ssl_rm_cached_session(SSL_get_SSL_CTX(ssl), session);
-#endif
-      SSL_SESSION_free(session);
-      session = nullptr;
-    } else {
-      Metrics::Counter::increment(ssl_rsb.session_cache_hit);
-      this->_setResumptionType(ResumptionType::RESUMED_FROM_SESSION_CACHE, !IS_RESUMED_ORIGIN_SESSION);
-      this->_setSSLCurveNID(exdata->curve);
-      this->_setSSLGroupName(exdata->group_name);
-    }
-  } else {
-    Metrics::Counter::increment(ssl_rsb.session_cache_miss);
-  }
-  return session;
-}
-
 std::shared_ptr<SSL_SESSION>
 TLSSessionResumptionSupport::getOriginSession(const std::string &lookup_key)
 {
diff --git a/src/proxy/hdrs/MIME.cc b/src/proxy/hdrs/MIME.cc
index 869468a..07ccd91 100644
--- a/src/proxy/hdrs/MIME.cc
+++ b/src/proxy/hdrs/MIME.cc
@@ -3767,6 +3767,9 @@
 
         for (s = csv_iter.get_first(field, &len); s != nullptr; s = csv_iter.get_next(&len)) {
           e = s + len;
+          // Store set mask bits from this CSV value so we can clear them if needed.
+          uint32_t csv_value_mask = 0;
+
           for (c = s; (c < e) && (ParseRules::is_token(*c)); c++) {
             ;
           }
@@ -3781,6 +3784,7 @@
             HdrTokenHeapPrefix *p                  = hdrtoken_wks_to_prefix(token_wks);
             mask                                   = p->wks_type_specific.u.cache_control.cc_mask;
             m_cooked_stuff.m_cache_control.m_mask |= mask;
+            csv_value_mask                        |= mask;
 
 #if TRACK_COOKING
             Dbg(dbg_ctl_http, "                        set mask 0x%0X", mask);
@@ -3789,27 +3793,72 @@
             if (mask & (MIME_COOKED_MASK_CC_MAX_AGE | MIME_COOKED_MASK_CC_S_MAXAGE | MIME_COOKED_MASK_CC_MAX_STALE |
                         MIME_COOKED_MASK_CC_MIN_FRESH)) {
               int value;
+              // Per RFC 7230 Section 3.2.3, there should be no whitespace around '='.
+              const char *value_start = c;
 
-              if (mime_parse_integer(c, e, &value)) {
+              // Check if the next character is '=' (no space allowed before '=').
+              if (c < e && *c == '=') {
+                ++c; // Move past the '='
+
+                // Again: no whitespace after the '=' either. Keep in mind that values can be negative.
+                bool valid_syntax = (c < e) && (is_digit(*c) || *c == '-');
+
+                if (valid_syntax) {
+                  // Reset to value_start to let mime_parse_integer do its work.
+                  c = value_start;
+                  if (mime_parse_integer(c, e, &value)) {
 #if TRACK_COOKING
-                Dbg(dbg_ctl_http, "                        set integer value %d", value);
+                    Dbg(dbg_ctl_http, "                        set integer value %d", value);
 #endif
-                if (token_wks == MIME_VALUE_MAX_AGE.c_str()) {
-                  m_cooked_stuff.m_cache_control.m_secs_max_age = value;
-                } else if (token_wks == MIME_VALUE_MIN_FRESH.c_str()) {
-                  m_cooked_stuff.m_cache_control.m_secs_min_fresh = value;
-                } else if (token_wks == MIME_VALUE_MAX_STALE.c_str()) {
-                  m_cooked_stuff.m_cache_control.m_secs_max_stale = value;
-                } else if (token_wks == MIME_VALUE_S_MAXAGE.c_str()) {
-                  m_cooked_stuff.m_cache_control.m_secs_s_maxage = value;
+                    if (token_wks == MIME_VALUE_MAX_AGE.c_str()) {
+                      m_cooked_stuff.m_cache_control.m_secs_max_age = value;
+                    } else if (token_wks == MIME_VALUE_MIN_FRESH.c_str()) {
+                      m_cooked_stuff.m_cache_control.m_secs_min_fresh = value;
+                    } else if (token_wks == MIME_VALUE_MAX_STALE.c_str()) {
+                      m_cooked_stuff.m_cache_control.m_secs_max_stale = value;
+                    } else if (token_wks == MIME_VALUE_S_MAXAGE.c_str()) {
+                      m_cooked_stuff.m_cache_control.m_secs_s_maxage = value;
+                    }
+                  } else {
+#if TRACK_COOKING
+                    Dbg(dbg_ctl_http, "                        set integer value %d", INT_MAX);
+#endif
+                    if (token_wks == MIME_VALUE_MAX_STALE.c_str()) {
+                      m_cooked_stuff.m_cache_control.m_secs_max_stale = INT_MAX;
+                    }
+                  }
+                } else {
+                  // Syntax is malformed (e.g., whitespace after '=', quotes around value, or no value).
+                  // Treat this as unrecognized and clear the mask.
+                  csv_value_mask                         = 0;
+                  m_cooked_stuff.m_cache_control.m_mask &= ~mask;
                 }
               } else {
-#if TRACK_COOKING
-                Dbg(dbg_ctl_http, "                        set integer value %d", INT_MAX);
-#endif
-                if (token_wks == MIME_VALUE_MAX_STALE.c_str()) {
-                  m_cooked_stuff.m_cache_control.m_secs_max_stale = INT_MAX;
-                }
+                // No '=' found, or whitespace before '='. This is malformed.
+                // For directives that require values, this is an error.
+                // Clear the mask for this directive.
+                csv_value_mask                         = 0;
+                m_cooked_stuff.m_cache_control.m_mask &= ~mask;
+              }
+            }
+
+            // Detect whether there is any more non-whitespace content after the
+            // directive. This indicates an unrecognized or malformed directive.
+            // This can happen, for instance, if the host uses semicolons
+            // instead of commas as separators which is against RFC 7234 (see
+            // issue #12029). Regardless of the cause, this means we need to
+            // ignore the directive and clear any mask bits we set from it.
+            while (c < e && ParseRules::is_ws(*c)) {
+              ++c;
+            }
+            if (c < e) {
+              // There's non-whitespace content that wasn't parsed. This means
+              // that we cannot really understand what this directive is.
+              // Per RFC 7234 Section 5.2: "A cache MUST ignore unrecognized cache
+              // directives."
+              if (csv_value_mask != 0) {
+                // Reverse the mask that we set above.
+                m_cooked_stuff.m_cache_control.m_mask &= ~csv_value_mask;
               }
             }
           }
diff --git a/src/proxy/hdrs/unit_tests/test_HdrUtils.cc b/src/proxy/hdrs/unit_tests/test_HdrUtils.cc
index 119a4bb..befd37b 100644
--- a/src/proxy/hdrs/unit_tests/test_HdrUtils.cc
+++ b/src/proxy/hdrs/unit_tests/test_HdrUtils.cc
@@ -24,33 +24,110 @@
 #include <bitset>
 #include <initializer_list>
 #include <new>
+#include <vector>
 
 #include <catch2/catch_test_macros.hpp>
+#include <catch2/generators/catch_generators.hpp>
+#include <catch2/generators/catch_generators_range.hpp>
 
 #include "proxy/hdrs/HdrHeap.h"
 #include "proxy/hdrs/MIME.h"
 #include "proxy/hdrs/HdrUtils.h"
 
-TEST_CASE("HdrUtils", "[proxy][hdrutils]")
+// Parameterized test for HdrCsvIter parsing.
+TEST_CASE("HdrCsvIter", "[proxy][hdrutils]")
 {
-  static constexpr swoc::TextView text{"One: alpha\r\n"
-                                       "Two: alpha, bravo\r\n"
-                                       "Three: zwoop, \"A,B\" , , phil  , \"unterminated\r\n"
-                                       "Five: alpha, bravo, charlie\r\n"
-                                       "Four: itchi, \"ni, \\\"san\" , \"\" , \"\r\n"
-                                       "Five: delta, echo\r\n"
-                                       "\r\n"};
+  constexpr bool COMBINE_DUPLICATES = true;
 
-  static constexpr std::string_view ONE_TAG{"One"};
-  static constexpr std::string_view TWO_TAG{"Two"};
-  static constexpr std::string_view THREE_TAG{"Three"};
-  static constexpr std::string_view FOUR_TAG{"Four"};
-  static constexpr std::string_view FIVE_TAG{"Five"};
+  // Structure for parameterized HdrCsvIter tests.
+  struct CsvIterTestCase {
+    const char                   *description;
+    const char                   *header_text;
+    const char                   *field_name;
+    std::vector<std::string_view> expected_values;
+    bool                          combine_dups; // Parameter for get_first()
+  };
+
+  // Test cases for HdrCsvIter parsing.
+  // clang-format off
+  static const std::vector<CsvIterTestCase> csv_iter_test_cases = {
+    // Basic CSV parsing tests
+    {"single value",
+     "One: alpha\r\n\r\n",
+     "One",
+     {"alpha"},
+     COMBINE_DUPLICATES},
+
+    {"two values",
+     "Two: alpha, bravo\r\n\r\n",
+     "Two",
+     {"alpha", "bravo"},
+     COMBINE_DUPLICATES},
+
+    {"quoted values and escaping",
+     "Three: zwoop, \"A,B\" , , phil  , \"unterminated\r\n\r\n",
+     "Three",
+     {"zwoop", "A,B", "phil", "unterminated"},
+     COMBINE_DUPLICATES},
+
+    {"escaped quotes passed through",
+     "Four: itchi, \"ni, \\\"san\" , \"\" , \"\r\n\r\n",
+     "Four",
+     {"itchi", "ni, \\\"san"},
+     COMBINE_DUPLICATES},
+
+    {"duplicate fields combined",
+     "Five: alpha, bravo, charlie\r\nFive: delta, echo\r\n\r\n",
+     "Five",
+     {"alpha", "bravo", "charlie", "delta", "echo"},
+     COMBINE_DUPLICATES},
+
+    {"duplicate fields not combined",
+     "Five: alpha, bravo, charlie\r\nFive: delta, echo\r\n\r\n",
+     "Five",
+     {"alpha", "bravo", "charlie"},
+     !COMBINE_DUPLICATES},
+
+    // Cache-Control specific tests
+    {"Cache-Control: basic max-age and public",
+     "Cache-Control: max-age=30, public\r\n\r\n",
+     "Cache-Control",
+     {"max-age=30", "public"},
+     COMBINE_DUPLICATES},
+
+    {"Cache-Control: extension directives with values",
+     "Cache-Control: stale-if-error=1, stale-while-revalidate=60, no-cache\r\n\r\n",
+     "Cache-Control",
+     {"stale-if-error=1", "stale-while-revalidate=60", "no-cache"},
+     COMBINE_DUPLICATES},
+
+    {"Cache-Control: mixed directives",
+     "Cache-Control: public, max-age=300, s-maxage=600\r\n\r\n",
+     "Cache-Control",
+     {"public", "max-age=300", "s-maxage=600"},
+     COMBINE_DUPLICATES},
+
+    {"Cache-Control: semicolon separator treated as single value",
+     "Cache-Control: public; max-age=30\r\n\r\n",
+     "Cache-Control",
+     {"public; max-age=30"},
+     COMBINE_DUPLICATES},
+
+    {"Cache-Control: empty value",
+     "Cache-Control: \r\n\r\n",
+     "Cache-Control",
+     {},
+     COMBINE_DUPLICATES},
+  };
+  // clang-format on
+  auto test_case = GENERATE(from_range(csv_iter_test_cases));
+
+  CAPTURE(test_case.description, test_case.header_text);
 
   HdrHeap    *heap = new_HdrHeap(HdrHeap::DEFAULT_SIZE + 64);
   MIMEParser  parser;
-  char const *real_s = text.data();
-  char const *real_e = text.data_end();
+  char const *real_s = test_case.header_text;
+  char const *real_e = test_case.header_text + strlen(test_case.header_text);
   MIMEHdr     mime;
 
   mime.create(heap);
@@ -60,65 +137,26 @@
   REQUIRE(ParseResult::DONE == result);
 
   HdrCsvIter iter;
-
-  MIMEField *field{mime.field_find(ONE_TAG)};
+  MIMEField *field = mime.field_find(test_case.field_name);
   REQUIRE(field != nullptr);
 
-  auto value = iter.get_first(field);
-  REQUIRE(value == "alpha");
+  if (test_case.expected_values.empty()) {
+    auto value = iter.get_first(field, test_case.combine_dups);
+    REQUIRE(value.empty());
+  } else {
+    auto value = iter.get_first(field, test_case.combine_dups);
+    REQUIRE(value == test_case.expected_values[0]);
 
-  field = mime.field_find(TWO_TAG);
-  value = iter.get_first(field);
-  REQUIRE(value == "alpha");
-  value = iter.get_next();
-  REQUIRE(value == "bravo");
-  value = iter.get_next();
-  REQUIRE(value.empty());
+    for (size_t i = 1; i < test_case.expected_values.size(); ++i) {
+      value = iter.get_next();
+      REQUIRE(value == test_case.expected_values[i]);
+    }
 
-  field = mime.field_find(THREE_TAG);
-  value = iter.get_first(field);
-  REQUIRE(value == "zwoop");
-  value = iter.get_next();
-  REQUIRE(value == "A,B"); // quotes escape separator, and are stripped.
-  value = iter.get_next();
-  REQUIRE(value == "phil");
-  value = iter.get_next();
-  REQUIRE(value == "unterminated");
-  value = iter.get_next();
-  REQUIRE(value.empty());
+    // After all expected values, the next should be empty.
+    value = iter.get_next();
+    REQUIRE(value.empty());
+  }
 
-  field = mime.field_find(FOUR_TAG);
-  value = iter.get_first(field);
-  REQUIRE(value == "itchi");
-  value = iter.get_next();
-  REQUIRE(value == "ni, \\\"san"); // verify escaped quotes are passed through.
-  value = iter.get_next();
-  REQUIRE(value.empty());
-
-  // Check that duplicates are handled correctly.
-  field = mime.field_find(FIVE_TAG);
-  value = iter.get_first(field);
-  REQUIRE(value == "alpha");
-  value = iter.get_next();
-  REQUIRE(value == "bravo");
-  value = iter.get_next();
-  REQUIRE(value == "charlie");
-  value = iter.get_next();
-  REQUIRE(value == "delta");
-  value = iter.get_next();
-  REQUIRE(value == "echo");
-  value = iter.get_next();
-  REQUIRE(value.empty());
-
-  field = mime.field_find(FIVE_TAG);
-  value = iter.get_first(field, false);
-  REQUIRE(value == "alpha");
-  value = iter.get_next();
-  REQUIRE(value == "bravo");
-  value = iter.get_next();
-  REQUIRE(value == "charlie");
-  value = iter.get_next();
-  REQUIRE(value.empty());
   heap->destroy();
 }
 
@@ -207,3 +245,320 @@
   REQUIRE(0 == memcmp(swoc::TextView(buff, idx), text));
   heap->destroy();
 };
+
+// Test that malformed Cache-Control directives are properly ignored during cooking.
+// All malformed directives should result in mask == 0.
+TEST_CASE("Cache-Control Malformed Cooking", "[proxy][hdrutils]")
+{
+  struct MalformedCCTestCase {
+    const char *description;
+    const char *header_text;
+  };
+
+  // clang-format off
+  // These tests align with cache-tests.fyi/#cc-parse
+  static const std::vector<MalformedCCTestCase> malformed_cc_test_cases = {
+    // Separator issues
+    {"semicolon separator (should be comma)",
+     "Cache-Control: public; max-age=30\r\n\r\n"},
+
+    // Space around equals (cc-parse: max-age with space before/after =)
+    {"space before equals sign",
+     "Cache-Control: max-age =300\r\n\r\n"},
+
+    {"space after equals sign",
+     "Cache-Control: max-age= 300\r\n\r\n"},
+
+    {"space both before and after equals sign",
+     "Cache-Control: max-age = 300\r\n\r\n"},
+
+    // Quoted values (cc-parse: single-quoted max-age)
+    {"single quotes around value",
+     "Cache-Control: max-age='300'\r\n\r\n"},
+
+    {"double quotes around value",
+     "Cache-Control: max-age=\"300\"\r\n\r\n"},
+
+    // s-maxage variants
+    {"s-maxage with space before equals",
+     "Cache-Control: s-maxage =600\r\n\r\n"},
+
+    {"s-maxage with space after equals",
+     "Cache-Control: s-maxage= 600\r\n\r\n"},
+
+    // Invalid numeric values (cc-parse: decimal max-age)
+    {"decimal value in max-age (1.5)",
+     "Cache-Control: max-age=1.5\r\n\r\n"},
+
+    {"decimal value in max-age (3600.0)",
+     "Cache-Control: max-age=3600.0\r\n\r\n"},
+
+    {"decimal value starting with dot (.5)",
+     "Cache-Control: max-age=.5\r\n\r\n"},
+
+    {"decimal value in s-maxage",
+     "Cache-Control: s-maxage=1.5\r\n\r\n"},
+
+    // Leading and trailing alpha characters
+    {"leading alpha in max-age value",
+     "Cache-Control: max-age=a300\r\n\r\n"},
+
+    {"trailing alpha in max-age value",
+     "Cache-Control: max-age=300a\r\n\r\n"},
+
+    {"leading alpha in s-maxage value",
+     "Cache-Control: s-maxage=a600\r\n\r\n"},
+
+    {"trailing alpha in s-maxage value",
+     "Cache-Control: s-maxage=600a\r\n\r\n"},
+
+    // Empty and missing values
+    {"empty max-age value alone",
+     "Cache-Control: max-age=\r\n\r\n"},
+  };
+  // clang-format on
+
+  auto test_case = GENERATE(from_range(malformed_cc_test_cases));
+
+  CAPTURE(test_case.description, test_case.header_text);
+
+  HdrHeap    *heap = new_HdrHeap(HdrHeap::DEFAULT_SIZE + 64);
+  MIMEParser  parser;
+  char const *real_s = test_case.header_text;
+  char const *real_e = test_case.header_text + strlen(test_case.header_text);
+  MIMEHdr     mime;
+
+  mime.create(heap);
+  mime_parser_init(&parser);
+
+  auto result = mime_parser_parse(&parser, heap, mime.m_mime, &real_s, real_e, false, true, false);
+  REQUIRE(ParseResult::DONE == result);
+
+  mime.m_mime->recompute_cooked_stuff();
+
+  // All malformed directives should result in mask == 0.
+  auto mask = mime.get_cooked_cc_mask();
+  REQUIRE(mask == 0);
+
+  heap->destroy();
+}
+
+// Test that properly formed Cache-Control directives are correctly cooked.
+TEST_CASE("Cache-Control Valid Cooking", "[proxy][hdrutils]")
+{
+  struct ValidCCTestCase {
+    const char *description;
+    const char *header_text;
+    uint32_t    expected_mask;
+    int32_t     expected_max_age;
+    int32_t     expected_s_maxage;
+    int32_t     expected_max_stale;
+    int32_t     expected_min_fresh;
+  };
+
+  // Use 0 to indicate "don't care" for integer values (mask determines which are valid).
+  // clang-format off
+  static const std::vector<ValidCCTestCase> valid_cc_test_cases = {
+    // Basic directives without values
+    {"public only",
+     "Cache-Control: public\r\n\r\n",
+     MIME_COOKED_MASK_CC_PUBLIC,
+     0, 0, 0, 0},
+
+    {"private only",
+     "Cache-Control: private\r\n\r\n",
+     MIME_COOKED_MASK_CC_PRIVATE,
+     0, 0, 0, 0},
+
+    {"no-cache only",
+     "Cache-Control: no-cache\r\n\r\n",
+     MIME_COOKED_MASK_CC_NO_CACHE,
+     0, 0, 0, 0},
+
+    {"no-store only",
+     "Cache-Control: no-store\r\n\r\n",
+     MIME_COOKED_MASK_CC_NO_STORE,
+     0, 0, 0, 0},
+
+    {"no-transform only",
+     "Cache-Control: no-transform\r\n\r\n",
+     MIME_COOKED_MASK_CC_NO_TRANSFORM,
+     0, 0, 0, 0},
+
+    {"must-revalidate only",
+     "Cache-Control: must-revalidate\r\n\r\n",
+     MIME_COOKED_MASK_CC_MUST_REVALIDATE,
+     0, 0, 0, 0},
+
+    {"proxy-revalidate only",
+     "Cache-Control: proxy-revalidate\r\n\r\n",
+     MIME_COOKED_MASK_CC_PROXY_REVALIDATE,
+     0, 0, 0, 0},
+
+    {"only-if-cached only",
+     "Cache-Control: only-if-cached\r\n\r\n",
+     MIME_COOKED_MASK_CC_ONLY_IF_CACHED,
+     0, 0, 0, 0},
+
+    // Directives with values
+    {"max-age=0",
+     "Cache-Control: max-age=0\r\n\r\n",
+     MIME_COOKED_MASK_CC_MAX_AGE,
+     0, 0, 0, 0},
+
+    {"max-age=300",
+     "Cache-Control: max-age=300\r\n\r\n",
+     MIME_COOKED_MASK_CC_MAX_AGE,
+     300, 0, 0, 0},
+
+    {"max-age=86400",
+     "Cache-Control: max-age=86400\r\n\r\n",
+     MIME_COOKED_MASK_CC_MAX_AGE,
+     86400, 0, 0, 0},
+
+    {"s-maxage=600",
+     "Cache-Control: s-maxage=600\r\n\r\n",
+     MIME_COOKED_MASK_CC_S_MAXAGE,
+     0, 600, 0, 0},
+
+    {"max-stale=100",
+     "Cache-Control: max-stale=100\r\n\r\n",
+     MIME_COOKED_MASK_CC_MAX_STALE,
+     0, 0, 100, 0},
+
+    {"min-fresh=60",
+     "Cache-Control: min-fresh=60\r\n\r\n",
+     MIME_COOKED_MASK_CC_MIN_FRESH,
+     0, 0, 0, 60},
+
+    // Multiple directives
+    {"max-age and public",
+     "Cache-Control: max-age=300, public\r\n\r\n",
+     MIME_COOKED_MASK_CC_MAX_AGE | MIME_COOKED_MASK_CC_PUBLIC,
+     300, 0, 0, 0},
+
+    {"public and max-age (reversed order)",
+     "Cache-Control: public, max-age=300\r\n\r\n",
+     MIME_COOKED_MASK_CC_MAX_AGE | MIME_COOKED_MASK_CC_PUBLIC,
+     300, 0, 0, 0},
+
+    {"max-age and s-maxage",
+     "Cache-Control: max-age=300, s-maxage=600\r\n\r\n",
+     MIME_COOKED_MASK_CC_MAX_AGE | MIME_COOKED_MASK_CC_S_MAXAGE,
+     300, 600, 0, 0},
+
+    {"private and no-cache",
+     "Cache-Control: private, no-cache\r\n\r\n",
+     MIME_COOKED_MASK_CC_PRIVATE | MIME_COOKED_MASK_CC_NO_CACHE,
+     0, 0, 0, 0},
+
+    {"no-store and no-cache",
+     "Cache-Control: no-store, no-cache\r\n\r\n",
+     MIME_COOKED_MASK_CC_NO_STORE | MIME_COOKED_MASK_CC_NO_CACHE,
+     0, 0, 0, 0},
+
+    {"must-revalidate and proxy-revalidate",
+     "Cache-Control: must-revalidate, proxy-revalidate\r\n\r\n",
+     MIME_COOKED_MASK_CC_MUST_REVALIDATE | MIME_COOKED_MASK_CC_PROXY_REVALIDATE,
+     0, 0, 0, 0},
+
+    {"complex: public, max-age, s-maxage, must-revalidate",
+     "Cache-Control: public, max-age=300, s-maxage=600, must-revalidate\r\n\r\n",
+     MIME_COOKED_MASK_CC_PUBLIC | MIME_COOKED_MASK_CC_MAX_AGE |
+       MIME_COOKED_MASK_CC_S_MAXAGE | MIME_COOKED_MASK_CC_MUST_REVALIDATE,
+     300, 600, 0, 0},
+
+    {"all request directives: max-age, max-stale, min-fresh, no-cache, no-store, no-transform, only-if-cached",
+     "Cache-Control: max-age=100, max-stale=200, min-fresh=50, no-cache, no-store, no-transform, only-if-cached\r\n\r\n",
+     MIME_COOKED_MASK_CC_MAX_AGE | MIME_COOKED_MASK_CC_MAX_STALE | MIME_COOKED_MASK_CC_MIN_FRESH |
+       MIME_COOKED_MASK_CC_NO_CACHE | MIME_COOKED_MASK_CC_NO_STORE |
+       MIME_COOKED_MASK_CC_NO_TRANSFORM | MIME_COOKED_MASK_CC_ONLY_IF_CACHED,
+     100, 0, 200, 50},
+
+    // Edge cases - whitespace
+    {"extra whitespace around directive",
+     "Cache-Control:   max-age=300  \r\n\r\n",
+     MIME_COOKED_MASK_CC_MAX_AGE,
+     300, 0, 0, 0},
+
+    {"extra whitespace between directives",
+     "Cache-Control: max-age=300 ,  public\r\n\r\n",
+     MIME_COOKED_MASK_CC_MAX_AGE | MIME_COOKED_MASK_CC_PUBLIC,
+     300, 0, 0, 0},
+
+    {"tab character in header value",
+     "Cache-Control:\tmax-age=300\r\n\r\n",
+     MIME_COOKED_MASK_CC_MAX_AGE,
+     300, 0, 0, 0},
+
+    // Edge cases - unknown directives
+    {"unknown directive ignored, known directive parsed",
+     "Cache-Control: unknown-directive, max-age=300\r\n\r\n",
+     MIME_COOKED_MASK_CC_MAX_AGE,
+     300, 0, 0, 0},
+
+    {"unknown directive with value ignored",
+     "Cache-Control: unknown=value, public\r\n\r\n",
+     MIME_COOKED_MASK_CC_PUBLIC,
+     0, 0, 0, 0},
+
+    // Edge cases - numeric values (cc-parse: 0000 max-age, large max-age)
+    {"max-age with leading zeros (cc-parse: 0000 max-age)",
+     "Cache-Control: max-age=0000\r\n\r\n",
+     MIME_COOKED_MASK_CC_MAX_AGE,
+     0, 0, 0, 0},
+
+    {"max-age with leading zeros and value",
+     "Cache-Control: max-age=00300\r\n\r\n",
+     MIME_COOKED_MASK_CC_MAX_AGE,
+     300, 0, 0, 0},
+
+    {"large max-age value",
+     "Cache-Control: max-age=999999999\r\n\r\n",
+     MIME_COOKED_MASK_CC_MAX_AGE,
+     999999999, 0, 0, 0},
+
+    // Edge cases - negative values should be parsed (behavior per implementation)
+    {"negative max-age value",
+     "Cache-Control: max-age=-1\r\n\r\n",
+     MIME_COOKED_MASK_CC_MAX_AGE,
+     -1, 0, 0, 0},
+  };
+  // clang-format on
+
+  auto test_case = GENERATE(from_range(valid_cc_test_cases));
+
+  CAPTURE(test_case.description, test_case.header_text);
+
+  HdrHeap    *heap = new_HdrHeap(HdrHeap::DEFAULT_SIZE + 64);
+  MIMEParser  parser;
+  char const *real_s = test_case.header_text;
+  char const *real_e = test_case.header_text + strlen(test_case.header_text);
+  MIMEHdr     mime;
+
+  mime.create(heap);
+  mime_parser_init(&parser);
+
+  auto result = mime_parser_parse(&parser, heap, mime.m_mime, &real_s, real_e, false, true, false);
+  REQUIRE(ParseResult::DONE == result);
+
+  mime.m_mime->recompute_cooked_stuff();
+
+  auto mask = mime.get_cooked_cc_mask();
+  REQUIRE(mask == test_case.expected_mask);
+
+  if (test_case.expected_mask & MIME_COOKED_MASK_CC_MAX_AGE) {
+    REQUIRE(mime.get_cooked_cc_max_age() == test_case.expected_max_age);
+  }
+  if (test_case.expected_mask & MIME_COOKED_MASK_CC_S_MAXAGE) {
+    REQUIRE(mime.get_cooked_cc_s_maxage() == test_case.expected_s_maxage);
+  }
+  if (test_case.expected_mask & MIME_COOKED_MASK_CC_MAX_STALE) {
+    REQUIRE(mime.get_cooked_cc_max_stale() == test_case.expected_max_stale);
+  }
+  if (test_case.expected_mask & MIME_COOKED_MASK_CC_MIN_FRESH) {
+    REQUIRE(mime.get_cooked_cc_min_fresh() == test_case.expected_min_fresh);
+  }
+
+  heap->destroy();
+}
diff --git a/src/proxy/logging/Log.cc b/src/proxy/logging/Log.cc
index 4ac59bf..8d2f8c9 100644
--- a/src/proxy/logging/Log.cc
+++ b/src/proxy/logging/Log.cc
@@ -549,11 +549,6 @@
   global_field_list.add(field, false);
   field_symbol_hash.emplace("cqssr", field);
 
-  field = new LogField("client_req_ssl_resumption_type", "cqssrt", LogField::dINT, &LogAccess::marshal_client_ssl_resumption_type,
-                       &LogAccess::unmarshal_int_to_str);
-  global_field_list.add(field, false);
-  field_symbol_hash.emplace("cqssrt", field);
-
   field = new LogField("client_req_is_internal", "cqint", LogField::sINT, &LogAccess::marshal_client_req_is_internal,
                        &LogAccess::unmarshal_int_to_str);
   global_field_list.add(field, false);
diff --git a/src/proxy/logging/LogAccess.cc b/src/proxy/logging/LogAccess.cc
index 908ec8c..848885c 100644
--- a/src/proxy/logging/LogAccess.cc
+++ b/src/proxy/logging/LogAccess.cc
@@ -2192,15 +2192,6 @@
 }
 
 int
-LogAccess::marshal_client_ssl_resumption_type(char *buf)
-{
-  if (buf) {
-    marshal_int(buf, m_http_sm->get_user_agent().get_client_ssl_resumption_type());
-  }
-  return INK_MIN_ALIGN;
-}
-
-int
 LogAccess::marshal_client_req_is_internal(char *buf)
 {
   if (buf) {
diff --git a/src/records/RecordsConfig.cc b/src/records/RecordsConfig.cc
index f0cefd2..07328b7 100644
--- a/src/records/RecordsConfig.cc
+++ b/src/records/RecordsConfig.cc
@@ -1207,24 +1207,8 @@
   ,
   {RECT_CONFIG, "proxy.config.ssl.origin_session_cache.size", RECD_INT, "10240", RECU_RESTART_TS, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
   ,
-  {RECT_CONFIG, "proxy.config.ssl.session_cache.mode", RECD_INT, "2", RECU_RESTART_TS, RR_NULL, RECC_INT, "[0-2]", RECA_NULL}
-  ,
-  {RECT_CONFIG, "proxy.config.ssl.session_cache.enabled", RECD_INT, "2", RECU_RESTART_TS, RR_NULL, RECC_INT, "[0-2]", RECA_NULL}
-  ,
-  {RECT_CONFIG, "proxy.config.ssl.session_cache.value", RECD_INT, "2", RECU_RESTART_TS, RR_NULL, RECC_INT, "[0-2]", RECA_NULL}
-  ,
-  {RECT_CONFIG, "proxy.config.ssl.session_cache.size", RECD_INT, "102400", RECU_RESTART_TS, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
-  ,
-  {RECT_CONFIG, "proxy.config.ssl.session_cache.num_buckets", RECD_INT, "256", RECU_RESTART_TS, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
-  ,
-  {RECT_CONFIG, "proxy.config.ssl.session_cache.skip_cache_on_bucket_contention", RECD_INT, "0", RECU_RESTART_TS, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
-  ,
   {RECT_CONFIG, "proxy.config.ssl.max_record_size", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-16383]", RECA_NULL}
   ,
-  {RECT_CONFIG, "proxy.config.ssl.session_cache.timeout", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
-  ,
-  {RECT_CONFIG, "proxy.config.ssl.session_cache.auto_clear", RECD_INT, "1", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
-  ,
   {RECT_CONFIG, "proxy.config.ssl.hsts_max_age", RECD_INT, "-1", RECU_DYNAMIC, RR_NULL, RECC_STR, "^-?[0-9]+$", RECA_NULL}
   ,
   {RECT_CONFIG, "proxy.config.ssl.hsts_include_subdomains", RECD_INT, "0", RECU_DYNAMIC, RR_NULL, RECC_INT, "[0-1]", RECA_NULL}
diff --git a/src/traffic_ctl/CMakeLists.txt b/src/traffic_ctl/CMakeLists.txt
index 671789b..c967d42 100644
--- a/src/traffic_ctl/CMakeLists.txt
+++ b/src/traffic_ctl/CMakeLists.txt
@@ -16,11 +16,18 @@
 #######################
 
 add_executable(
-  traffic_ctl traffic_ctl.cc CtrlCommands.cc CtrlPrinters.cc FileConfigCommand.cc PrintUtils.cc
-              ${CMAKE_SOURCE_DIR}/src/shared/rpc/IPCSocketClient.cc
+  traffic_ctl
+  traffic_ctl.cc
+  ConvertConfigCommand.cc
+  CtrlCommands.cc
+  CtrlPrinters.cc
+  FileConfigCommand.cc
+  PrintUtils.cc
+  SSLMultiCertCommand.cc
+  ${CMAKE_SOURCE_DIR}/src/shared/rpc/IPCSocketClient.cc
 )
 
-target_link_libraries(traffic_ctl ts::tscore libswoc::libswoc yaml-cpp::yaml-cpp ts::tsutil)
+target_link_libraries(traffic_ctl ts::tscore ts::config libswoc::libswoc yaml-cpp::yaml-cpp ts::tsutil)
 
 install(TARGETS traffic_ctl)
 
diff --git a/src/traffic_ctl/ConvertConfigCommand.cc b/src/traffic_ctl/ConvertConfigCommand.cc
new file mode 100644
index 0000000..b387673
--- /dev/null
+++ b/src/traffic_ctl/ConvertConfigCommand.cc
@@ -0,0 +1,80 @@
+/** @file
+
+  Configuration format conversion command for traffic_ctl.
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+*/
+
+#include "ConvertConfigCommand.h"
+#include "config/ssl_multicert.h"
+
+#include <fstream>
+#include <iostream>
+
+ConvertConfigCommand::ConvertConfigCommand(ts::Arguments *args) : CtrlCommand(args)
+{
+  BasePrinter::Options print_opts{parse_print_opts(args)};
+  _printer = std::make_unique<GenericPrinter>(print_opts);
+
+  if (args->get("ssl_multicert")) {
+    auto const &convert_args = args->get("ssl_multicert");
+    if (convert_args.size() < 2) {
+      throw std::invalid_argument("ssl_multicert requires <input_file> <output_file>");
+    }
+    _input_file   = convert_args[0];
+    _output_file  = convert_args[1];
+    _invoked_func = [this]() { convert_ssl_multicert(); };
+  } else {
+    throw std::invalid_argument("Unsupported config type for conversion");
+  }
+}
+
+void
+ConvertConfigCommand::convert_ssl_multicert()
+{
+  config::SSLMultiCertParser                       parser;
+  config::ConfigResult<config::SSLMultiCertConfig> result = parser.parse(_input_file);
+
+  if (!result.ok()) {
+    std::string error_msg = "Failed to parse input file '" + _input_file + "'";
+    if (!result.errata.empty()) {
+      error_msg += ": ";
+      error_msg += std::string(result.errata.front().text());
+    }
+    _printer->write_output(error_msg);
+    return;
+  }
+
+  config::SSLMultiCertMarshaller marshaller;
+  std::string const              yaml_output = marshaller.to_yaml(result.value);
+
+  // Write to output file or stdout if output is "-".
+  if (_output_file == "-") {
+    std::cout << yaml_output << '\n';
+  } else {
+    std::ofstream out(_output_file);
+    if (!out) {
+      _printer->write_output("Failed to open output file '" + _output_file + "' for writing");
+      return;
+    }
+    out << yaml_output << '\n';
+    out.close();
+    _printer->write_output("Converted " + _input_file + " -> " + _output_file);
+  }
+}
diff --git a/src/traffic_ctl/ConvertConfigCommand.h b/src/traffic_ctl/ConvertConfigCommand.h
new file mode 100644
index 0000000..aee7e55
--- /dev/null
+++ b/src/traffic_ctl/ConvertConfigCommand.h
@@ -0,0 +1,49 @@
+/** @file
+
+  Configuration format conversion command for traffic_ctl.
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+*/
+
+#pragma once
+
+#include "CtrlCommands.h"
+
+/**
+ * Command handler for configuration format conversion.
+ *
+ * Converts configuration files from legacy formats to YAML.
+ * Supports: ssl_multicert
+ */
+class ConvertConfigCommand : public CtrlCommand
+{
+public:
+  /**
+   * Construct the command from parsed arguments.
+   *
+   * @param[in] args Parsed command line arguments.
+   */
+  ConvertConfigCommand(ts::Arguments *args);
+
+private:
+  void convert_ssl_multicert();
+
+  std::string _input_file;
+  std::string _output_file;
+};
diff --git a/src/traffic_ctl/SSLMultiCertCommand.cc b/src/traffic_ctl/SSLMultiCertCommand.cc
new file mode 100644
index 0000000..ebc5d00
--- /dev/null
+++ b/src/traffic_ctl/SSLMultiCertCommand.cc
@@ -0,0 +1,84 @@
+/** @file
+
+  SSL Multi-Certificate configuration command for traffic_ctl.
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+*/
+
+#include "SSLMultiCertCommand.h"
+#include "config/ssl_multicert.h"
+#include "tscore/Layout.h"
+#include "tscore/Filenames.h"
+
+#include <iostream>
+
+namespace
+{
+
+/// Get the default ssl_multicert.yaml file path.
+std::string
+get_default_ssl_multicert_path()
+{
+  std::string sysconfdir;
+  if (char const *env = getenv("PROXY_CONFIG_CONFIG_DIR")) {
+    sysconfdir = Layout::get()->relative(env);
+  } else {
+    sysconfdir = Layout::get()->sysconfdir;
+  }
+  return Layout::get()->relative_to(sysconfdir, ts::filename::SSL_MULTICERT);
+}
+
+} // namespace
+
+SSLMultiCertCommand::SSLMultiCertCommand(ts::Arguments *args) : CtrlCommand(args)
+{
+  BasePrinter::Options print_opts{parse_print_opts(args)};
+  _printer = std::make_unique<GenericPrinter>(print_opts);
+
+  if (args->get("show")) {
+    // Default to YAML; use JSON only if explicitly requested.
+    _output_json  = args->get("json");
+    _invoked_func = [this]() { show_config(); };
+  } else {
+    throw std::invalid_argument("Unsupported ssl-multicert subcommand");
+  }
+}
+
+void
+SSLMultiCertCommand::show_config()
+{
+  std::string const filename = get_default_ssl_multicert_path();
+
+  config::SSLMultiCertParser                       parser;
+  config::ConfigResult<config::SSLMultiCertConfig> result = parser.parse(filename);
+
+  if (!result.ok()) {
+    std::string error_msg = "Failed to parse ssl_multicert config";
+    if (!result.errata.empty()) {
+      error_msg += ": ";
+      error_msg += std::string(result.errata.front().text());
+    }
+    _printer->write_output(error_msg);
+    return;
+  }
+
+  config::SSLMultiCertMarshaller marshaller;
+  std::string const              output = _output_json ? marshaller.to_json(result.value) : marshaller.to_yaml(result.value);
+  _printer->write_output(output);
+}
diff --git a/src/traffic_ctl/SSLMultiCertCommand.h b/src/traffic_ctl/SSLMultiCertCommand.h
new file mode 100644
index 0000000..d796d1d
--- /dev/null
+++ b/src/traffic_ctl/SSLMultiCertCommand.h
@@ -0,0 +1,47 @@
+/** @file
+
+  SSL Multi-Certificate configuration command for traffic_ctl.
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+*/
+
+#pragma once
+
+#include "CtrlCommands.h"
+
+/**
+ * Command handler for ssl-multicert configuration operations.
+ *
+ * Supports reading and displaying ssl_multicert configuration in JSON or YAML format.
+ */
+class SSLMultiCertCommand : public CtrlCommand
+{
+public:
+  /**
+   * Construct the command from parsed arguments.
+   *
+   * @param[in] args Parsed command line arguments.
+   */
+  SSLMultiCertCommand(ts::Arguments *args);
+
+private:
+  void show_config();
+
+  bool _output_json = false;
+};
diff --git a/src/traffic_ctl/traffic_ctl.cc b/src/traffic_ctl/traffic_ctl.cc
index 6529c0f..9ac5900 100644
--- a/src/traffic_ctl/traffic_ctl.cc
+++ b/src/traffic_ctl/traffic_ctl.cc
@@ -31,7 +31,9 @@
 #include "tscore/signals.h"
 
 #include "CtrlCommands.h"
+#include "ConvertConfigCommand.h"
 #include "FileConfigCommand.h"
+#include "SSLMultiCertCommand.h"
 #include "TrafficCtlStatus.h"
 
 // Define the global variable
@@ -143,6 +145,25 @@
 
   config_command.add_command("registry", "Show configuration file registry", Command_Execute)
     .add_example_usage("traffic_ctl config registry");
+
+  // ssl-multicert subcommand
+  auto &ssl_multicert_command =
+    config_command.add_command("ssl-multicert", "Manage ssl_multicert configuration").require_commands();
+  auto &ssl_multicert_show = ssl_multicert_command.add_command("show", "Show the ssl_multicert configuration", Command_Execute)
+                               .add_example_usage("traffic_ctl config ssl-multicert show")
+                               .add_example_usage("traffic_ctl config ssl-multicert show --yaml")
+                               .add_example_usage("traffic_ctl config ssl-multicert show --json");
+  ssl_multicert_show.add_mutex_group("format", false, "Output format");
+  ssl_multicert_show.add_option_to_group("format", "--yaml", "-y", "Output in YAML format (default)");
+  ssl_multicert_show.add_option_to_group("format", "--json", "-j", "Output in JSON format");
+
+  // convert subcommand - convert config files between formats
+  auto &convert_command = config_command.add_command("convert", "Convert configuration files to YAML format").require_commands();
+  convert_command.add_command("ssl_multicert", "Convert ssl_multicert.config to ssl_multicert.yaml", "", 2, Command_Execute)
+    .add_example_usage("traffic_ctl config convert ssl_multicert <input_file> <output_file>")
+    .add_example_usage("traffic_ctl config convert ssl_multicert ssl_multicert.config ssl_multicert.yaml")
+    .add_example_usage("traffic_ctl config convert ssl_multicert ssl_multicert.config -  # output to stdout");
+
   // host commands
   host_command.add_command("status", "Get one or more host statuses", "", MORE_THAN_ZERO_ARG_N, Command_Execute)
     .add_example_usage("traffic_ctl host status HOST  [HOST  ...]");
@@ -226,6 +247,12 @@
 
   auto create_command = [](ts::Arguments &args) -> std::unique_ptr<CtrlCommand> {
     if (args.get("config")) {
+      if (args.get("convert")) {
+        return std::make_unique<ConvertConfigCommand>(&args);
+      }
+      if (args.get("ssl-multicert")) {
+        return std::make_unique<SSLMultiCertCommand>(&args);
+      }
       if (args.get("cold")) {
         return std::make_unique<FileConfigCommand>(&args);
       }
diff --git a/tests/gold_tests/autest-site/traffic_replay.test.ext b/tests/gold_tests/autest-site/traffic_replay.test.ext
index f173033..c276fb2 100644
--- a/tests/gold_tests/autest-site/traffic_replay.test.ext
+++ b/tests/gold_tests/autest-site/traffic_replay.test.ext
@@ -29,7 +29,13 @@
     ts.addSSLfile(os.path.join(obj.Variables["AtsTestToolsDir"], "microserver", "ssl", "server.pem"))
     ts.addSSLfile(os.path.join(obj.Variables["AtsTestToolsDir"], "microserver", "ssl", "server.crt"))
 
-    ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.pem')
+    ts.Disk.ssl_multicert_yaml.AddLines(
+        """
+    ssl_multicert:
+      - ssl_cert_name: server.pem
+        ssl_key_name: server.pem
+        dest_ip: "*"
+    """.split("\n"))
 
     # MicroServer setup - NOTE: expand to multiple microserver in future?
     server = obj.MakeOriginServer("server", both=True, lookup_key='{%uuid}')
diff --git a/tests/gold_tests/autest-site/trafficserver.test.ext b/tests/gold_tests/autest-site/trafficserver.test.ext
index a41f894..d15d505 100755
--- a/tests/gold_tests/autest-site/trafficserver.test.ext
+++ b/tests/gold_tests/autest-site/trafficserver.test.ext
@@ -303,7 +303,7 @@
     tmpname = os.path.join(config_dir, fname)
     p.Disk.File(tmpname, id=make_id(fname), typename="ats:config")
 
-    fname = "ssl_multicert.config"
+    fname = "ssl_multicert.yaml"
     tmpname = os.path.join(config_dir, fname)
     p.Disk.File(tmpname, id=make_id(fname), typename="ats:config")
 
diff --git a/tests/gold_tests/bigobj/bigobj.test.py b/tests/gold_tests/bigobj/bigobj.test.py
index 55bb5c7..790516b 100644
--- a/tests/gold_tests/bigobj/bigobj.test.py
+++ b/tests/gold_tests/bigobj/bigobj.test.py
@@ -41,7 +41,13 @@
         'proxy.config.url_remap.remap_required': 0
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLine(f'map https://localhost:{ts.Variables.ssl_port} http://localhost:{ts.Variables.port}')
 ts.Disk.remap_config.AddLine(f'map https://localhost:{ts.Variables.ssl_portv6} http://localhost:{ts.Variables.port}')
@@ -120,7 +126,13 @@
         'proxy.config.url_remap.remap_required': 0
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLine(f'map https://localhost:{ts.Variables.ssl_port} http://localhost:{ts.Variables.port}')
 ts.Disk.remap_config.AddLine(f'map https://localhost:{ts.Variables.ssl_portv6} http://localhost:{ts.Variables.port}')
diff --git a/tests/gold_tests/cache/background_fill.test.py b/tests/gold_tests/cache/background_fill.test.py
index 552add2..c9f5024 100644
--- a/tests/gold_tests/cache/background_fill.test.py
+++ b/tests/gold_tests/cache/background_fill.test.py
@@ -50,7 +50,13 @@
             self.ts[name] = Test.MakeATSProcess(name, select_ports=True, enable_tls=True, enable_cache=True)
 
             self.ts[name].addDefaultSSLFiles()
-            self.ts[name].Disk.ssl_multicert_config.AddLine("dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key")
+            self.ts[name].Disk.ssl_multicert_yaml.AddLines(
+                """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
             self.ts[name].Disk.records_config.update(
                 {
diff --git a/tests/gold_tests/cache/negative-caching.test.py b/tests/gold_tests/cache/negative-caching.test.py
index 0e09ae1..853624e 100644
--- a/tests/gold_tests/cache/negative-caching.test.py
+++ b/tests/gold_tests/cache/negative-caching.test.py
@@ -132,3 +132,6 @@
 p.StartBefore(dns)
 p.StartBefore(server)
 p.StartBefore(ts)
+
+# Test malformed Cache-Control header with semicolons instead of commas (issue #12029)
+Test.ATSReplayTest(replay_file="replay/negative-caching-malformed-cc.replay.yaml")
diff --git a/tests/gold_tests/cache/replay/negative-caching-malformed-cc.replay.yaml b/tests/gold_tests/cache/replay/negative-caching-malformed-cc.replay.yaml
new file mode 100644
index 0000000..c973aa1
--- /dev/null
+++ b/tests/gold_tests/cache/replay/negative-caching-malformed-cc.replay.yaml
@@ -0,0 +1,317 @@
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+#
+# This replay file tests negative caching with malformed Cache-Control headers
+# that use semicolons instead of commas as separators (issue #12029).
+#
+
+meta:
+  version: "1.0"
+
+# Configuration section for autest integration
+autest:
+  description: 'Test malformed Cache-Control header with semicolons instead of commas (issue #12029)'
+
+  # Server configuration
+  server:
+    name: 'server-malformed-cc'
+
+  # Client configuration
+  client:
+    name: 'client-malformed-cc'
+
+  # ATS configuration
+  ats:
+    name: 'ts-malformed-cc'
+
+    # ATS records.config settings
+    records_config:
+      proxy.config.diags.debug.enabled: 1
+      proxy.config.diags.debug.tags: 'http'
+      proxy.config.http.insert_age_in_response: 0
+      proxy.config.http.negative_caching_enabled: 0
+
+    # Remap configuration
+    remap_config:
+      - from: "/"
+        to: "http://127.0.0.1:{SERVER_HTTP_PORT}/"
+
+sessions:
+- transactions:
+
+  #
+  # Phase 1: Initial requests to populate the cache.
+  #
+  # These requests are sent to the origin server and the responses are cached
+  # (or not cached, depending on the Cache-Control header).
+  #
+
+  # First, verify that a 400 response with a proper Cache-Control header is cached.
+  - client-request:
+      method: "GET"
+      version: "1.1"
+      scheme: "http"
+      url: /path/400_proper_cc
+      headers:
+        fields:
+        - [ Host, example.com ]
+        - [ uuid, proper_cc ]
+
+    server-response:
+      status: 400
+      reason: "Bad Request"
+      headers:
+        fields:
+        - [ Content-Length, 0 ]
+        - [ Cache-Control, max-age=300 ]
+
+    proxy-response:
+      status: 400
+
+  # Test: Verify that a 400 response with malformed Cache-Control using
+  # semicolons is not cached. The header "Cache-Control: public; max-age=30"
+  # uses semicolons instead of commas as separators, violating RFC 7234.
+  # Per RFC 7234 Section 5.2, caches must ignore unrecognized directives.
+  # Since the directive is malformed, it should be ignored, and the response
+  # should not be cached (no valid Cache-Control + negative_caching_enabled=0).
+  - client-request:
+      method: "GET"
+      version: "1.1"
+      scheme: "http"
+      url: /path/400_malformed_cc_semicolon
+      headers:
+        fields:
+        - [ Host, example.com ]
+        - [ uuid, semicolon ]
+
+    server-response:
+      status: 400
+      reason: "Bad Request"
+      headers:
+        fields:
+        - [ Content-Length, 0 ]
+        # Note: Using semicolon instead of comma - this is malformed per RFC 7234
+        - [ Cache-Control, "public; max-age=30" ]
+
+    proxy-response:
+      status: 400
+
+  # Test: Verify that a 400 response with space before = in max-age is not cached.
+  # The header "Cache-Control: max-age =300" is malformed per RFC 7230.
+  # Per RFC 7234 Section 5.2, caches must ignore unrecognized directives.
+  - client-request:
+      method: "GET"
+      version: "1.1"
+      scheme: "http"
+      url: /path/400_malformed_cc_space_before_equals
+      headers:
+        fields:
+        - [ Host, example.com ]
+        - [ uuid, space_before_equals ]
+
+    server-response:
+      status: 400
+      reason: "Bad Request"
+      headers:
+        fields:
+        - [ Content-Length, 0 ]
+        # Note: Space before = is malformed per RFC 7230
+        - [ Cache-Control, "max-age =300" ]
+
+    proxy-response:
+      status: 400
+
+  # Test: Verify that a 400 response with space after = in max-age is not cached.
+  # The header "Cache-Control: max-age= 300" is malformed per RFC 7230.
+  # Per RFC 7234 Section 5.2, caches must ignore unrecognized directives.
+  - client-request:
+      method: "GET"
+      version: "1.1"
+      scheme: "http"
+      url: /path/400_malformed_cc_space_after_equals
+      headers:
+        fields:
+        - [ Host, example.com ]
+        - [ uuid, space_after_equals ]
+
+    server-response:
+      status: 400
+      reason: "Bad Request"
+      headers:
+        fields:
+        - [ Content-Length, 0 ]
+        # Note: Space after = is malformed per RFC 7230
+        - [ Cache-Control, "max-age= 300" ]
+
+    proxy-response:
+      status: 400
+
+  # Test: Verify that a 400 response with single-quoted max-age value is not cached.
+  # The header "Cache-Control: max-age='300'" is malformed per RFC 7230.
+  # Per RFC 7234 Section 5.2, caches must ignore unrecognized directives.
+  - client-request:
+      method: "GET"
+      version: "1.1"
+      scheme: "http"
+      url: /path/400_malformed_cc_single_quotes
+      headers:
+        fields:
+        - [ Host, example.com ]
+        - [ uuid, single_quotes ]
+
+    server-response:
+      status: 400
+      reason: "Bad Request"
+      headers:
+        fields:
+        - [ Content-Length, 0 ]
+        # Note: Single quotes around value are malformed per RFC 7230
+        - [ Cache-Control, "max-age='300'" ]
+
+    proxy-response:
+      status: 400
+
+  #
+  # Phase 2: Verification requests to check if responses were cached.
+  #
+  # These requests should be served from the cache (if cached) or from the
+  # origin server (if not cached).
+  #
+
+  # Second request to /path/400_proper_cc should be served from cache.
+  - client-request:
+
+      # Add delay to ensure time for cache IO processing.
+      delay: 100ms
+
+      method: "GET"
+      version: "1.1"
+      scheme: "http"
+      url: /path/400_proper_cc
+      headers:
+        fields:
+        - [ Host, example.com ]
+        - [ uuid, proper_cc_verify ]
+
+    # The server should not receive this request because it should be served from cache.
+    server-response:
+      status: 200
+      reason: "OK"
+      headers:
+        fields:
+        - [ Content-Length, 0 ]
+        - [ Cache-Control, max-age=300 ]
+
+    proxy-response:
+      status: 400
+
+  # Second request to /path/400_malformed_cc_semicolon should NOT be served from cache.
+  - client-request:
+      method: "GET"
+      version: "1.1"
+      scheme: "http"
+      url: /path/400_malformed_cc_semicolon
+      headers:
+        fields:
+        - [ Host, example.com ]
+        - [ uuid, semicolon_verify ]
+
+    # Since the initial CC was malformed, the response should not be cached.
+    server-response:
+      status: 200
+      reason: OK
+      headers:
+        fields:
+        - [ Content-Length, 16 ]
+        - [ Cache-Control, max-age=300 ]
+
+    # Expect the origin's 200 response.
+    proxy-response:
+      status: 200
+
+  # Second request to /path/400_malformed_cc_space_before_equals should NOT be served from cache.
+  - client-request:
+      method: "GET"
+      version: "1.1"
+      scheme: "http"
+      url: /path/400_malformed_cc_space_before_equals
+      headers:
+        fields:
+        - [ Host, example.com ]
+        - [ uuid, space_before_equals_verify ]
+
+    # Since the initial CC was malformed, the response should not be cached.
+    server-response:
+      status: 200
+      reason: OK
+      headers:
+        fields:
+        - [ Content-Length, 16 ]
+        - [ Cache-Control, max-age=300 ]
+
+    # Expect the origin's 200 response.
+    proxy-response:
+      status: 200
+
+  # Second request to /path/400_malformed_cc_space_after_equals should NOT be served from cache.
+  - client-request:
+      method: "GET"
+      version: "1.1"
+      scheme: "http"
+      url: /path/400_malformed_cc_space_after_equals
+      headers:
+        fields:
+        - [ Host, example.com ]
+        - [ uuid, space_after_equals_verify ]
+
+    # Since the initial CC was malformed, the response should not be cached.
+    server-response:
+      status: 200
+      reason: OK
+      headers:
+        fields:
+        - [ Content-Length, 16 ]
+        - [ Cache-Control, max-age=300 ]
+
+    # Expect the origin's 200 response.
+    proxy-response:
+      status: 200
+
+  # Second request to /path/400_malformed_cc_single_quotes should NOT be served from cache.
+  - client-request:
+      method: "GET"
+      version: "1.1"
+      scheme: "http"
+      url: /path/400_malformed_cc_single_quotes
+      headers:
+        fields:
+        - [ Host, example.com ]
+        - [ uuid, single_quotes_verify ]
+
+    # Since the initial CC was malformed, the response should not be cached.
+    server-response:
+      status: 200
+      reason: OK
+      headers:
+        fields:
+        - [ Content-Length, 16 ]
+        - [ Cache-Control, max-age=300 ]
+
+    # Expect the origin's 200 response.
+    proxy-response:
+      status: 200
+
diff --git a/tests/gold_tests/chunked_encoding/bad_chunked_encoding.test.py b/tests/gold_tests/chunked_encoding/bad_chunked_encoding.test.py
index 80addce..7e405f7 100644
--- a/tests/gold_tests/chunked_encoding/bad_chunked_encoding.test.py
+++ b/tests/gold_tests/chunked_encoding/bad_chunked_encoding.test.py
@@ -96,7 +96,13 @@
                 "proxy.config.ssl.server.private_key.path": f'{self.ts.Variables.SSLDir}',
                 "proxy.config.ssl.client.verify.server.policy": 'PERMISSIVE',
             })
-        self.ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+        self.ts.Disk.ssl_multicert_yaml.AddLines(
+            """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
         self.ts.Disk.remap_config.AddLine(f"map / http://127.0.0.1:{self.server.Variables.http_port}/",)
 
     def runChunkedTraffic(self):
@@ -150,7 +156,13 @@
                 "proxy.config.ssl.server.private_key.path": f'{self.ts.Variables.SSLDir}',
                 "proxy.config.ssl.client.verify.server.policy": 'PERMISSIVE',
             })
-        self.ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+        self.ts.Disk.ssl_multicert_yaml.AddLines(
+            """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
         self.ts.Disk.remap_config.AddLine(f"map / http://127.0.0.1:{self.server.Variables.http_port}/",)
         self.ts.Disk.traffic_out.Content += Testers.ContainsExpression(
             "user agent post chunk decoding error", "Verify that ATS detected a problem parsing a chunk.")
diff --git a/tests/gold_tests/chunked_encoding/chunked_encoding.test.py b/tests/gold_tests/chunked_encoding/chunked_encoding.test.py
index ab47b5e..1c24420 100644
--- a/tests/gold_tests/chunked_encoding/chunked_encoding.test.py
+++ b/tests/gold_tests/chunked_encoding/chunked_encoding.test.py
@@ -90,7 +90,13 @@
     'map https://www.anotherexample.com https://127.0.0.1:{0}'.format(server2.Variables.SSL_Port, ts.Variables.ssl_port))
 ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(Test.Variables.upstream_port))
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 # smuggle-client is built via `make`. Here we copy the built binary down to the
 # test directory so that the test runs in this file can use it.
diff --git a/tests/gold_tests/chunked_encoding/chunked_encoding_disabled.test.py b/tests/gold_tests/chunked_encoding/chunked_encoding_disabled.test.py
index 8b7dc7c..f584b90 100644
--- a/tests/gold_tests/chunked_encoding/chunked_encoding_disabled.test.py
+++ b/tests/gold_tests/chunked_encoding/chunked_encoding_disabled.test.py
@@ -44,7 +44,13 @@
                 # Never respond with chunked encoding.
                 "proxy.config.http.chunking_enabled": 0,
             })
-        self.ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+        self.ts.Disk.ssl_multicert_yaml.AddLines(
+            """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
         self.ts.Disk.remap_config.AddLines(
             [
                 f"map /for/http http://127.0.0.1:{self.server.Variables.http_port}/",
diff --git a/tests/gold_tests/chunked_encoding/chunked_encoding_h2.test.py b/tests/gold_tests/chunked_encoding/chunked_encoding_h2.test.py
index 004267c..cbf6358 100644
--- a/tests/gold_tests/chunked_encoding/chunked_encoding_h2.test.py
+++ b/tests/gold_tests/chunked_encoding/chunked_encoding_h2.test.py
@@ -56,7 +56,13 @@
 ts.Disk.remap_config.AddLine('map /post-full http://127.0.0.1:{0}'.format(Test.Variables.upstream_port2))
 ts.Disk.remap_config.AddLine('map /post-chunked http://127.0.0.1:{0}'.format(Test.Variables.upstream_port3))
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 # Using netcat as a cheap origin server in case 1 so we can insert a delay in sending back the response.
 # Replaced microserver for cases 2 and 3 as well because I was getting python exceptions when running
diff --git a/tests/gold_tests/client_connection/per_client_connection_max.test.py b/tests/gold_tests/client_connection/per_client_connection_max.test.py
index aa887d0..4bec83d 100644
--- a/tests/gold_tests/client_connection/per_client_connection_max.test.py
+++ b/tests/gold_tests/client_connection/per_client_connection_max.test.py
@@ -125,7 +125,13 @@
         name = f'ts{self._process_counter}'
         self._ts = Test.MakeATSProcess(name, enable_cache=False, enable_tls=True)
         self._ts.addDefaultSSLFiles()
-        self._ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+        self._ts.Disk.ssl_multicert_yaml.AddLines(
+            """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
         if self._protocol == Protocol.HTTP:
             server_port = self._server.Variables.http_port
             scheme = 'http'
diff --git a/tests/gold_tests/connect/connect.test.py b/tests/gold_tests/connect/connect.test.py
index c1ff3a1..3ea5821 100644
--- a/tests/gold_tests/connect/connect.test.py
+++ b/tests/gold_tests/connect/connect.test.py
@@ -221,7 +221,13 @@
             })
 
         self.ts.addDefaultSSLFiles()
-        self.ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+        self.ts.Disk.ssl_multicert_yaml.AddLines(
+            """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
         self.ts.Disk.remap_config.AddLines([
             f"map / http://127.0.0.1:{self.server.Variables.http_port}/",
diff --git a/tests/gold_tests/continuations/double_h2.test.py b/tests/gold_tests/continuations/double_h2.test.py
index cb81c2d..8ff4fa0 100644
--- a/tests/gold_tests/continuations/double_h2.test.py
+++ b/tests/gold_tests/continuations/double_h2.test.py
@@ -48,7 +48,13 @@
 # add port and remap rule
 ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port))
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.records_config.update(
     {
diff --git a/tests/gold_tests/continuations/openclose_h2.test.py b/tests/gold_tests/continuations/openclose_h2.test.py
index 8cbeb01..987636e 100644
--- a/tests/gold_tests/continuations/openclose_h2.test.py
+++ b/tests/gold_tests/continuations/openclose_h2.test.py
@@ -59,7 +59,13 @@
     })
 
 ts.Disk.remap_config.AddLine('map https://oc.test:{0} http://127.0.0.1:{1}'.format(ts.Variables.ssl_port, server.Variables.Port))
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 cmd = '-k --resolve oc.test:{0}:127.0.0.1 --http2 https://oc.test:{0}'.format(ts.Variables.ssl_port)
 numberOfRequests = 100
diff --git a/tests/gold_tests/continuations/session_id.test.py b/tests/gold_tests/continuations/session_id.test.py
index 5c20d02..a10e45b 100644
--- a/tests/gold_tests/continuations/session_id.test.py
+++ b/tests/gold_tests/continuations/session_id.test.py
@@ -53,7 +53,13 @@
 
 ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port))
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 #
 # Run some HTTP/1 traffic.
diff --git a/tests/gold_tests/cripts/cripts.test.py b/tests/gold_tests/cripts/cripts.test.py
index ce46154..bffe3c4 100644
--- a/tests/gold_tests/cripts/cripts.test.py
+++ b/tests/gold_tests/cripts/cripts.test.py
@@ -50,7 +50,13 @@
         self.ts = Test.MakeATSProcess("ts_in", enable_tls=True, enable_cache=False, enable_cripts=True)
 
         self.ts.addDefaultSSLFiles()
-        self.ts.Disk.ssl_multicert_config.AddLine("dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key")
+        self.ts.Disk.ssl_multicert_yaml.AddLines(
+            """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
         self.ts.Setup.Copy('files/basic.cript', self.ts.Variables.CONFIGDIR)
 
diff --git a/tests/gold_tests/early_hints/early_hints.test.py b/tests/gold_tests/early_hints/early_hints.test.py
index 84c7a76..0f64662 100644
--- a/tests/gold_tests/early_hints/early_hints.test.py
+++ b/tests/gold_tests/early_hints/early_hints.test.py
@@ -91,7 +91,13 @@
         self._ts = ts
         ts.Disk.remap_config.AddLine(f'map / http://backend.server.com:{self._server.Variables.http_port}')
         ts.addDefaultSSLFiles()
-        ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+        ts.Disk.ssl_multicert_yaml.AddLines(
+            """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
         ts.Disk.records_config.update(
             {
                 'proxy.config.ssl.server.cert.path': ts.Variables.SSLDir,
diff --git a/tests/gold_tests/forward_proxy/forward_proxy.test.py b/tests/gold_tests/forward_proxy/forward_proxy.test.py
index 49dd714..6566414 100644
--- a/tests/gold_tests/forward_proxy/forward_proxy.test.py
+++ b/tests/gold_tests/forward_proxy/forward_proxy.test.py
@@ -59,7 +59,13 @@
         self.ts = Test.MakeATSProcess(proc_name, enable_tls=True, enable_cache=False)
         ForwardProxyTest._ts_counter += 1
         self.ts.addDefaultSSLFiles()
-        self.ts.Disk.ssl_multicert_config.AddLine("dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key")
+        self.ts.Disk.ssl_multicert_yaml.AddLines(
+            """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
         self.ts.Disk.remap_config.AddLine(f"map / http://127.0.0.1:{self.server.Variables.http_port}/")
 
         self.ts.Disk.records_config.update(
diff --git a/tests/gold_tests/h2/grpc/grpc.test.py b/tests/gold_tests/h2/grpc/grpc.test.py
index 72c5249..4c0c19f 100644
--- a/tests/gold_tests/h2/grpc/grpc.test.py
+++ b/tests/gold_tests/h2/grpc/grpc.test.py
@@ -53,7 +53,13 @@
         self._ts = tr.MakeATSProcess("ts", enable_tls=True, enable_cache=False)
 
         self._ts.addDefaultSSLFiles()
-        self._ts.Disk.ssl_multicert_config.AddLine("dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key")
+        self._ts.Disk.ssl_multicert_yaml.AddLines(
+            """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
         self._ts.Disk.remap_config.AddLine(f"map / https://example.com:{server_port}/")
 
diff --git a/tests/gold_tests/h2/h2disable.test.py b/tests/gold_tests/h2/h2disable.test.py
index 9e64541..7946228 100644
--- a/tests/gold_tests/h2/h2disable.test.py
+++ b/tests/gold_tests/h2/h2disable.test.py
@@ -35,7 +35,13 @@
 
 ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port))
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 # Case 1, global config policy=permissive properties=signature
 #         override for foo.com policy=enforced properties=all
diff --git a/tests/gold_tests/h2/h2disable_no_accept_threads.test.py b/tests/gold_tests/h2/h2disable_no_accept_threads.test.py
index 3fc0a1d..e431858 100644
--- a/tests/gold_tests/h2/h2disable_no_accept_threads.test.py
+++ b/tests/gold_tests/h2/h2disable_no_accept_threads.test.py
@@ -35,7 +35,13 @@
 
 ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port))
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 # Case 1, global config policy=permissive properties=signature
 #         override for foo.com policy=enforced properties=all
diff --git a/tests/gold_tests/h2/h2enable.test.py b/tests/gold_tests/h2/h2enable.test.py
index 36eb2fc..1aaf016 100644
--- a/tests/gold_tests/h2/h2enable.test.py
+++ b/tests/gold_tests/h2/h2enable.test.py
@@ -35,7 +35,13 @@
 
 ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port))
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 # Set up port 4444 with HTTP1 only, no HTTP/2
 ts.Disk.records_config.update(
diff --git a/tests/gold_tests/h2/h2enable_no_accept_threads.test.py b/tests/gold_tests/h2/h2enable_no_accept_threads.test.py
index c07acee..c6818be 100644
--- a/tests/gold_tests/h2/h2enable_no_accept_threads.test.py
+++ b/tests/gold_tests/h2/h2enable_no_accept_threads.test.py
@@ -35,7 +35,13 @@
 
 ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port))
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 # Set up port 4444 with HTTP1 only, no HTTP/2
 ts.Disk.records_config.update(
diff --git a/tests/gold_tests/h2/h2get_with_body.test.py b/tests/gold_tests/h2/h2get_with_body.test.py
index f97aa04..89661b6 100644
--- a/tests/gold_tests/h2/h2get_with_body.test.py
+++ b/tests/gold_tests/h2/h2get_with_body.test.py
@@ -29,7 +29,13 @@
 ts = Test.MakeATSProcess('ts', select_ports=True, enable_tls=True, enable_cache=True)
 
 ts.addDefaultSSLFiles()
-ts.Disk.ssl_multicert_config.AddLine("dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key")
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 ts.Disk.records_config.update(
     {
         "proxy.config.http.server_ports": f"{ts.Variables.port} {ts.Variables.ssl_port}:ssl",
diff --git a/tests/gold_tests/h2/h2origin.test.py b/tests/gold_tests/h2/h2origin.test.py
index 24e92b5..6b0211a 100644
--- a/tests/gold_tests/h2/h2origin.test.py
+++ b/tests/gold_tests/h2/h2origin.test.py
@@ -51,7 +51,13 @@
 
 ts.Disk.remap_config.AddLines(
     [f'map /expect http://127.0.0.1:{server_expect.Variables.http_port}', f'map / https://127.0.0.1:{server.Variables.https_port}'])
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.logging_yaml.AddLines(
     '''
diff --git a/tests/gold_tests/h2/h2origin_single_thread.test.py b/tests/gold_tests/h2/h2origin_single_thread.test.py
index d8d5e46..ba0b338 100644
--- a/tests/gold_tests/h2/h2origin_single_thread.test.py
+++ b/tests/gold_tests/h2/h2origin_single_thread.test.py
@@ -49,7 +49,13 @@
     })
 
 ts.Disk.remap_config.AddLine('map / https://127.0.0.1:{0}'.format(server.Variables.https_port))
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.logging_yaml.AddLines(
     '''
diff --git a/tests/gold_tests/h2/h2spec.test.py b/tests/gold_tests/h2/h2spec.test.py
index fee2dd5..ba84115 100644
--- a/tests/gold_tests/h2/h2spec.test.py
+++ b/tests/gold_tests/h2/h2spec.test.py
@@ -38,7 +38,13 @@
 ts.addDefaultSSLFiles()
 
 ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(httpbin.Variables.Port))
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 ts.Disk.records_config.update(
     {
         'proxy.config.http.insert_request_via_str': 1,
diff --git a/tests/gold_tests/h2/http2.test.py b/tests/gold_tests/h2/http2.test.py
index 1635615..f6dfcf8 100644
--- a/tests/gold_tests/h2/http2.test.py
+++ b/tests/gold_tests/h2/http2.test.py
@@ -139,7 +139,13 @@
 
 ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port))
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 ts.Disk.records_config.update(
     {
         'proxy.config.diags.debug.enabled': 1,
diff --git a/tests/gold_tests/h2/http2_close_connection.test.py b/tests/gold_tests/h2/http2_close_connection.test.py
index 7ad60d8..5a16bd4 100644
--- a/tests/gold_tests/h2/http2_close_connection.test.py
+++ b/tests/gold_tests/h2/http2_close_connection.test.py
@@ -27,7 +27,13 @@
 ts = Test.MakeATSProcess('ts', select_ports=True, enable_tls=True)
 
 ts.addDefaultSSLFiles()
-ts.Disk.ssl_multicert_config.AddLine("dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key")
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 ts.Disk.records_config.update(
     {
         "proxy.config.http.server_ports": f"{ts.Variables.port} {ts.Variables.ssl_port}:ssl",
diff --git a/tests/gold_tests/h2/http2_concurrent_streams.test.py b/tests/gold_tests/h2/http2_concurrent_streams.test.py
index 7510b8e..79e6b9d 100644
--- a/tests/gold_tests/h2/http2_concurrent_streams.test.py
+++ b/tests/gold_tests/h2/http2_concurrent_streams.test.py
@@ -43,7 +43,13 @@
                 'proxy.config.http.insert_response_via_str': 2,
             })
         self._ts.Disk.remap_config.AddLine(f"map / http://127.0.0.1:{self._server.Variables.http_port}")
-        self._ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+        self._ts.Disk.ssl_multicert_yaml.AddLines(
+            """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
     def run(self):
         tr = Test.AddTestRun()
diff --git a/tests/gold_tests/h2/http2_empty_data_frame.test.py b/tests/gold_tests/h2/http2_empty_data_frame.test.py
index d0e9038..ae2caf7 100644
--- a/tests/gold_tests/h2/http2_empty_data_frame.test.py
+++ b/tests/gold_tests/h2/http2_empty_data_frame.test.py
@@ -45,7 +45,13 @@
                 'proxy.config.http2.stream_error_rate_threshold': 0.1  # default
             })
         self._ts.Disk.remap_config.AddLine(f"map / http://127.0.0.1:{self._server.Variables.Port}")
-        self._ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+        self._ts.Disk.ssl_multicert_yaml.AddLines(
+            """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
     def __setupClient(self):
         self._ts.Setup.CopyAs("clients/h2empty_data_frame.py", Test.RunDirectory)
diff --git a/tests/gold_tests/h2/http2_flow_control.test.py b/tests/gold_tests/h2/http2_flow_control.test.py
index 1d000c8..c8d3dba 100644
--- a/tests/gold_tests/h2/http2_flow_control.test.py
+++ b/tests/gold_tests/h2/http2_flow_control.test.py
@@ -164,7 +164,13 @@
                 configuration: self._max_concurrent_streams,
             })
 
-        ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+        ts.Disk.ssl_multicert_yaml.AddLines(
+            """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
         ts.Disk.remap_config.AddLine(f'map / https://127.0.0.1:{self._server.Variables.https_port}')
 
diff --git a/tests/gold_tests/h2/http2_priority.test.py b/tests/gold_tests/h2/http2_priority.test.py
index 64b58c3..8b8168c 100644
--- a/tests/gold_tests/h2/http2_priority.test.py
+++ b/tests/gold_tests/h2/http2_priority.test.py
@@ -52,7 +52,13 @@
 ts.addDefaultSSLFiles()
 
 ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port))
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 ts.Disk.records_config.update(
     {
         'proxy.config.http2.stream_priority_enabled': 1,
diff --git a/tests/gold_tests/h2/http2_rst_stream.test.py b/tests/gold_tests/h2/http2_rst_stream.test.py
index caf2a71..339e0de 100644
--- a/tests/gold_tests/h2/http2_rst_stream.test.py
+++ b/tests/gold_tests/h2/http2_rst_stream.test.py
@@ -45,7 +45,13 @@
         'proxy.config.http.server_session_sharing.match': 'ip,sni,cert',
     })
 ts.Disk.remap_config.AddLine(f'map / https://127.0.0.1:{server.Variables.https_port}')
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 tr = Test.AddTestRun('Client sends RST_STREAM after DATA frame')
 tr.Processes.Default.StartBefore(server)
@@ -89,7 +95,13 @@
         'proxy.config.http.server_session_sharing.match': 'ip,sni,cert',
     })
 ts.Disk.remap_config.AddLine(f'map / https://127.0.0.1:{server.Variables.https_port}')
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 tr = Test.AddTestRun('Client sends RST_STREAM after HEADERS frame')
 tr.Processes.Default.StartBefore(server)
@@ -133,7 +145,13 @@
         'proxy.config.http.server_session_sharing.match': 'ip,sni,cert',
     })
 ts.Disk.remap_config.AddLine(f'map / https://127.0.0.1:{server.Variables.https_port}')
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 tr = Test.AddTestRun('Server sends RST_STREAM after HEADERS frame')
 tr.Processes.Default.StartBefore(server)
diff --git a/tests/gold_tests/h2/http2_write_threshold.test.py b/tests/gold_tests/h2/http2_write_threshold.test.py
index 694037a..53bfcae 100644
--- a/tests/gold_tests/h2/http2_write_threshold.test.py
+++ b/tests/gold_tests/h2/http2_write_threshold.test.py
@@ -89,7 +89,13 @@
         self._ts = tr.MakeATSProcess("ts", enable_tls=True, enable_cache=False)
 
         self._ts.addDefaultSSLFiles()
-        self._ts.Disk.ssl_multicert_config.AddLine("dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key")
+        self._ts.Disk.ssl_multicert_yaml.AddLines(
+            """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
         self._ts.Disk.remap_config.AddLine(f"map / https://example.com:{server_port}/")
 
diff --git a/tests/gold_tests/h2/httpbin.test.py b/tests/gold_tests/h2/httpbin.test.py
index 342f175..3d04d60 100644
--- a/tests/gold_tests/h2/httpbin.test.py
+++ b/tests/gold_tests/h2/httpbin.test.py
@@ -46,7 +46,13 @@
 ts.addDefaultSSLFiles()
 
 ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(httpbin.Variables.Port))
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 ts.Disk.records_config.update(
     {
         'proxy.config.http.insert_request_via_str': 1,
diff --git a/tests/gold_tests/h2/nghttp.test.py b/tests/gold_tests/h2/nghttp.test.py
index 071643a..b465e27 100644
--- a/tests/gold_tests/h2/nghttp.test.py
+++ b/tests/gold_tests/h2/nghttp.test.py
@@ -52,7 +52,13 @@
             httpbin.Variables.Port, Test.RunDirectory)
     ])
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.records_config.update(
     {
diff --git a/tests/gold_tests/h3/h3_sni_check.test.py b/tests/gold_tests/h3/h3_sni_check.test.py
index 4ac57c6..185ef70 100644
--- a/tests/gold_tests/h3/h3_sni_check.test.py
+++ b/tests/gold_tests/h3/h3_sni_check.test.py
@@ -67,7 +67,13 @@
         self._ts = ts
         # Configure TLS for Traffic Server.
         self._ts.addDefaultSSLFiles()
-        self._ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+        self._ts.Disk.ssl_multicert_yaml.AddLines(
+            """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
         self._ts.Disk.records_config.update(
             {
                 'proxy.config.diags.debug.enabled': 1,
diff --git a/tests/gold_tests/headers/cachedIMSRange.test.py b/tests/gold_tests/headers/cachedIMSRange.test.py
index 64af159..6bd064a 100644
--- a/tests/gold_tests/headers/cachedIMSRange.test.py
+++ b/tests/gold_tests/headers/cachedIMSRange.test.py
@@ -109,7 +109,13 @@
 ts = Test.MakeATSProcess("ts", enable_tls=True)
 ts.Disk.plugin_config.AddLine('xdebug.so --enable=x-cache,x-cache-key,via')
 ts.addDefaultSSLFiles()
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 ts.Disk.records_config.update(
     {
         'proxy.config.diags.debug.enabled': 1,
diff --git a/tests/gold_tests/headers/forwarded.test.py b/tests/gold_tests/headers/forwarded.test.py
index 8add15e..958e27a 100644
--- a/tests/gold_tests/headers/forwarded.test.py
+++ b/tests/gold_tests/headers/forwarded.test.py
@@ -99,7 +99,13 @@
             'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir)
         })
 
-    ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+    ts.Disk.ssl_multicert_yaml.AddLines(
+        """
+    ssl_multicert:
+      - dest_ip: "*"
+        ssl_cert_name: server.pem
+        ssl_key_name: server.key
+    """.split("\n"))
 
     ts.Disk.remap_config.AddLine('map http://www.no-oride.com http://127.0.0.1:{0}'.format(server.Variables.Port))
 
diff --git a/tests/gold_tests/headers/hsts.test.py b/tests/gold_tests/headers/hsts.test.py
index e543832..9aaa866 100644
--- a/tests/gold_tests/headers/hsts.test.py
+++ b/tests/gold_tests/headers/hsts.test.py
@@ -47,7 +47,13 @@
 
 ts.Disk.remap_config.AddLine('map https://www.example.com http://127.0.0.1:{0}'.format(server.Variables.Port))
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 # Test 1 - 200 Response
 tr = Test.AddTestRun()
diff --git a/tests/gold_tests/headers/via.test.py b/tests/gold_tests/headers/via.test.py
index 3ab495a..8417363 100644
--- a/tests/gold_tests/headers/via.test.py
+++ b/tests/gold_tests/headers/via.test.py
@@ -57,7 +57,13 @@
 ts.Disk.remap_config.AddLine(
     'map https://www.example.com http://127.0.0.1:{0}'.format(server.Variables.Port, ts.Variables.ssl_port))
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 # Set up to check the output after the tests have run.
 via_log_id = Test.Disk.File("via.log")
diff --git a/tests/gold_tests/ip_allow/ip_allow.test.py b/tests/gold_tests/ip_allow/ip_allow.test.py
index 0454a4e..0020e62 100644
--- a/tests/gold_tests/ip_allow/ip_allow.test.py
+++ b/tests/gold_tests/ip_allow/ip_allow.test.py
@@ -80,7 +80,13 @@
 # Configure TLS for Traffic Server for HTTP/2.
 ts.addDefaultSSLFiles()
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.records_config.update(
     {
@@ -248,7 +254,13 @@
         self._ts = ts
         # Configure TLS for Traffic Server.
         self._ts.addDefaultSSLFiles()
-        self._ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+        self._ts.Disk.ssl_multicert_yaml.AddLines(
+            """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
         self._ts.Disk.records_config.update(
             {
                 'proxy.config.diags.debug.enabled': 1,
diff --git a/tests/gold_tests/ip_allow/ip_category.test.py b/tests/gold_tests/ip_allow/ip_category.test.py
index 76a8c1b..3e0f29b 100644
--- a/tests/gold_tests/ip_allow/ip_category.test.py
+++ b/tests/gold_tests/ip_allow/ip_category.test.py
@@ -218,7 +218,13 @@
         Test_ip_category._ts = ts
 
         ts.addDefaultSSLFiles()
-        ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+        ts.Disk.ssl_multicert_yaml.AddLines(
+            """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
         ts.Disk.records_config.update(
             {
                 'proxy.config.diags.debug.enabled': 1,
diff --git a/tests/gold_tests/logging/new_log_flds.test.py b/tests/gold_tests/logging/new_log_flds.test.py
index 94be8ba..b646e43 100644
--- a/tests/gold_tests/logging/new_log_flds.test.py
+++ b/tests/gold_tests/logging/new_log_flds.test.py
@@ -53,7 +53,13 @@
 ts.Disk.remap_config.AddLine(
     'map https://reallyreallyreallyreallylong.com http://127.0.0.1:{1}/ip'.format(ts.Variables.ssl_port, httpbin.Variables.Port))
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.logging_yaml.AddLines(
     '''
diff --git a/tests/gold_tests/logging/qlog_quiche.test.py b/tests/gold_tests/logging/qlog_quiche.test.py
index cb86cf1..6d5a393 100644
--- a/tests/gold_tests/logging/qlog_quiche.test.py
+++ b/tests/gold_tests/logging/qlog_quiche.test.py
@@ -83,8 +83,13 @@
                     qlog:
                       file_base: log/test_qlog # we expect to have log/test_qlog-<TRACE ID>.sqlog
                   ''')
-        self._ts.Disk.ssl_multicert_config.AddLine(
-            f'dest_ip=* ssl_cert_name={ts.Variables.SSLDir}/server.pem ssl_key_name={ts.Variables.SSLDir}/server.key')
+        self._ts.Disk.ssl_multicert_yaml.AddLines(
+            f"""
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: {ts.Variables.SSLDir}/server.pem
+    ssl_key_name: {ts.Variables.SSLDir}/server.key
+""".split("\n"))
 
         self._ts.Disk.remap_config.AddLine(f'map / http://127.0.0.1:{self._server.Variables.http_port}')
 
diff --git a/tests/gold_tests/pluginTest/access_control/access_control.test.py b/tests/gold_tests/pluginTest/access_control/access_control.test.py
index 8e1f787..65ed2b3 100644
--- a/tests/gold_tests/pluginTest/access_control/access_control.test.py
+++ b/tests/gold_tests/pluginTest/access_control/access_control.test.py
@@ -39,7 +39,13 @@
     def setupTS(self):
         self.ts = Test.MakeATSProcess("ts", enable_tls=True)
         self.ts.addDefaultSSLFiles()
-        self.ts.Disk.ssl_multicert_config.AddLine("dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key")
+        self.ts.Disk.ssl_multicert_yaml.AddLines(
+            """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
         self.ts.Disk.records_config.update(
             {
                 "proxy.config.diags.debug.enabled": 1,
diff --git a/tests/gold_tests/pluginTest/cert_update/cert_update.test.py b/tests/gold_tests/pluginTest/cert_update/cert_update.test.py
index 3d2766a..bbbaa31 100644
--- a/tests/gold_tests/pluginTest/cert_update/cert_update.test.py
+++ b/tests/gold_tests/pluginTest/cert_update/cert_update.test.py
@@ -57,7 +57,13 @@
         'proxy.config.url_remap.pristine_host_hdr': 1
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server1.pem ssl_key_name=server1.pem')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server1.pem
+    ssl_key_name: server1.pem
+""".split("\n"))
 
 ts.Disk.remap_config.AddLines(
     [
diff --git a/tests/gold_tests/pluginTest/certifier/certifier.test.py b/tests/gold_tests/pluginTest/certifier/certifier.test.py
index cb397fd..33e444f 100644
--- a/tests/gold_tests/pluginTest/certifier/certifier.test.py
+++ b/tests/gold_tests/pluginTest/certifier/certifier.test.py
@@ -53,7 +53,13 @@
                 "proxy.config.ssl.server.cert.path": f'{self.ts.Variables.SSLDir}',
                 "proxy.config.ssl.server.private_key.path": f'{self.ts.Variables.SSLDir}',
             })
-        self.ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+        self.ts.Disk.ssl_multicert_yaml.AddLines(
+            """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
         self.ts.Disk.remap_config.AddLine(f"map / http://127.0.0.1:{self.server.Variables.http_port}/",)
         self.ts.Disk.plugin_config.AddLine(
             f'certifier.so -s {os.path.join(self.certPathDest, "store")} -m 1000 -c {os.path.join(self.certPathDest, "ca.cert")} -k {os.path.join(self.certPathDest, "ca.key")} -r {os.path.join(self.certPathDest, "ca-serial.txt")}'
@@ -128,7 +134,13 @@
                 "proxy.config.ssl.server.cert.path": f'{self.ts.Variables.SSLDir}',
                 "proxy.config.ssl.server.private_key.path": f'{self.ts.Variables.SSLDir}',
             })
-        self.ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+        self.ts.Disk.ssl_multicert_yaml.AddLines(
+            """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
         self.ts.Disk.remap_config.AddLine(f"map / http://127.0.0.1:{self.server.Variables.http_port}/",)
         self.ts.Disk.plugin_config.AddLine(
             f'certifier.so -s {os.path.join(self.certPathDest, "store")} -m 1000 -c {os.path.join(self.certPathDest, "ca.cert")} -k {os.path.join(self.certPathDest, "ca.key")} -r {os.path.join(self.certPathDest, "ca-serial.txt")}'
diff --git a/tests/gold_tests/pluginTest/client_context_dump/client_context_dump.test.py b/tests/gold_tests/pluginTest/client_context_dump/client_context_dump.test.py
index 195caf5..7645684 100644
--- a/tests/gold_tests/pluginTest/client_context_dump/client_context_dump.test.py
+++ b/tests/gold_tests/pluginTest/client_context_dump/client_context_dump.test.py
@@ -40,7 +40,13 @@
         'proxy.config.ssl.client.private_key.path': '{}'.format(ts.Variables.SSLDir),
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=one.com.pem ssl_key_name=one.com.pem')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: one.com.pem
+    ssl_key_name: one.com.pem
+""".split("\n"))
 
 ts.Disk.sni_yaml.AddLines(
     [
diff --git a/tests/gold_tests/pluginTest/healthchecks/healthchecks.test.py b/tests/gold_tests/pluginTest/healthchecks/healthchecks.test.py
index bbf1a96..28479e0 100644
--- a/tests/gold_tests/pluginTest/healthchecks/healthchecks.test.py
+++ b/tests/gold_tests/pluginTest/healthchecks/healthchecks.test.py
@@ -72,7 +72,13 @@
                 "proxy.config.ssl.server.private_key.path": f'{ts.Variables.SSLDir}',
                 "proxy.config.ssl.client.verify.server.policy": 'PERMISSIVE',
             })
-        ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+        ts.Disk.ssl_multicert_yaml.AddLines(
+            """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
         # Other configuration.
         ts.Disk.records_config.update({
diff --git a/tests/gold_tests/pluginTest/ja3_fingerprint/ja3_fingerprint.test.py b/tests/gold_tests/pluginTest/ja3_fingerprint/ja3_fingerprint.test.py
index 89eb57e..354c30c 100644
--- a/tests/gold_tests/pluginTest/ja3_fingerprint/ja3_fingerprint.test.py
+++ b/tests/gold_tests/pluginTest/ja3_fingerprint/ja3_fingerprint.test.py
@@ -98,7 +98,13 @@
         self._ts = Test.MakeATSProcess(name, enable_cache=False, enable_tls=True)
         JA3FingerprintTest._ts_counter += 1
         self._ts.addDefaultSSLFiles()
-        self._ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+        self._ts.Disk.ssl_multicert_yaml.AddLines(
+            """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
         server_port = self._server.Variables.https_port
         self._ts.Disk.remap_config.AddLine(f'map https://https.server.com https://https.backend.com:{server_port}')
 
diff --git a/tests/gold_tests/pluginTest/ja4_fingerprint/ja4_fingerprint.test.py b/tests/gold_tests/pluginTest/ja4_fingerprint/ja4_fingerprint.test.py
index 71de43c..cd2bb3c 100644
--- a/tests/gold_tests/pluginTest/ja4_fingerprint/ja4_fingerprint.test.py
+++ b/tests/gold_tests/pluginTest/ja4_fingerprint/ja4_fingerprint.test.py
@@ -127,7 +127,13 @@
 
         ts.Disk.remap_config.AddLine(f'map / http://localhost:{server_one.Variables.http_port}')
 
-        ts.Disk.ssl_multicert_config.AddLine(f'dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+        ts.Disk.ssl_multicert_yaml.AddLines(
+            """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
         plugin_args = 'ja4_fingerprint.so'
         if self.use_preserve:
diff --git a/tests/gold_tests/pluginTest/multiplexer/multiplexer.test.py b/tests/gold_tests/pluginTest/multiplexer/multiplexer.test.py
index c4ffe1d..3468e6b 100644
--- a/tests/gold_tests/pluginTest/multiplexer/multiplexer.test.py
+++ b/tests/gold_tests/pluginTest/multiplexer/multiplexer.test.py
@@ -130,7 +130,13 @@
                 'proxy.config.dns.nameservers': f'127.0.0.1:{self.dns.Variables.Port}',
                 'proxy.config.dns.resolv_conf': 'NULL',
             })
-        self.ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+        self.ts.Disk.ssl_multicert_yaml.AddLines(
+            """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
         skip_remap_param = ''
         if skip_post:
             skip_remap_param = ' @pparam=proxy.config.multiplexer.skip_post_put=1'
diff --git a/tests/gold_tests/pluginTest/server_push_preload/server_push_preload.test.py b/tests/gold_tests/pluginTest/server_push_preload/server_push_preload.test.py
index af8dc4d..9ca1347 100644
--- a/tests/gold_tests/pluginTest/server_push_preload/server_push_preload.test.py
+++ b/tests/gold_tests/pluginTest/server_push_preload/server_push_preload.test.py
@@ -71,7 +71,13 @@
 
 ts.addDefaultSSLFiles()
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}/ @plugin=server_push_preload.so'.format(microserver.Variables.Port))
 
diff --git a/tests/gold_tests/pluginTest/sslheaders/sslheaders.test.py b/tests/gold_tests/pluginTest/sslheaders/sslheaders.test.py
index 85c12b8..b5cdf9b 100644
--- a/tests/gold_tests/pluginTest/sslheaders/sslheaders.test.py
+++ b/tests/gold_tests/pluginTest/sslheaders/sslheaders.test.py
@@ -55,7 +55,13 @@
                     ts.Variables.port, ts.Variables.ssl_port)),
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLine('map http://bar.com http://127.0.0.1:{0}'.format(server.Variables.Port))
 ts.Disk.remap_config.AddLine('map https://bar.com http://127.0.0.1:{0}'.format(server.Variables.Port))
diff --git a/tests/gold_tests/pluginTest/stale_response/stale_response_no_default.replay.yaml b/tests/gold_tests/pluginTest/stale_response/stale_response_no_default.replay.yaml
index 35d72c5..d096105 100644
--- a/tests/gold_tests/pluginTest/stale_response/stale_response_no_default.replay.yaml
+++ b/tests/gold_tests/pluginTest/stale_response/stale_response_no_default.replay.yaml
@@ -135,7 +135,7 @@
         - [ Content-Type, image/jpeg ]
         - [ Content-Length, 100 ]
         - [ Connection, keep-alive ]
-        - [ Cache-Control, max-age=1 stale-if-error=30 ]
+        - [ Cache-Control, "max-age=1, stale-if-error=30" ]
         - [ X-Response, fourth-response ]
 
     # We better have gone back to the origin and gotten second-response.
diff --git a/tests/gold_tests/pluginTest/stale_response/stale_response_with_force_sie.replay.yaml b/tests/gold_tests/pluginTest/stale_response/stale_response_with_force_sie.replay.yaml
index 10a8cd6..5837d68 100644
--- a/tests/gold_tests/pluginTest/stale_response/stale_response_with_force_sie.replay.yaml
+++ b/tests/gold_tests/pluginTest/stale_response/stale_response_with_force_sie.replay.yaml
@@ -43,7 +43,7 @@
         - [ Content-Length, 100 ]
         - [ Connection, keep-alive ]
         # Configure a small stale-if-error.
-        - [ Cache-Control, max-age=1 stale-if-error=1 ]
+        - [ Cache-Control, "max-age=1, stale-if-error=1" ]
         - [ X-Response, first-response ]
 
     proxy-response:
diff --git a/tests/gold_tests/pluginTest/stale_response/stale_response_with_force_swr.replay.yaml b/tests/gold_tests/pluginTest/stale_response/stale_response_with_force_swr.replay.yaml
index a980bb5..7a54ee9 100644
--- a/tests/gold_tests/pluginTest/stale_response/stale_response_with_force_swr.replay.yaml
+++ b/tests/gold_tests/pluginTest/stale_response/stale_response_with_force_swr.replay.yaml
@@ -42,14 +42,14 @@
         - [ Connection, keep-alive ]
         # The low stale-while-revalidate should be overridden by
         # --force-stale-while-revalidate.
-        - [ Cache-Control, max-age=1 stale-while-revalidate=1 ]
+        - [ Cache-Control, "max-age=1, stale-while-revalidate=1" ]
         - [ X-Response, first-response ]
 
     proxy-response:
       status: 200
       headers:
         fields:
-        - [ Cache-Control, { value: "max-age=1 stale-while-revalidate=1", as: equal } ]
+        - [ Cache-Control, { value: "max-age=1, stale-while-revalidate=1", as: equal } ]
         - [ X-Response, { value: first-response, as: equal } ]
 
   - client-request:
diff --git a/tests/gold_tests/pluginTest/stek_share/stek_share.test.py b/tests/gold_tests/pluginTest/stek_share/stek_share.test.py
index fc6a3a6..31f7241 100644
--- a/tests/gold_tests/pluginTest/stek_share/stek_share.test.py
+++ b/tests/gold_tests/pluginTest/stek_share/stek_share.test.py
@@ -164,16 +164,18 @@
         'proxy.config.exec_thread.limit': 4,
         'proxy.config.ssl.server.cert.path': '{0}'.format(Test.RunDirectory),
         'proxy.config.ssl.server.private_key.path': '{0}'.format(Test.RunDirectory),
-        'proxy.config.ssl.session_cache.mode': 2,
-        'proxy.config.ssl.session_cache.size': 1024,
-        'proxy.config.ssl.session_cache.timeout': 7200,
-        'proxy.config.ssl.session_cache.num_buckets': 16,
         'proxy.config.ssl.server.session_ticket.enable': 1,
         'proxy.config.ssl.server.cipher_suite':
             'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA'
     })
 ts1.Disk.plugin_config.AddLine('stek_share.so {0}'.format(stek_share_conf_path_1))
-ts1.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=self_signed.crt ssl_key_name=self_signed.key')
+ts1.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: self_signed.crt
+    ssl_key_name: self_signed.key
+""".split("\n"))
 ts1.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port))
 
 ts2.Disk.records_config.update(
@@ -184,16 +186,18 @@
         'proxy.config.exec_thread.limit': 4,
         'proxy.config.ssl.server.cert.path': '{0}'.format(Test.RunDirectory),
         'proxy.config.ssl.server.private_key.path': '{0}'.format(Test.RunDirectory),
-        'proxy.config.ssl.session_cache.mode': 2,
-        'proxy.config.ssl.session_cache.size': 1024,
-        'proxy.config.ssl.session_cache.timeout': 7200,
-        'proxy.config.ssl.session_cache.num_buckets': 16,
         'proxy.config.ssl.server.session_ticket.enable': 1,
         'proxy.config.ssl.server.cipher_suite':
             'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA'
     })
 ts2.Disk.plugin_config.AddLine('stek_share.so {0}'.format(stek_share_conf_path_2))
-ts2.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=self_signed.crt ssl_key_name=self_signed.key')
+ts2.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: self_signed.crt
+    ssl_key_name: self_signed.key
+""".split("\n"))
 ts2.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port))
 
 ts3.Disk.records_config.update(
@@ -204,16 +208,18 @@
         'proxy.config.exec_thread.limit': 4,
         'proxy.config.ssl.server.cert.path': '{0}'.format(Test.RunDirectory),
         'proxy.config.ssl.server.private_key.path': '{0}'.format(Test.RunDirectory),
-        'proxy.config.ssl.session_cache.mode': 2,
-        'proxy.config.ssl.session_cache.size': 1024,
-        'proxy.config.ssl.session_cache.timeout': 7200,
-        'proxy.config.ssl.session_cache.num_buckets': 16,
         'proxy.config.ssl.server.session_ticket.enable': 1,
         'proxy.config.ssl.server.cipher_suite':
             'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA'
     })
 ts3.Disk.plugin_config.AddLine('stek_share.so {0}'.format(stek_share_conf_path_3))
-ts3.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=self_signed.crt ssl_key_name=self_signed.key')
+ts3.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: self_signed.crt
+    ssl_key_name: self_signed.key
+""".split("\n"))
 ts3.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port))
 
 ts4.Disk.records_config.update(
@@ -224,16 +230,18 @@
         'proxy.config.exec_thread.limit': 4,
         'proxy.config.ssl.server.cert.path': '{0}'.format(Test.RunDirectory),
         'proxy.config.ssl.server.private_key.path': '{0}'.format(Test.RunDirectory),
-        'proxy.config.ssl.session_cache.mode': 2,
-        'proxy.config.ssl.session_cache.size': 1024,
-        'proxy.config.ssl.session_cache.timeout': 7200,
-        'proxy.config.ssl.session_cache.num_buckets': 16,
         'proxy.config.ssl.server.session_ticket.enable': 1,
         'proxy.config.ssl.server.cipher_suite':
             'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA'
     })
 ts4.Disk.plugin_config.AddLine('stek_share.so {0}'.format(stek_share_conf_path_4))
-ts4.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=self_signed.crt ssl_key_name=self_signed.key')
+ts4.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: self_signed.crt
+    ssl_key_name: self_signed.key
+""".split("\n"))
 ts4.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port))
 
 ts5.Disk.records_config.update(
@@ -244,16 +252,18 @@
         'proxy.config.exec_thread.limit': 4,
         'proxy.config.ssl.server.cert.path': '{0}'.format(Test.RunDirectory),
         'proxy.config.ssl.server.private_key.path': '{0}'.format(Test.RunDirectory),
-        'proxy.config.ssl.session_cache.mode': 2,
-        'proxy.config.ssl.session_cache.size': 1024,
-        'proxy.config.ssl.session_cache.timeout': 7200,
-        'proxy.config.ssl.session_cache.num_buckets': 16,
         'proxy.config.ssl.server.session_ticket.enable': 1,
         'proxy.config.ssl.server.cipher_suite':
             'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA'
     })
 ts5.Disk.plugin_config.AddLine('stek_share.so {0}'.format(stek_share_conf_path_5))
-ts5.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=self_signed.crt ssl_key_name=self_signed.key')
+ts5.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: self_signed.crt
+    ssl_key_name: self_signed.key
+""".split("\n"))
 ts5.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port))
 
 
diff --git a/tests/gold_tests/pluginTest/test_hooks/test_hooks.test.py b/tests/gold_tests/pluginTest/test_hooks/test_hooks.test.py
index 20ca7fb..b0cb46d 100644
--- a/tests/gold_tests/pluginTest/test_hooks/test_hooks.test.py
+++ b/tests/gold_tests/pluginTest/test_hooks/test_hooks.test.py
@@ -49,7 +49,13 @@
         'proxy.config.diags.debug.tags': 'http|test_hooks',
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 Test.PrepareTestPlugin(os.path.join(Test.Variables.AtsTestPluginsDir, 'test_hooks.so'), ts)
 
diff --git a/tests/gold_tests/pluginTest/traffic_dump/traffic_dump.test.py b/tests/gold_tests/pluginTest/traffic_dump/traffic_dump.test.py
index 645c5c5..57e7146 100644
--- a/tests/gold_tests/pluginTest/traffic_dump/traffic_dump.test.py
+++ b/tests/gold_tests/pluginTest/traffic_dump/traffic_dump.test.py
@@ -59,7 +59,13 @@
         'proxy.config.http.connect_ports': f"{server.Variables.http_port}",
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLines(
     [
diff --git a/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_http3.test.py b/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_http3.test.py
index 5c8bfa6..758b469 100644
--- a/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_http3.test.py
+++ b/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_http3.test.py
@@ -60,7 +60,13 @@
         'proxy.config.ssl.client.verify.server.policy': 'PERMISSIVE',
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLine(f'map https://www.client_only_tls.com/ http://127.0.0.1:{server.Variables.http_port}')
 ts.Disk.remap_config.AddLine(f'map https://www.tls.com/ https://127.0.0.1:{server.Variables.https_port}')
diff --git a/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_response_body.test.py b/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_response_body.test.py
index f816b63..1b296b5 100644
--- a/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_response_body.test.py
+++ b/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_response_body.test.py
@@ -52,7 +52,13 @@
         'proxy.config.ssl.client.verify.server.policy': 'PERMISSIVE',
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLines([
     f'map / http://127.0.0.1:{server.Variables.http_port}',
diff --git a/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_sni_filter.test.py b/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_sni_filter.test.py
index 710d2c9..b3ff3ca 100644
--- a/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_sni_filter.test.py
+++ b/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_sni_filter.test.py
@@ -53,7 +53,13 @@
         'proxy.config.ssl.client.verify.server.policy': 'PERMISSIVE',
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLine(f'map / https://127.0.0.1:{server.Variables.https_port}')
 
diff --git a/tests/gold_tests/pluginTest/transform/transaction_data_sink.test.py b/tests/gold_tests/pluginTest/transform/transaction_data_sink.test.py
index 46d71b8..a632521 100644
--- a/tests/gold_tests/pluginTest/transform/transaction_data_sink.test.py
+++ b/tests/gold_tests/pluginTest/transform/transaction_data_sink.test.py
@@ -51,7 +51,13 @@
             })
         self.ts.addDefaultSSLFiles()
         self.ts.Disk.remap_config.AddLine(f'map / http://localhost:{self.server.Variables.http_port}/')
-        self.ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+        self.ts.Disk.ssl_multicert_yaml.AddLines(
+            """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
         self.ts.Disk.plugin_config.AddLine('txn_data_sink.so')
 
         # All of the bodies that contained "not_dumped" were not configured to
diff --git a/tests/gold_tests/pluginTest/tsapi/test_TSHttpSsnInfo.test.py b/tests/gold_tests/pluginTest/tsapi/test_TSHttpSsnInfo.test.py
index 3d1dad1..2685ee3 100644
--- a/tests/gold_tests/pluginTest/tsapi/test_TSHttpSsnInfo.test.py
+++ b/tests/gold_tests/pluginTest/tsapi/test_TSHttpSsnInfo.test.py
@@ -49,7 +49,13 @@
 
 ts.Disk.remap_config.AddLines(['map /httpbin/ http://127.0.0.1:{0}/'.format(httpbin.Variables.Port)])
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 Test.PrepareTestPlugin(
     os.path.join(Test.Variables.AtsBuildGoldTestsDir, 'pluginTest', 'tsapi', '.libs', 'test_TSHttpSsnInfo.so'), ts)
diff --git a/tests/gold_tests/pluginTest/tsapi/test_TSVConnPPInfo.test.py b/tests/gold_tests/pluginTest/tsapi/test_TSVConnPPInfo.test.py
index 0c45109..3adea66 100644
--- a/tests/gold_tests/pluginTest/tsapi/test_TSVConnPPInfo.test.py
+++ b/tests/gold_tests/pluginTest/tsapi/test_TSVConnPPInfo.test.py
@@ -49,7 +49,13 @@
 
 ts.Disk.remap_config.AddLines(['map /httpbin/ http://127.0.0.1:{0}/'.format(httpbin.Variables.Port)])
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 Test.PrepareTestPlugin(
     os.path.join(Test.Variables.AtsBuildGoldTestsDir, 'pluginTest', 'tsapi', '.libs', 'test_TSVConnPPInfo.so'), ts)
diff --git a/tests/gold_tests/pluginTest/tsapi/tsapi.test.py b/tests/gold_tests/pluginTest/tsapi/tsapi.test.py
index 84b5ba4..2b4e2e2 100644
--- a/tests/gold_tests/pluginTest/tsapi/tsapi.test.py
+++ b/tests/gold_tests/pluginTest/tsapi/tsapi.test.py
@@ -55,7 +55,13 @@
         'proxy.config.diags.debug.tags': f'http|{plugin_name}',
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 rp = os.path.join(Test.Variables.AtsBuildGoldTestsDir, 'pluginTest', 'tsapi', '.libs', f'{plugin_name}.so')
 ts.Setup.Copy(rp, ts.Env['PROXY_CONFIG_PLUGIN_PLUGIN_DIR'])
diff --git a/tests/gold_tests/pluginTest/txn_box/basic/txn_box_tls-cert.test.py b/tests/gold_tests/pluginTest/txn_box/basic/txn_box_tls-cert.test.py
index 00181e4..496e5f0 100644
--- a/tests/gold_tests/pluginTest/txn_box/basic/txn_box_tls-cert.test.py
+++ b/tests/gold_tests/pluginTest/txn_box/basic/txn_box_tls-cert.test.py
@@ -58,4 +58,10 @@
         'proxy.config.ssl.client.cert.path': ts.Variables.SSLDir,
         'proxy.config.ssl.client.cert.filename': "bravo-signed.cert"
     })
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
diff --git a/tests/gold_tests/pluginTest/txn_box/basic/txn_box_tls.test.py b/tests/gold_tests/pluginTest/txn_box/basic/txn_box_tls.test.py
index 0807534..e23165d 100644
--- a/tests/gold_tests/pluginTest/txn_box/basic/txn_box_tls.test.py
+++ b/tests/gold_tests/pluginTest/txn_box/basic/txn_box_tls.test.py
@@ -53,4 +53,10 @@
         'proxy.config.http.server_ports': '{0} {1}:ssl'.format(ts.Variables.port, ts.Variables.ssl_port),
         'proxy.config.ssl.client.verify.server.policy': 'DISABLED'
     })
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
diff --git a/tests/gold_tests/pluginTest/txn_box/ct_header/txn_box_ct_header.test.py b/tests/gold_tests/pluginTest/txn_box/ct_header/txn_box_ct_header.test.py
index c5fc5e9..07cae3c 100644
--- a/tests/gold_tests/pluginTest/txn_box/ct_header/txn_box_ct_header.test.py
+++ b/tests/gold_tests/pluginTest/txn_box/ct_header/txn_box_ct_header.test.py
@@ -56,4 +56,10 @@
         'proxy.config.http.server_ports': '{0} {1}:ssl'.format(ts.Variables.port, ts.Variables.ssl_port),
         'proxy.config.ssl.client.verify.server.policy': 'DISABLED'
     })
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
diff --git a/tests/gold_tests/pluginTest/txn_box/prod/txn_box_mTLS.test.py b/tests/gold_tests/pluginTest/txn_box/prod/txn_box_mTLS.test.py
index c76d0fa..bfb76b2 100644
--- a/tests/gold_tests/pluginTest/txn_box/prod/txn_box_mTLS.test.py
+++ b/tests/gold_tests/pluginTest/txn_box/prod/txn_box_mTLS.test.py
@@ -67,4 +67,10 @@
         'proxy.config.ssl.client.certification_level': 2,
         'proxy.config.http.server_ports': '{0} {1}:ssl'.format(ts.Variables.port, ts.Variables.ssl_port)
     })
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
diff --git a/tests/gold_tests/pluginTest/txn_box/ramp/multi_ramp_common.py b/tests/gold_tests/pluginTest/txn_box/ramp/multi_ramp_common.py
index b7322dc..9feddac 100644
--- a/tests/gold_tests/pluginTest/txn_box/ramp/multi_ramp_common.py
+++ b/tests/gold_tests/pluginTest/txn_box/ramp/multi_ramp_common.py
@@ -97,7 +97,13 @@
             'proxy.config.ssl.server.cipher_suite':
                 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:RC4-SHA:RC4-MD5:AES128-SHA:AES256-SHA:DES-CBC3-SHA!SRP:!DSS:!PSK:!aNULL:!eNULL:!SSLv2'
         })
-    ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+    ts.Disk.ssl_multicert_yaml.AddLines(
+        """
+    ssl_multicert:
+      - dest_ip: "*"
+        ssl_cert_name: server.pem
+        ssl_key_name: server.key
+    """.split("\n"))
 
     pv_client = tr.Variables.CLIENT
 
diff --git a/tests/gold_tests/pluginTest/txn_box/remap/txn_box_remap-base.test.py b/tests/gold_tests/pluginTest/txn_box/remap/txn_box_remap-base.test.py
index 04b08f7..6e16c73 100644
--- a/tests/gold_tests/pluginTest/txn_box/remap/txn_box_remap-base.test.py
+++ b/tests/gold_tests/pluginTest/txn_box/remap/txn_box_remap-base.test.py
@@ -60,4 +60,10 @@
         'proxy.config.ssl.client.verify.server.policy': "disabled"
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
diff --git a/tests/gold_tests/pluginTest/url_sig/url_sig.test.py b/tests/gold_tests/pluginTest/url_sig/url_sig.test.py
index 373255c..16c2927 100644
--- a/tests/gold_tests/pluginTest/url_sig/url_sig.test.py
+++ b/tests/gold_tests/pluginTest/url_sig/url_sig.test.py
@@ -96,7 +96,13 @@
         'proxy.config.ssl.server.private_key.path': ts.Variables.SSLDir,
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 # Use unchanged incoming URL.
 #
diff --git a/tests/gold_tests/post/post-continue.test.py b/tests/gold_tests/post/post-continue.test.py
index 5dfd750..b170f0e 100644
--- a/tests/gold_tests/post/post-continue.test.py
+++ b/tests/gold_tests/post/post-continue.test.py
@@ -47,7 +47,13 @@
 ts2.addDefaultSSLFiles()
 
 ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.http_port))
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 ts.Disk.records_config.update(
     {
         'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir),
@@ -56,7 +62,13 @@
         'proxy.config.diags.debug.tags': 'http',
     })
 ts2.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.http_port))
-ts2.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts2.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 ts2.Disk.records_config.update(
     {
         'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir),
diff --git a/tests/gold_tests/post/post-early-return.test.py b/tests/gold_tests/post/post-early-return.test.py
index a8e85e2..054fd08 100644
--- a/tests/gold_tests/post/post-early-return.test.py
+++ b/tests/gold_tests/post/post-early-return.test.py
@@ -51,7 +51,13 @@
         'map /five http://127.0.0.1:{0}'.format(Test.Variables.upstream_port5),
         'map /six http://127.0.0.1:{0}'.format(Test.Variables.upstream_port6),
     ])
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 ts.Disk.records_config.update(
     {
         'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir),
diff --git a/tests/gold_tests/post_slow_server/post_slow_server.test.py b/tests/gold_tests/post_slow_server/post_slow_server.test.py
index 4447eb9..1f4612b 100644
--- a/tests/gold_tests/post_slow_server/post_slow_server.test.py
+++ b/tests/gold_tests/post_slow_server/post_slow_server.test.py
@@ -41,7 +41,13 @@
         'proxy.config.http2.no_activity_timeout_in': 150,
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 Test.GetTcpPort("server_port")
 
diff --git a/tests/gold_tests/proxy_protocol/proxy_protocol.test.py b/tests/gold_tests/proxy_protocol/proxy_protocol.test.py
index bd008b9..5150849 100644
--- a/tests/gold_tests/proxy_protocol/proxy_protocol.test.py
+++ b/tests/gold_tests/proxy_protocol/proxy_protocol.test.py
@@ -44,7 +44,13 @@
             enable_proxy_protocol_cp_src=enable_cp)
 
         self.ts.addDefaultSSLFiles()
-        self.ts.Disk.ssl_multicert_config.AddLine("dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key")
+        self.ts.Disk.ssl_multicert_yaml.AddLines(
+            """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
         self.ts.Disk.remap_config.AddLine(f"map / http://127.0.0.1:{self.server.Variables.http_port}/")
 
@@ -146,7 +152,13 @@
         self._ts = tr.MakeATSProcess(process_name, enable_tls=True, enable_cache=False)
 
         self._ts.addDefaultSSLFiles()
-        self._ts.Disk.ssl_multicert_config.AddLine("dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key")
+        self._ts.Disk.ssl_multicert_yaml.AddLines(
+            """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
         scheme = 'https' if self._is_tls_to_origin else 'http'
         server_port = self._server.Variables.https_port if self._is_tls_to_origin else self._server.Variables.http_port
         self._ts.Disk.remap_config.AddLine(f"map / {scheme}://backend.pp.origin.com:{server_port}/")
diff --git a/tests/gold_tests/remap/remap_https.test.py b/tests/gold_tests/remap/remap_https.test.py
index 19f74ae..0c53fa2 100644
--- a/tests/gold_tests/remap/remap_https.test.py
+++ b/tests/gold_tests/remap/remap_https.test.py
@@ -57,7 +57,13 @@
 ts.Disk.remap_config.AddLine(
     'map https://www.anotherexample.com https://127.0.0.1:{0}'.format(server2.Variables.SSL_Port, ts.Variables.ssl_port))
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 # call localhost straight
 tr = Test.AddTestRun()
diff --git a/tests/gold_tests/remap/remap_ws.test.py b/tests/gold_tests/remap/remap_ws.test.py
index 5d677a0..3e12244 100644
--- a/tests/gold_tests/remap/remap_ws.test.py
+++ b/tests/gold_tests/remap/remap_ws.test.py
@@ -51,7 +51,13 @@
         'map wss://www.example.com:{1} ws://127.0.0.1:{0}'.format(server.Variables.Port, ts.Variables.ssl_port),
     ])
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 if not Condition.CurlUsingUnixDomainSocket():
     # wss mapping
diff --git a/tests/gold_tests/slow_post/server_abort.test.py b/tests/gold_tests/slow_post/server_abort.test.py
index 0586785..394e7b3 100644
--- a/tests/gold_tests/slow_post/server_abort.test.py
+++ b/tests/gold_tests/slow_post/server_abort.test.py
@@ -30,7 +30,13 @@
     # on the origin server so that it aborts the connection upon receiving a
     # request
     'map / https://127.0.0.1:{0}'.format(server.Variables.Port))
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=aaa-signed.pem ssl_key_name=aaa-signed.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: aaa-signed.pem
+    ssl_key_name: aaa-signed.key
+""".split("\n"))
 ts.Disk.records_config.update(
     {
         'proxy.config.diags.debug.tags': 'http|dns',
diff --git a/tests/gold_tests/timeout/accept_timeout.test.py b/tests/gold_tests/timeout/accept_timeout.test.py
index 1521a12..80a7d44 100644
--- a/tests/gold_tests/timeout/accept_timeout.test.py
+++ b/tests/gold_tests/timeout/accept_timeout.test.py
@@ -38,7 +38,13 @@
         'proxy.config.net.defer_accept': 0  # Must turn off defer accept to test the raw TCP case
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 # case 1 TLS with no data
 tr = Test.AddTestRun("tr")
diff --git a/tests/gold_tests/timeout/active_timeout.test.py b/tests/gold_tests/timeout/active_timeout.test.py
index 5f5d1d5..5227cc0 100644
--- a/tests/gold_tests/timeout/active_timeout.test.py
+++ b/tests/gold_tests/timeout/active_timeout.test.py
@@ -43,7 +43,13 @@
 
 ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}/'.format(server.Variables.Port))
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 tr = Test.AddTestRun("tr")
 tr.Processes.Default.StartBefore(server)
diff --git a/tests/gold_tests/timeout/http2_no_activity_timeout.test.py b/tests/gold_tests/timeout/http2_no_activity_timeout.test.py
index bab2202..ab910e0 100644
--- a/tests/gold_tests/timeout/http2_no_activity_timeout.test.py
+++ b/tests/gold_tests/timeout/http2_no_activity_timeout.test.py
@@ -75,9 +75,13 @@
 
         self._ts.addSSLfile("ssl/cert.crt")
         self._ts.addSSLfile("ssl/private-key.key")
-        self._ts.Disk.ssl_multicert_config.AddLine(
-            f'dest_ip=* ssl_cert_name={self._ts.Variables.SSLDir}/cert.crt '
-            f'ssl_key_name={self._ts.Variables.SSLDir}/private-key.key')
+        self._ts.Disk.ssl_multicert_yaml.AddLines(
+            f"""
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: {self._ts.Variables.SSLDir}/cert.crt
+    ssl_key_name: {self._ts.Variables.SSLDir}/private-key.key
+""".split("\n"))
 
         self._ts.Disk.records_config.update(
             {
diff --git a/tests/gold_tests/timeout/inactive_client_timeout.test.py b/tests/gold_tests/timeout/inactive_client_timeout.test.py
index 6c0a06f..84d3de4 100644
--- a/tests/gold_tests/timeout/inactive_client_timeout.test.py
+++ b/tests/gold_tests/timeout/inactive_client_timeout.test.py
@@ -46,7 +46,13 @@
         'map / http://127.0.0.1:{0}'.format(server.Variables.http_port),
     ])
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 #
 # Test 1: Verify that server delay does not trigger client activity timeout.
diff --git a/tests/gold_tests/timeout/inactive_timeout.test.py b/tests/gold_tests/timeout/inactive_timeout.test.py
index c1fc3d8..2265763 100644
--- a/tests/gold_tests/timeout/inactive_timeout.test.py
+++ b/tests/gold_tests/timeout/inactive_timeout.test.py
@@ -40,7 +40,13 @@
 
 ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}/'.format(server.Variables.Port))
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 tr = Test.AddTestRun("tr")
 tr.Processes.Default.StartBefore(server)
diff --git a/tests/gold_tests/timeout/quic_no_activity_timeout.test.py b/tests/gold_tests/timeout/quic_no_activity_timeout.test.py
index 0b688c8..f50146f 100644
--- a/tests/gold_tests/timeout/quic_no_activity_timeout.test.py
+++ b/tests/gold_tests/timeout/quic_no_activity_timeout.test.py
@@ -74,9 +74,13 @@
             self._ts.Disk.records_config.update(self.extra_recs)
 
         self._ts.Disk.remap_config.AddLine(f'map / http://127.0.0.1:{self._server.Variables.http_port}')
-        self._ts.Disk.ssl_multicert_config.AddLine(
-            f'dest_ip=* ssl_cert_name={self._ts.Variables.SSLDir}/cert.crt ssl_key_name={self._ts.Variables.SSLDir}/private-key.key'
-        )
+        self._ts.Disk.ssl_multicert_yaml.AddLines(
+            f"""
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: {self._ts.Variables.SSLDir}/cert.crt
+    ssl_key_name: {self._ts.Variables.SSLDir}/private-key.key
+""".split("\n"))
 
     def run(self, check_for_max_idle_timeout=False):
         """Run the test."""
diff --git a/tests/gold_tests/tls/allow-plain.test.py b/tests/gold_tests/tls/allow-plain.test.py
index d479138..0e2802b 100644
--- a/tests/gold_tests/tls/allow-plain.test.py
+++ b/tests/gold_tests/tls/allow-plain.test.py
@@ -53,7 +53,13 @@
         'map /post http://127.0.0.1:{0}/post'.format(server.Variables.http_port),
     ])
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 big_post_body = "0123456789" * 50000
 big_post_body_file = open(os.path.join(Test.RunDirectory, "big_post_body"), "w")
diff --git a/tests/gold_tests/tls/exit_on_cert_load_fail.test.py b/tests/gold_tests/tls/exit_on_cert_load_fail.test.py
index c075e1a..cbac56e 100644
--- a/tests/gold_tests/tls/exit_on_cert_load_fail.test.py
+++ b/tests/gold_tests/tls/exit_on_cert_load_fail.test.py
@@ -58,7 +58,13 @@
             # Also setup the server certs so that issues are limited to client
             # cert loading.
             self._ts.addDefaultSSLFiles()
-        self._ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+        self._ts.Disk.ssl_multicert_yaml.AddLines(
+            """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
         self._ts.Disk.records_config.update(
             {
                 'proxy.config.ssl.server.cert.path': f'{ts.Variables.SSLDir}',
diff --git a/tests/gold_tests/tls/ssl_key_dialog.test.py b/tests/gold_tests/tls/ssl_key_dialog.test.py
index dd9438f..107e4d7 100644
--- a/tests/gold_tests/tls/ssl_key_dialog.test.py
+++ b/tests/gold_tests/tls/ssl_key_dialog.test.py
@@ -39,10 +39,14 @@
         'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir),
     })
 
-ts.Disk.ssl_multicert_config.AddLines(
-    [
-        'dest_ip=* ssl_cert_name=passphrase.pem ssl_key_name=passphrase.key ssl_key_dialog="exec:/bin/bash -c \'echo -n passphrase\'"',
-    ])
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: passphrase.pem
+    ssl_key_name: passphrase.key
+    ssl_key_dialog: "exec:/bin/bash -c 'echo -n passphrase'"
+""".split("\n"))
 
 request_header = {"headers": "GET / HTTP/1.1\r\nHost: bogus\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
 response_header = {"headers": "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", "timestamp": "1469733493.993", "body": "success!"}
@@ -63,12 +67,16 @@
 
 tr2 = Test.AddTestRun("Update config files")
 # Update the multicert config
-sslcertpath = ts.Disk.ssl_multicert_config.AbsPath
+sslcertpath = ts.Disk.ssl_multicert_yaml.AbsPath
 tr2.Disk.File(sslcertpath, id="ssl_multicert_config", typename="ats:config"),
 tr2.Disk.ssl_multicert_config.AddLines(
-    [
-        'dest_ip=* ssl_cert_name=passphrase2.pem ssl_key_name=passphrase2.key ssl_key_dialog="exec:/bin/bash -c \'echo -n passphrase\'"',
-    ])
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: passphrase2.pem
+    ssl_key_name: passphrase2.key
+    ssl_key_dialog: "exec:/bin/bash -c 'echo -n passphrase'"
+""".split("\n"))
 tr2.StillRunningAfter = ts
 tr2.StillRunningAfter = server
 tr2.Processes.Default.Command = 'echo Updated configs'
@@ -90,7 +98,7 @@
 p.Env = ts.Env
 p.ReturnCode = 0
 await_config_reload = tr.Processes.Process(f'config_reload_succeeded', 'sleep 30')
-await_config_reload.Ready = When.FileContains(ts.Disk.diags_log.Name, "ssl_multicert.config finished loading", 2)
+await_config_reload.Ready = When.FileContains(ts.Disk.diags_log.Name, "ssl_multicert.yaml finished loading", 2)
 p.StartBefore(await_config_reload)
 
 tr3 = Test.AddTestRun("use a key with passphrase")
diff --git a/tests/gold_tests/tls/ssl_multicert_loader.test.py b/tests/gold_tests/tls/ssl_multicert_loader.test.py
index 60834b1..6b74da7 100644
--- a/tests/gold_tests/tls/ssl_multicert_loader.test.py
+++ b/tests/gold_tests/tls/ssl_multicert_loader.test.py
@@ -15,7 +15,7 @@
 #  limitations under the License.
 
 Test.Summary = '''
-Test reloading ssl_multicert.config with errors and keeping around the old ssl config structure
+Test reloading ssl_multicert.yaml with errors and keeping around the old ssl config structure
 '''
 
 sni_domain = 'example.com'
@@ -39,7 +39,13 @@
 
 ts.Disk.remap_config.AddLine(f'map / http://127.0.0.1:{server.Variables.Port}')
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 tr = Test.AddTestRun("ensure we can connect for SNI $sni_domain")
 tr.Processes.Default.StartBefore(Test.Processes.ts)
@@ -53,15 +59,19 @@
 tr.Processes.Default.Streams.stderr = Testers.IncludesExpression(f"CN={sni_domain}", "Check response")
 
 tr2 = Test.AddTestRun("Update config files")
-# Update the configs
-sslcertpath = ts.Disk.ssl_multicert_config.AbsPath
+# Update the configs - overwrite the ssl_multicert.yaml file with an invalid config
+sslcertpath = ts.Disk.ssl_multicert_yaml.AbsPath
 
-tr2.Disk.File(sslcertpath, id="ssl_multicert_config", typename="ats:config")
-tr2.Disk.ssl_multicert_config.AddLines(
-    [
-        'ssl_cert_name=server_does_not_exist.pem ssl_key_name=server_does_not_exist.key',
-        'dest_ip=* ssl_cert_name=server.pem_doesnotexist ssl_key_name=server.key',
-    ])
+tr2.Disk.File(sslcertpath, id="ssl_multicert_update", typename="ats:config")
+tr2.Disk.ssl_multicert_update.AddLines(
+    """
+ssl_multicert:
+  - ssl_cert_name: server_does_not_exist.pem
+    ssl_key_name: server_does_not_exist.key
+  - dest_ip: "*"
+    ssl_cert_name: server.pem_doesnotexist
+    ssl_key_name: server.key
+""".split("\n"))
 tr2.StillRunningAfter = ts
 tr2.StillRunningAfter = server
 tr2.Processes.Default.Command = 'echo Updated configs'
@@ -76,7 +86,7 @@
 tr2reload.Processes.Default.ReturnCode = 0
 ts.Disk.diags_log.Content = Testers.ContainsExpression('ERROR: ', 'ERROR')
 
-# Reload of ssl_multicert.config should fail, BUT the old config structure
+# Reload of ssl_multicert.yaml should fail, BUT the old config structure
 # should be in place to successfully answer for the test domain
 tr3 = Test.AddTestRun("Make request again for $sni_domain")
 # Wait for the reload to complete
@@ -95,9 +105,13 @@
 # to catch if the current default (1) changes in the future
 
 ts2 = Test.MakeATSProcess("ts2", enable_tls=True)
-ts2.Disk.ssl_multicert_config.AddLines([
-    'dest_ip=* ssl_cert_name=server.pem_doesnotexist ssl_key_name=server.key',
-])
+ts2.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem_doesnotexist
+    ssl_key_name: server.key
+""".split("\n"))
 
 tr4 = Test.AddTestRun()
 tr4.Processes.Default.Command = 'echo Waiting'
diff --git a/tests/gold_tests/tls/tls.test.py b/tests/gold_tests/tls/tls.test.py
index 6078937..5ac2951 100644
--- a/tests/gold_tests/tls/tls.test.py
+++ b/tests/gold_tests/tls/tls.test.py
@@ -65,7 +65,13 @@
 
 ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port))
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 ts.Disk.records_config.update(
     {
         'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir),
diff --git a/tests/gold_tests/tls/tls_0rtt_server.test.py b/tests/gold_tests/tls/tls_0rtt_server.test.py
index 081644a..49e6d5f 100644
--- a/tests/gold_tests/tls/tls_0rtt_server.test.py
+++ b/tests/gold_tests/tls/tls_0rtt_server.test.py
@@ -104,10 +104,6 @@
         'proxy.config.exec_thread.limit': 8,
         'proxy.config.ssl.server.cert.path': '{0}'.format(ts1.Variables.SSLDir),
         'proxy.config.ssl.server.private_key.path': '{0}'.format(ts1.Variables.SSLDir),
-        'proxy.config.ssl.session_cache.mode': 2,
-        'proxy.config.ssl.session_cache.size': 512000,
-        'proxy.config.ssl.session_cache.timeout': 7200,
-        'proxy.config.ssl.session_cache.num_buckets': 32768,
         'proxy.config.ssl.server.session_ticket.enable': 1,
         'proxy.config.ssl.server.max_early_data': 16384,
         'proxy.config.ssl.server.allow_early_data_params': 0,
@@ -115,7 +111,13 @@
             'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA'
     })
 
-ts1.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts1.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts1.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port))
 
@@ -133,10 +135,6 @@
         'proxy.config.exec_thread.limit': 8,
         'proxy.config.ssl.server.cert.path': '{0}'.format(ts1.Variables.SSLDir),
         'proxy.config.ssl.server.private_key.path': '{0}'.format(ts1.Variables.SSLDir),
-        'proxy.config.ssl.session_cache.mode': 2,
-        'proxy.config.ssl.session_cache.size': 512000,
-        'proxy.config.ssl.session_cache.timeout': 7200,
-        'proxy.config.ssl.session_cache.num_buckets': 32768,
         'proxy.config.ssl.server.session_ticket.enable': 1,
         'proxy.config.ssl.server.max_early_data': 0,
         'proxy.config.ssl.server.allow_early_data_params': 0,
@@ -144,7 +142,13 @@
             'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA'
     })
 
-ts2.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts2.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts2.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port))
 
diff --git a/tests/gold_tests/tls/tls_bad_alpn.test.py b/tests/gold_tests/tls/tls_bad_alpn.test.py
index 027eed1..513aba7 100644
--- a/tests/gold_tests/tls/tls_bad_alpn.test.py
+++ b/tests/gold_tests/tls/tls_bad_alpn.test.py
@@ -34,7 +34,13 @@
 ts.addSSLfile("ssl/server.key")
 
 # Make sure the TS server certs are different from the origin certs
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 # Case 1, global config policy=permissive properties=signature
 #         override for foo.com policy=enforced properties=all
diff --git a/tests/gold_tests/tls/tls_check_cert_select_plugin.test.py b/tests/gold_tests/tls/tls_check_cert_select_plugin.test.py
index b4b4d8f..6cc069e 100644
--- a/tests/gold_tests/tls/tls_check_cert_select_plugin.test.py
+++ b/tests/gold_tests/tls/tls_check_cert_select_plugin.test.py
@@ -44,12 +44,18 @@
 
 ts.Disk.remap_config.AddLine('map / https://foo.com:{1}'.format(ts.Variables.ssl_port, server.Variables.SSL_Port))
 
-ts.Disk.ssl_multicert_config.AddLines(
-    [
-        'dest_ip=127.0.0.1 ssl_cert_name=signed-foo.pem ssl_key_name=signed-foo.key',
-        'ssl_cert_name=signed2-bar.pem ssl_key_name=signed-bar.key',
-        'dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key',
-    ])
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: 127.0.0.1
+    ssl_cert_name: signed-foo.pem
+    ssl_key_name: signed-foo.key
+  - ssl_cert_name: signed2-bar.pem
+    ssl_key_name: signed-bar.key
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 Test.PrepareTestPlugin(os.path.join(Test.Variables.AtsTestPluginsDir, 'ssl_secret_load_test.so'), ts)
 
diff --git a/tests/gold_tests/tls/tls_check_cert_selection.test.py b/tests/gold_tests/tls/tls_check_cert_selection.test.py
index d5c1886..bc30061 100644
--- a/tests/gold_tests/tls/tls_check_cert_selection.test.py
+++ b/tests/gold_tests/tls/tls_check_cert_selection.test.py
@@ -41,12 +41,17 @@
 
 ts.Disk.remap_config.AddLine('map / https://foo.com:{1}'.format(ts.Variables.ssl_port, server.Variables.SSL_Port))
 
-ts.Disk.ssl_multicert_config.AddLines(
-    [
-        'dest_ip=127.0.0.1 ssl_cert_name=signed-foo.pem ssl_key_name=signed-foo.key',
-        'ssl_cert_name=signed2-bar.pem ssl_key_name=signed-bar.key',
-        'dest_ip=* ssl_cert_name=combo.pem',
-    ])
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "127.0.0.1"
+    ssl_cert_name: signed-foo.pem
+    ssl_key_name: signed-foo.key
+  - ssl_cert_name: signed2-bar.pem
+    ssl_key_name: signed-bar.key
+  - dest_ip: "*"
+    ssl_cert_name: combo.pem
+""".split("\n"))
 
 # Case 1, global config policy=permissive properties=signature
 #         override for foo.com policy=enforced properties=all
diff --git a/tests/gold_tests/tls/tls_check_cert_selection_reload.test.py b/tests/gold_tests/tls/tls_check_cert_selection_reload.test.py
index 51d092a..84d526b 100644
--- a/tests/gold_tests/tls/tls_check_cert_selection_reload.test.py
+++ b/tests/gold_tests/tls/tls_check_cert_selection_reload.test.py
@@ -39,11 +39,14 @@
 
 ts.Disk.remap_config.AddLine('map /stuff https://foo.com:{1}'.format(ts.Variables.ssl_port, server.Variables.SSL_Port))
 
-ts.Disk.ssl_multicert_config.AddLines(
-    [
-        'ssl_cert_name=signed-bar.pem ssl_key_name=signed-bar.key',
-        'dest_ip=* ssl_cert_name=combo.pem',
-    ])
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - ssl_cert_name: signed-bar.pem
+    ssl_key_name: signed-bar.key
+  - dest_ip: "*"
+    ssl_cert_name: combo.pem
+""".split("\n"))
 
 # Case 1, global config policy=permissive properties=signature
 #         override for foo.com policy=enforced properties=all
@@ -106,8 +109,7 @@
 
 tr = Test.AddTestRun("Try with signer 1 again")
 # Wait for the reload to complete
-tr.Processes.Default.StartBefore(
-    server3, ready=When.FileContains(ts.Disk.diags_log.Name, 'ssl_multicert.config finished loading', 2))
+tr.Processes.Default.StartBefore(server3, ready=When.FileContains(ts.Disk.diags_log.Name, 'ssl_multicert.yaml finished loading', 2))
 tr.StillRunningAfter = ts
 tr.StillRunningAfter = server
 tr.MakeCurlCommand(
diff --git a/tests/gold_tests/tls/tls_check_dual_cert_selection.test.py b/tests/gold_tests/tls/tls_check_dual_cert_selection.test.py
index cff5c54..5fd3c2a 100644
--- a/tests/gold_tests/tls/tls_check_dual_cert_selection.test.py
+++ b/tests/gold_tests/tls/tls_check_dual_cert_selection.test.py
@@ -48,12 +48,17 @@
 
 ts.Disk.remap_config.AddLine('map / https://foo.com:{1}'.format(ts.Variables.ssl_port, server.Variables.SSL_Port))
 
-ts.Disk.ssl_multicert_config.AddLines(
-    [
-        'ssl_cert_name=signed-foo-ec.pem,signed-foo.pem ssl_key_name=signed-foo-ec.key,signed-foo.key',
-        'ssl_cert_name=signed-san-ec.pem,signed-san.pem ssl_key_name=signed-san-ec.key,signed-san.key',
-        'dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key',
-    ])
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - ssl_cert_name: signed-foo-ec.pem,signed-foo.pem
+    ssl_key_name: signed-foo-ec.key,signed-foo.key
+  - ssl_cert_name: signed-san-ec.pem,signed-san.pem
+    ssl_key_name: signed-san-ec.key,signed-san.key
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 # Case 1, global config policy=permissive properties=signature
 #         override for foo.com policy=enforced properties=all
diff --git a/tests/gold_tests/tls/tls_check_dual_cert_selection2.test.py b/tests/gold_tests/tls/tls_check_dual_cert_selection2.test.py
index 94b01e8..b443564 100644
--- a/tests/gold_tests/tls/tls_check_dual_cert_selection2.test.py
+++ b/tests/gold_tests/tls/tls_check_dual_cert_selection2.test.py
@@ -49,12 +49,14 @@
 
 ts.Disk.remap_config.AddLine('map / https://foo.com:{1}'.format(ts.Variables.ssl_port, server.Variables.SSL_Port))
 
-ts.Disk.ssl_multicert_config.AddLines(
-    [
-        'ssl_cert_name=combined-ec.pem,combined.pem',
-        'ssl_cert_name=signed-foo-ec.pem,signed-foo.pem',
-        'dest_ip=* ssl_cert_name=signed-san-ec.pem,signed-san.pem',
-    ])
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - ssl_cert_name: combined-ec.pem,combined.pem
+  - ssl_cert_name: signed-foo-ec.pem,signed-foo.pem
+  - dest_ip: "*"
+    ssl_cert_name: signed-san-ec.pem,signed-san.pem
+""".split("\n"))
 
 # Case 1, global config policy=permissive properties=signature
 #         override for foo.com policy=enforced properties=all
diff --git a/tests/gold_tests/tls/tls_check_dual_cert_selection_plugin.test.py b/tests/gold_tests/tls/tls_check_dual_cert_selection_plugin.test.py
index 14b3b2f..fbc1dea 100644
--- a/tests/gold_tests/tls/tls_check_dual_cert_selection_plugin.test.py
+++ b/tests/gold_tests/tls/tls_check_dual_cert_selection_plugin.test.py
@@ -50,12 +50,17 @@
 
 ts.Disk.remap_config.AddLine('map / https://foo.com:{1}'.format(ts.Variables.ssl_port, server.Variables.SSL_Port))
 
-ts.Disk.ssl_multicert_config.AddLines(
-    [
-        'ssl_cert_name=signed-foo-ec.pem,signed-foo.pem ssl_key_name=signed-foo-ec.key,signed-foo.key',
-        'ssl_cert_name=signed-san-ec.pem,signed-san.pem ssl_key_name=signed-san-ec.key,signed-san.key',
-        'dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key',
-    ])
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - ssl_cert_name: signed-foo-ec.pem,signed-foo.pem
+    ssl_key_name: signed-foo-ec.key,signed-foo.key
+  - ssl_cert_name: signed-san-ec.pem,signed-san.pem
+    ssl_key_name: signed-san-ec.key,signed-san.key
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 Test.PrepareTestPlugin(os.path.join(Test.Variables.AtsTestPluginsDir, 'ssl_secret_load_test.so'), ts)
 
diff --git a/tests/gold_tests/tls/tls_client_alpn_configuration.test.py b/tests/gold_tests/tls/tls_client_alpn_configuration.test.py
index dc57b3a..1bd5cff 100644
--- a/tests/gold_tests/tls/tls_client_alpn_configuration.test.py
+++ b/tests/gold_tests/tls/tls_client_alpn_configuration.test.py
@@ -107,7 +107,13 @@
                 'proxy.config.ssl.client.alpn_protocols': records_config_alpn,
             })
 
-        ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+        ts.Disk.ssl_multicert_yaml.AddLines(
+            """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
         conf_remap_specification = ''
         if conf_remap_alpn is not None:
diff --git a/tests/gold_tests/tls/tls_client_cert.test.py b/tests/gold_tests/tls/tls_client_cert.test.py
index c848c73..605ad84 100644
--- a/tests/gold_tests/tls/tls_client_cert.test.py
+++ b/tests/gold_tests/tls/tls_client_cert.test.py
@@ -91,7 +91,13 @@
         'proxy.config.ssl.client.verify.server.policy': 'PERMISSIVE',
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLine('map /case1 https://127.0.0.1:{0}/'.format(server.Variables.SSL_Port))
 ts.Disk.remap_config.AddLine('map /case2 https://127.0.0.1:{0}/'.format(server2.Variables.SSL_Port))
diff --git a/tests/gold_tests/tls/tls_client_cert2.test.py b/tests/gold_tests/tls/tls_client_cert2.test.py
index 1e6970e..4645c04 100644
--- a/tests/gold_tests/tls/tls_client_cert2.test.py
+++ b/tests/gold_tests/tls/tls_client_cert2.test.py
@@ -86,7 +86,13 @@
         'proxy.config.ssl.client.verify.server.policy': 'PERMISSIVE',
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLine('map /case1 https://127.0.0.1:{0}/'.format(server.Variables.SSL_Port))
 ts.Disk.remap_config.AddLine('map /case2 https://127.0.0.1:{0}/'.format(server2.Variables.SSL_Port))
diff --git a/tests/gold_tests/tls/tls_client_cert2_plugin.test.py b/tests/gold_tests/tls/tls_client_cert2_plugin.test.py
index 3f7924d..c3dade3 100644
--- a/tests/gold_tests/tls/tls_client_cert2_plugin.test.py
+++ b/tests/gold_tests/tls/tls_client_cert2_plugin.test.py
@@ -95,7 +95,13 @@
         'proxy.config.url_remap.pristine_host_hdr': 1,
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLine('map /case1 https://127.0.0.1:{0}/'.format(server.Variables.SSL_Port))
 ts.Disk.remap_config.AddLine('map /case2 https://127.0.0.1:{0}/'.format(server2.Variables.SSL_Port))
diff --git a/tests/gold_tests/tls/tls_client_cert_override.test.py b/tests/gold_tests/tls/tls_client_cert_override.test.py
index 38f951d..466eaf9 100644
--- a/tests/gold_tests/tls/tls_client_cert_override.test.py
+++ b/tests/gold_tests/tls/tls_client_cert_override.test.py
@@ -88,7 +88,13 @@
         'proxy.config.ssl.client.verify.server.policy': 'PERMISSIVE',
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLine(
     'map /case1 https://127.0.0.1:{0}/ @plugin=conf_remap.so @pparam=proxy.config.ssl.client.cert.filename={1} plugin=conf_remap.so @pparam=proxy.config.ssl.client.private_key.filename={2}'
diff --git a/tests/gold_tests/tls/tls_client_cert_override_plugin.test.py b/tests/gold_tests/tls/tls_client_cert_override_plugin.test.py
index c9af9e2..3b7b8a4 100644
--- a/tests/gold_tests/tls/tls_client_cert_override_plugin.test.py
+++ b/tests/gold_tests/tls/tls_client_cert_override_plugin.test.py
@@ -107,7 +107,13 @@
         'proxy.config.diags.debug.tags': 'ssl_secret_load|http|ssl',
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLine(
     'map /case1 https://127.0.0.1:{0}/ @plugin=conf_remap.so @pparam=proxy.config.ssl.client.cert.filename={1} plugin=conf_remap.so @pparam=proxy.config.ssl.client.private_key.filename={2}'
diff --git a/tests/gold_tests/tls/tls_client_cert_plugin.test.py b/tests/gold_tests/tls/tls_client_cert_plugin.test.py
index 7b8d39d..98c9c6c 100644
--- a/tests/gold_tests/tls/tls_client_cert_plugin.test.py
+++ b/tests/gold_tests/tls/tls_client_cert_plugin.test.py
@@ -98,7 +98,13 @@
         'proxy.config.url_remap.pristine_host_hdr': 1,
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLine('map /case1 https://127.0.0.1:{0}/'.format(server.Variables.SSL_Port))
 ts.Disk.remap_config.AddLine('map /case2 https://127.0.0.1:{0}/'.format(server2.Variables.SSL_Port))
diff --git a/tests/gold_tests/tls/tls_client_verify.test.py b/tests/gold_tests/tls/tls_client_verify.test.py
index 917e488..710a235 100644
--- a/tests/gold_tests/tls/tls_client_verify.test.py
+++ b/tests/gold_tests/tls/tls_client_verify.test.py
@@ -49,7 +49,13 @@
         'proxy.config.ssl.TLSv1_3.enabled': 0
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 # Just map everything through to origin.  This test is concentrating on the user-agent side
 ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}/'.format(server.Variables.Port))
diff --git a/tests/gold_tests/tls/tls_client_verify2.test.py b/tests/gold_tests/tls/tls_client_verify2.test.py
index 657d8eb..d8b00d3 100644
--- a/tests/gold_tests/tls/tls_client_verify2.test.py
+++ b/tests/gold_tests/tls/tls_client_verify2.test.py
@@ -48,7 +48,13 @@
         'proxy.config.ssl.CA.cert.filename': '{0}/signer.pem'.format(ts.Variables.SSLDir)
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 # Just map everything through to origin.  This test is concentrating on the user-agent side
 ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}/'.format(server.Variables.Port))
diff --git a/tests/gold_tests/tls/tls_client_verify3.test.py b/tests/gold_tests/tls/tls_client_verify3.test.py
index 27fd741..acda0e1 100644
--- a/tests/gold_tests/tls/tls_client_verify3.test.py
+++ b/tests/gold_tests/tls/tls_client_verify3.test.py
@@ -50,8 +50,19 @@
         'proxy.config.ssl.TLSv1_3.enabled': 0
     })
 
-ts.Disk.ssl_multicert_config.AddLine('ssl_cert_name=bbb-signed.pem ssl_key_name=bbb-signed.key')
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - ssl_cert_name: bbb-signed.pem
+    ssl_key_name: bbb-signed.key
+""".split("\n"))
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 # Just map everything through to origin.  This test is concentrating on the user-agent side.
 ts.Disk.remap_config.AddLine(f'map / http://127.0.0.1:{server.Variables.Port}/')
diff --git a/tests/gold_tests/tls/tls_client_versions.test.py b/tests/gold_tests/tls/tls_client_versions.test.py
index 134124e..2aed6bc 100644
--- a/tests/gold_tests/tls/tls_client_versions.test.py
+++ b/tests/gold_tests/tls/tls_client_versions.test.py
@@ -42,7 +42,13 @@
 # Need no remap rules.  Everything should be processed by sni
 
 # Make sure the TS server certs are different from the origin certs
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 cipher_suite = 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:RC4-SHA:RC4-MD5:AES128-SHA:AES256-SHA:DES-CBC3-SHA!SRP:!DSS:!PSK:!aNULL:!eNULL:!SSLv2'
 if Condition.HasOpenSSLVersion("3.0.0"):
diff --git a/tests/gold_tests/tls/tls_client_versions_minmax.test.py b/tests/gold_tests/tls/tls_client_versions_minmax.test.py
index 4c0d174..e84324c 100644
--- a/tests/gold_tests/tls/tls_client_versions_minmax.test.py
+++ b/tests/gold_tests/tls/tls_client_versions_minmax.test.py
@@ -43,7 +43,13 @@
 # Need no remap rules.  Everything should be processed by sni
 
 # Make sure the TS server certs are different from the origin certs
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.records_config.update(
     {
diff --git a/tests/gold_tests/tls/tls_engine.test.py b/tests/gold_tests/tls/tls_engine.test.py
index 623f430..eadc8e8 100644
--- a/tests/gold_tests/tls/tls_engine.test.py
+++ b/tests/gold_tests/tls/tls_engine.test.py
@@ -55,7 +55,13 @@
 
 ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port))
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 ts.Disk.records_config.update(
     {
         'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir),
diff --git a/tests/gold_tests/tls/tls_forward_nonhttp.test.py b/tests/gold_tests/tls/tls_forward_nonhttp.test.py
index e2ba698..23c6846 100644
--- a/tests/gold_tests/tls/tls_forward_nonhttp.test.py
+++ b/tests/gold_tests/tls/tls_forward_nonhttp.test.py
@@ -38,7 +38,13 @@
 # Need no remap rules.  Everything should be processed by sni
 
 # Make sure the TS server certs are different from the origin certs
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 # Case 1, global config policy=permissive properties=signature
 #         override for foo.com policy=enforced properties=all
diff --git a/tests/gold_tests/tls/tls_hooks_client_verify.test.py b/tests/gold_tests/tls/tls_hooks_client_verify.test.py
index f25e80b..33c8b21 100644
--- a/tests/gold_tests/tls/tls_hooks_client_verify.test.py
+++ b/tests/gold_tests/tls/tls_hooks_client_verify.test.py
@@ -48,7 +48,13 @@
         'proxy.config.url_remap.pristine_host_hdr': 1
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLine(
     'map https://foo.com:{1}/ https://127.0.0.1:{0}'.format(server.Variables.SSL_Port, ts.Variables.ssl_port))
diff --git a/tests/gold_tests/tls/tls_hooks_verify.test.py b/tests/gold_tests/tls/tls_hooks_verify.test.py
index dd33443..8507e55 100644
--- a/tests/gold_tests/tls/tls_hooks_verify.test.py
+++ b/tests/gold_tests/tls/tls_hooks_verify.test.py
@@ -44,7 +44,13 @@
         'proxy.config.url_remap.pristine_host_hdr': 1
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLine(
     'map https://foo.com:{1}/ https://127.0.0.1:{0}'.format(server.Variables.SSL_Port, ts.Variables.ssl_port))
diff --git a/tests/gold_tests/tls/tls_keepalive.test.py b/tests/gold_tests/tls/tls_keepalive.test.py
index 3022e4f..7e393ed 100644
--- a/tests/gold_tests/tls/tls_keepalive.test.py
+++ b/tests/gold_tests/tls/tls_keepalive.test.py
@@ -44,7 +44,13 @@
         'proxy.config.log.max_secs_per_buffer': 1
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLine(
     'map https://example.com:{0} http://127.0.0.1:{1}'.format(ts.Variables.ssl_port, server.Variables.Port))
diff --git a/tests/gold_tests/tls/tls_ocsp.test.py b/tests/gold_tests/tls/tls_ocsp.test.py
index 5a77f84..8585b35 100644
--- a/tests/gold_tests/tls/tls_ocsp.test.py
+++ b/tests/gold_tests/tls/tls_ocsp.test.py
@@ -42,8 +42,20 @@
 ts.Disk.remap_config.AddLine(
     'map https://example.com:{0} http://127.0.0.1:{1}'.format(ts.Variables.ssl_port, server.Variables.Port))
 
-ts.Disk.ssl_multicert_config.AddLine(
-    'dest_ip=* ssl_cert_name=server.ocsp.pem ssl_key_name=server.ocsp.key ssl_ocsp_name=ocsp_response.der')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+
+
+ssl_multicert:
+
+
+  - dest_ip: "*"
+    ssl_cert_name: server.ocsp.pem
+    ssl_key_name: server.ocsp.key
+    ssl_ocsp_name: ocsp_response.der
+
+
+""".split("\n"))
 
 # Case 1, global config policy=permissive properties=signature
 #         override for foo.com policy=enforced properties=all
diff --git a/tests/gold_tests/tls/tls_origin_session_reuse.test.py b/tests/gold_tests/tls/tls_origin_session_reuse.test.py
index 5825ae6..6889535 100644
--- a/tests/gold_tests/tls/tls_origin_session_reuse.test.py
+++ b/tests/gold_tests/tls/tls_origin_session_reuse.test.py
@@ -53,10 +53,34 @@
 ts3.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port))
 ts4.Disk.remap_config.AddLine('map / https://127.0.0.1:{0}'.format(ts3.Variables.ssl_port))
 
-ts1.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
-ts2.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
-ts3.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
-ts4.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts1.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
+ts2.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
+ts3.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
+ts4.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts1.Disk.records_config.update(
     {
@@ -64,12 +88,6 @@
         'proxy.config.ssl.server.cert.path': '{0}'.format(ts1.Variables.SSLDir),
         'proxy.config.ssl.server.private_key.path': '{0}'.format(ts1.Variables.SSLDir),
         'proxy.config.exec_thread.autoconfig.scale': 1.0,
-        'proxy.config.ssl.session_cache.mode': 2,
-        'proxy.config.ssl.session_cache.size': 4096,
-        'proxy.config.ssl.session_cache.num_buckets': 256,
-        'proxy.config.ssl.session_cache.skip_cache_on_bucket_contention': 0,
-        'proxy.config.ssl.session_cache.timeout': 0,
-        'proxy.config.ssl.session_cache.auto_clear': 1,
         'proxy.config.ssl.server.session_ticket.enable': 1,
         'proxy.config.ssl.origin_session_cache.enabled': 1,
         'proxy.config.ssl.origin_session_cache.size': 1,
@@ -83,12 +101,6 @@
         'proxy.config.ssl.server.cert.path': '{0}'.format(ts2.Variables.SSLDir),
         'proxy.config.ssl.server.private_key.path': '{0}'.format(ts2.Variables.SSLDir),
         'proxy.config.exec_thread.autoconfig.scale': 1.0,
-        'proxy.config.ssl.session_cache.mode': 2,
-        'proxy.config.ssl.session_cache.size': 4096,
-        'proxy.config.ssl.session_cache.num_buckets': 256,
-        'proxy.config.ssl.session_cache.skip_cache_on_bucket_contention': 0,
-        'proxy.config.ssl.session_cache.timeout': 0,
-        'proxy.config.ssl.session_cache.auto_clear': 1,
         'proxy.config.ssl.server.session_ticket.enable': 1,
         'proxy.config.ssl.origin_session_cache.enabled': 1,
         'proxy.config.ssl.origin_session_cache.size': 1,
@@ -100,12 +112,6 @@
         'proxy.config.ssl.server.cert.path': '{0}'.format(ts3.Variables.SSLDir),
         'proxy.config.ssl.server.private_key.path': '{0}'.format(ts3.Variables.SSLDir),
         'proxy.config.exec_thread.autoconfig.scale': 1.0,
-        'proxy.config.ssl.session_cache.mode': 2,
-        'proxy.config.ssl.session_cache.size': 4096,
-        'proxy.config.ssl.session_cache.num_buckets': 256,
-        'proxy.config.ssl.session_cache.skip_cache_on_bucket_contention': 0,
-        'proxy.config.ssl.session_cache.timeout': 0,
-        'proxy.config.ssl.session_cache.auto_clear': 1,
         'proxy.config.ssl.server.session_ticket.enable': 1,
         'proxy.config.ssl.origin_session_cache.enabled': 1,
         'proxy.config.ssl.origin_session_cache.size': 1,
@@ -119,12 +125,6 @@
         'proxy.config.ssl.server.cert.path': '{0}'.format(ts4.Variables.SSLDir),
         'proxy.config.ssl.server.private_key.path': '{0}'.format(ts4.Variables.SSLDir),
         'proxy.config.exec_thread.autoconfig.scale': 1.0,
-        'proxy.config.ssl.session_cache.mode': 2,
-        'proxy.config.ssl.session_cache.size': 4096,
-        'proxy.config.ssl.session_cache.num_buckets': 256,
-        'proxy.config.ssl.session_cache.skip_cache_on_bucket_contention': 0,
-        'proxy.config.ssl.session_cache.timeout': 0,
-        'proxy.config.ssl.session_cache.auto_clear': 1,
         'proxy.config.ssl.server.session_ticket.enable': 1,
         'proxy.config.ssl.origin_session_cache.enabled': 0,
         'proxy.config.ssl.origin_session_cache.size': 1,
diff --git a/tests/gold_tests/tls/tls_partial_blind_tunnel.test.py b/tests/gold_tests/tls/tls_partial_blind_tunnel.test.py
index eab7b9f..2a9dee3 100644
--- a/tests/gold_tests/tls/tls_partial_blind_tunnel.test.py
+++ b/tests/gold_tests/tls/tls_partial_blind_tunnel.test.py
@@ -36,7 +36,13 @@
 # Need no remap rules. Everything should be processed by sni
 
 # Make sure the TS server certs are different from the origin certs
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=signed-foo.pem ssl_key_name=signed-foo.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: signed-foo.pem
+    ssl_key_name: signed-foo.key
+""".split("\n"))
 
 # Case 1, global config policy=permissive properties=signature
 #         override for foo.com policy=enforced properties=all
diff --git a/tests/gold_tests/tls/tls_session_key_logging.test.py b/tests/gold_tests/tls/tls_session_key_logging.test.py
index 7ace4a0..ecb269d 100644
--- a/tests/gold_tests/tls/tls_session_key_logging.test.py
+++ b/tests/gold_tests/tls/tls_session_key_logging.test.py
@@ -55,7 +55,13 @@
                 'proxy.config.diags.debug.enabled': 1,
                 'proxy.config.diags.debug.tags': 'ssl_keylog'
             })
-        self.ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+        self.ts.Disk.ssl_multicert_yaml.AddLines(
+            """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
         self.ts.Disk.remap_config.AddLine(f'map / https://127.0.0.1:{self.server.Variables.https_port}')
 
         keylog_file = os.path.join(self.ts.Variables.LOGDIR, "tls_secrets.txt")
diff --git a/tests/gold_tests/tls/tls_session_reuse.test.py b/tests/gold_tests/tls/tls_session_reuse.test.py
index 2d36848..c66e2bc 100644
--- a/tests/gold_tests/tls/tls_session_reuse.test.py
+++ b/tests/gold_tests/tls/tls_session_reuse.test.py
@@ -26,7 +26,6 @@
 # Define default ATS
 ts1 = Test.MakeATSProcess("ts1", enable_tls=True)
 ts2 = Test.MakeATSProcess("ts2", enable_tls=True)
-ts3 = Test.MakeATSProcess("ts3", enable_tls=True)
 server = Test.MakeOriginServer("server")
 
 # Add info the origin server responses
@@ -39,124 +38,123 @@
 ts1.addSSLfile("ssl/server.key")
 ts2.addSSLfile("ssl/server.pem")
 ts2.addSSLfile("ssl/server.key")
-ts3.addSSLfile("ssl/server.pem")
-ts3.addSSLfile("ssl/server.key")
 
 ts1.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port))
 ts2.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port))
-ts3.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port))
 
-ts1.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
-ts2.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
-ts3.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts1.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
+ts2.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts1.Disk.records_config.update(
     {
+        'proxy.config.diags.debug.enabled': 1,
+        'proxy.config.diags.debug.tags': 'ssl',
         'proxy.config.ssl.server.cert.path': '{0}'.format(ts1.Variables.SSLDir),
         'proxy.config.ssl.server.private_key.path': '{0}'.format(ts1.Variables.SSLDir),
         'proxy.config.ssl.server.cipher_suite':
             'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA',
         'proxy.config.exec_thread.autoconfig.scale': 1.0,
-        'proxy.config.ssl.session_cache.mode': 2,
-        'proxy.config.ssl.session_cache.size': 4096,
-        'proxy.config.ssl.session_cache.num_buckets': 256,
-        'proxy.config.ssl.session_cache.skip_cache_on_bucket_contention': 0,
-        'proxy.config.ssl.session_cache.timeout': 0,
-        'proxy.config.ssl.session_cache.auto_clear': 1,
-        'proxy.config.ssl.server.session_ticket.enable': 0,
+        'proxy.config.ssl.server.session_ticket.enable': 1,
+        'proxy.config.ssl.server.session_ticket.number': 2,
     })
 ts2.Disk.records_config.update(
     {
+        'proxy.config.diags.debug.enabled': 1,
+        'proxy.config.diags.debug.tags': 'ssl',
         'proxy.config.ssl.server.cert.path': '{0}'.format(ts2.Variables.SSLDir),
         'proxy.config.ssl.server.private_key.path': '{0}'.format(ts2.Variables.SSLDir),
         'proxy.config.ssl.server.cipher_suite':
             'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA',
         'proxy.config.exec_thread.autoconfig.scale': 1.0,
-        'proxy.config.ssl.session_cache.mode': 2,
-        'proxy.config.ssl.session_cache.size': 4096,
-        'proxy.config.ssl.session_cache.num_buckets': 256,
-        'proxy.config.ssl.session_cache.skip_cache_on_bucket_contention': 0,
-        'proxy.config.ssl.session_cache.timeout': 0,
-        'proxy.config.ssl.session_cache.auto_clear': 1,
-        'proxy.config.ssl.server.session_ticket.enable': 1,
-    })
-ts3.Disk.records_config.update(
-    {
-        'proxy.config.ssl.server.cert.path': '{0}'.format(ts3.Variables.SSLDir),
-        'proxy.config.ssl.server.private_key.path': '{0}'.format(ts3.Variables.SSLDir),
-        'proxy.config.ssl.server.cipher_suite':
-            'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA',
-        'proxy.config.exec_thread.autoconfig.scale': 1.0,
-        'proxy.config.ssl.session_cache.mode': 0,
-        'proxy.config.ssl.session_cache.size': 4096,
-        'proxy.config.ssl.session_cache.num_buckets': 256,
-        'proxy.config.ssl.session_cache.skip_cache_on_bucket_contention': 0,
-        'proxy.config.ssl.session_cache.timeout': 0,
-        'proxy.config.ssl.session_cache.auto_clear': 1,
-        'proxy.config.ssl.server.session_ticket.enable': 1,
+        'proxy.config.ssl.server.session_ticket.enable': 0,
+        'proxy.config.ssl.server.session_ticket.number': 0,
     })
 
 
-def check_session(ev, test):
+def check_session(output_path, tls_ver, reuse_count):
     retval = False
-    f = open(test.GetContent(ev), 'r')
-    err = "Session ids match"
+    f = open(output_path, 'r')
     if not f:
-        err = "Failed to open {0}".format(openssl_output)
-        return (retval, "Check that session ids match", err)
+        err = "Failed to open {0}".format(output_path)
+        return (retval, "Check session is reused", err)
 
     content = f.read()
-    match = re.findall('Session-ID: ([0-9A-F]+)', content)
-
-    if match:
-        if all(i == j for i, j in zip(match, match[1:])):
-            err = "{0} reused successfully {1} times".format(match[0], len(match) - 1)
-            retval = True
-        else:
-            err = "Session is not being reused as expected"
+    match = re.findall(f'Reused, {tls_ver}', content)
+    if len(match) == reuse_count:
+        retval = True
+        err = "Reused successfully {0} times".format(len(match))
     else:
-        err = "Didn't find session id"
-    return (retval, "Check that session ids match", err)
+        err = "Session is not being reused as expected"
+    f.close()
+    return (retval, "Check session is reused", err)
 
 
-tr = Test.AddTestRun("TLSv1.2 Session ID")
-tr.Command = \
-    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -no_ticket -sess_out {1} && ' \
-    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -no_ticket -sess_in {1} && ' \
-    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -no_ticket -sess_in {1} && ' \
-    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -no_ticket -sess_in {1} && ' \
-    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -no_ticket -sess_in {1} && ' \
-    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -no_ticket -sess_in {1}' \
-    .format(ts1.Variables.ssl_port, os.path.join(Test.RunDirectory, 'sess.dat'))
-tr.ReturnCode = 0
-tr.Processes.Default.StartBefore(server)
-tr.Processes.Default.StartBefore(ts1)
-tr.Processes.Default.Streams.All.Content = Testers.Lambda(check_session)
-tr.StillRunningAfter = server
-
-tr1 = Test.AddTestRun("TLSv1.2 Session Ticket")
+tr1 = Test.AddTestRun("TLSv1.2 Session Resumption Enabled")
 tr1.Command = \
-    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -sess_out {1} && ' \
-    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -sess_in {1} && ' \
-    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -sess_in {1} && ' \
-    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -sess_in {1} && ' \
-    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -sess_in {1} && ' \
-    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -sess_in {1}' \
-    .format(ts2.Variables.ssl_port, os.path.join(Test.RunDirectory, 'sess.dat'))
+    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_out {1} -tls1_2 && ' \
+    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in  {1} -tls1_2 && ' \
+    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in  {1} -tls1_2 && ' \
+    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in  {1} -tls1_2 && ' \
+    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in  {1} -tls1_2 && ' \
+    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in  {1} -tls1_2' \
+    .format(ts1.Variables.ssl_port, os.path.join(Test.RunDirectory, 'sess1.dat'))
 tr1.ReturnCode = 0
-tr1.Processes.Default.StartBefore(ts2)
-tr1.Processes.Default.Streams.All.Content = Testers.Lambda(check_session)
-tr1.StillRunningAfter = server
+tr1.Processes.Default.StartBefore(server)
+tr1.Processes.Default.StartBefore(ts1)
+tr1.Processes.Default.Streams.All.Content = Testers.Lambda(
+    lambda info, tester: check_session(tr1.Processes.Default.Streams.All.AbsPath, 'TLSv1.2', 5))
+tr1.StillRunningAfter += server
+tr1.StillRunningAfter += ts1
 
-tr2 = Test.AddTestRun("Disabled Session Cache")
+tr2 = Test.AddTestRun("TLSv1.3 Session Resumption Enabled")
 tr2.Command = \
-    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -no_ticket -sess_out {1} && ' \
-    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -no_ticket -sess_in {1} && ' \
-    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -no_ticket -sess_in {1} && ' \
-    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -no_ticket -sess_in {1} && ' \
-    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -no_ticket -sess_in {1} && ' \
-    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -tls1_2 -connect 127.0.0.1:{0} -no_ticket -sess_in {1}' \
-    .format(ts3.Variables.ssl_port, os.path.join(Test.RunDirectory, 'sess.dat'))
+    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_out {1} -tls1_2 && ' \
+    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in  {1} -tls1_2 && ' \
+    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in  {1} -tls1_2 && ' \
+    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in  {1} -tls1_2 && ' \
+    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in  {1} -tls1_2 && ' \
+    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in  {1} -tls1_2' \
+    .format(ts1.Variables.ssl_port, os.path.join(Test.RunDirectory, 'sess2.dat'))
 tr2.ReturnCode = 0
-tr2.Processes.Default.StartBefore(ts3)
-tr2.Processes.Default.Streams.All = Testers.ExcludesExpression('Reused', '')
+tr2.Processes.Default.Streams.All.Content = Testers.Lambda(
+    lambda info, tester: check_session(tr2.Processes.Default.Streams.All.AbsPath, 'TLSv1.2', 5))
+tr2.StillRunningAfter += server
+
+tr3 = Test.AddTestRun("TLSv1.2 Session Resumption Disabled")
+tr3.Command = \
+    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_out {1} -tls1_2 && ' \
+    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in  {1} -tls1_2 && ' \
+    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in  {1} -tls1_2 && ' \
+    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in  {1} -tls1_2 && ' \
+    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in  {1} -tls1_2 && ' \
+    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in  {1} -tls1_2' \
+    .format(ts2.Variables.ssl_port, os.path.join(Test.RunDirectory, 'sess3.dat'))
+tr3.Processes.Default.StartBefore(ts2)
+tr3.Processes.Default.Streams.All = Testers.ExcludesExpression('Reused', '')
+tr3.Processes.Default.Streams.All += Testers.ContainsExpression('TLSv1.2', '')
+tr3.StillRunningAfter += server
+tr3.StillRunningAfter += ts2
+
+tr4 = Test.AddTestRun("TLSv1.3 Session Resumption Disabled")
+tr4.Command = \
+    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_out {1} -tls1_3 && ' \
+    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in  {1} -tls1_3 && ' \
+    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in  {1} -tls1_3 && ' \
+    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in  {1} -tls1_3 && ' \
+    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in  {1} -tls1_3 && ' \
+    'echo -e "GET / HTTP/1.1\r\n" | openssl s_client -connect 127.0.0.1:{0} -sess_in  {1} -tls1_3' \
+    .format(ts2.Variables.ssl_port, os.path.join(Test.RunDirectory, 'sess4.dat'))
+tr4.Processes.Default.Streams.All = Testers.ExcludesExpression('Reused', '')
+tr4.Processes.Default.Streams.All += Testers.ContainsExpression('TLSv1.3', '')
diff --git a/tests/gold_tests/tls/tls_sni_groups.test.py b/tests/gold_tests/tls/tls_sni_groups.test.py
index 16c1cce..79e9b20 100644
--- a/tests/gold_tests/tls/tls_sni_groups.test.py
+++ b/tests/gold_tests/tls/tls_sni_groups.test.py
@@ -37,7 +37,13 @@
 # Need no remap rules.  Everything should be processed by sni
 
 # Make sure the TS server certs are different from the origin certs
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.records_config.update(
     {
diff --git a/tests/gold_tests/tls/tls_sni_host_policy.test.py b/tests/gold_tests/tls/tls_sni_host_policy.test.py
index 03a6e17..bf7386b 100644
--- a/tests/gold_tests/tls/tls_sni_host_policy.test.py
+++ b/tests/gold_tests/tls/tls_sni_host_policy.test.py
@@ -52,7 +52,13 @@
         'proxy.config.diags.debug.tags': 'ssl',
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 # Just map everything through to origin.  This test is concentrating on the user-agent side
 ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}/'.format(server.Variables.Port))
diff --git a/tests/gold_tests/tls/tls_sni_ip_allow.test.py b/tests/gold_tests/tls/tls_sni_ip_allow.test.py
index 84cb9f6..fa23342 100644
--- a/tests/gold_tests/tls/tls_sni_ip_allow.test.py
+++ b/tests/gold_tests/tls/tls_sni_ip_allow.test.py
@@ -121,7 +121,13 @@
                 '  ip_allow: 1.2.3.4',
             ])
         ts.addDefaultSSLFiles()
-        ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+        ts.Disk.ssl_multicert_yaml.AddLines(
+            """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
         ts.Disk.remap_config.AddLine(f'map / http://remapped.backend.server.com:{server.Variables.http_port}/')
         ts.Disk.records_config.update(
             {
diff --git a/tests/gold_tests/tls/tls_sni_with_port.test.py b/tests/gold_tests/tls/tls_sni_with_port.test.py
index 3f107d6..cadda0c 100644
--- a/tests/gold_tests/tls/tls_sni_with_port.test.py
+++ b/tests/gold_tests/tls/tls_sni_with_port.test.py
@@ -134,7 +134,13 @@
                 f"  tunnel_route: localhost:{server_two.Variables.https_port}",
             ])
 
-        ts.Disk.ssl_multicert_config.AddLine(f"dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key")
+        ts.Disk.ssl_multicert_yaml.AddLines(
+            """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
         self._ts = ts
 
diff --git a/tests/gold_tests/tls/tls_sni_yaml_reload.test.py b/tests/gold_tests/tls/tls_sni_yaml_reload.test.py
index 8e2e0b2..133ecaf 100644
--- a/tests/gold_tests/tls/tls_sni_yaml_reload.test.py
+++ b/tests/gold_tests/tls/tls_sni_yaml_reload.test.py
@@ -45,9 +45,21 @@
 
 ts.Disk.remap_config.AddLine(f'map / http://127.0.0.1:{server.Variables.Port}')
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.sni_yaml.AddLines(
     f"""
diff --git a/tests/gold_tests/tls/tls_ticket.test.py b/tests/gold_tests/tls/tls_ticket.test.py
index d49421b..d7e2df1 100644
--- a/tests/gold_tests/tls/tls_ticket.test.py
+++ b/tests/gold_tests/tls/tls_ticket.test.py
@@ -41,8 +41,20 @@
 ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port))
 ts2.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variables.Port))
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
-ts2.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
+ts2.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.records_config.update(
     {
diff --git a/tests/gold_tests/tls/tls_tunnel.test.py b/tests/gold_tests/tls/tls_tunnel.test.py
index e2de652..ab02018 100644
--- a/tests/gold_tests/tls/tls_tunnel.test.py
+++ b/tests/gold_tests/tls/tls_tunnel.test.py
@@ -74,7 +74,13 @@
 # Need no remap rules.  Everything should be processed by sni
 
 # Make sure the TS server certs are different from the origin certs
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=signed-foo.pem ssl_key_name=signed-foo.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: signed-foo.pem
+    ssl_key_name: signed-foo.key
+""".split("\n"))
 
 # Case 1, global config policy=permissive properties=signature
 #         override for foo.com policy=enforced properties=all
diff --git a/tests/gold_tests/tls/tls_tunnel_forward.test.py b/tests/gold_tests/tls/tls_tunnel_forward.test.py
index 472797d..dc9e4a4 100644
--- a/tests/gold_tests/tls/tls_tunnel_forward.test.py
+++ b/tests/gold_tests/tls/tls_tunnel_forward.test.py
@@ -54,7 +54,13 @@
 # Need no remap rules.  Everything should be processed by sni
 
 # Make sure the TS server certs are different from the origin certs
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=signed-foo.pem ssl_key_name=signed-foo.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: signed-foo.pem
+    ssl_key_name: signed-foo.key
+""".split("\n"))
 
 # Case 1, global config policy=permissive properties=signature
 #         override for foo.com policy=enforced properties=all
diff --git a/tests/gold_tests/tls/tls_verify.test.py b/tests/gold_tests/tls/tls_verify.test.py
index 64b3240..890bae1 100644
--- a/tests/gold_tests/tls/tls_verify.test.py
+++ b/tests/gold_tests/tls/tls_verify.test.py
@@ -76,7 +76,13 @@
 ts.Disk.remap_config.AddLine('map https://foo.wild.com/ https://127.0.0.1:{0}'.format(server_wild.Variables.SSL_Port))
 ts.Disk.remap_config.AddLine('map https://foo_bar.wild.com/ https://127.0.0.1:{0}'.format(server_wild.Variables.SSL_Port))
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 # Case 1, global config policy=permissive properties=signature
 #         override for foo.com policy=enforced properties=all
diff --git a/tests/gold_tests/tls/tls_verify2.test.py b/tests/gold_tests/tls/tls_verify2.test.py
index db54aeb..72f261e 100644
--- a/tests/gold_tests/tls/tls_verify2.test.py
+++ b/tests/gold_tests/tls/tls_verify2.test.py
@@ -64,7 +64,13 @@
 ts.Disk.remap_config.AddLine('map https://bad_bar.com/ https://127.0.0.1:{0}'.format(server_bar.Variables.SSL_Port))
 ts.Disk.remap_config.AddLine('map / https://127.0.0.1:{0}'.format(server.Variables.SSL_Port))
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 # Case 1, global config policy=permissive properties=signature
 #         override for foo.com policy=enforced properties=all
diff --git a/tests/gold_tests/tls/tls_verify3.test.py b/tests/gold_tests/tls/tls_verify3.test.py
index 08920ff..0603499 100644
--- a/tests/gold_tests/tls/tls_verify3.test.py
+++ b/tests/gold_tests/tls/tls_verify3.test.py
@@ -68,7 +68,13 @@
     'map https://bob.bar.com:{1}/ https://127.0.0.1:{0}'.format(server_bar.Variables.SSL_Port, ts.Variables.ssl_port))
 ts.Disk.remap_config.AddLine('map / https://127.0.0.1:{0}'.format(server.Variables.SSL_Port))
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 # Case 1, global config policy=permissive properties=signature
 #         override for foo.com policy=enforced properties=all
diff --git a/tests/gold_tests/tls/tls_verify4.test.py b/tests/gold_tests/tls/tls_verify4.test.py
index bab0909..437d582 100644
--- a/tests/gold_tests/tls/tls_verify4.test.py
+++ b/tests/gold_tests/tls/tls_verify4.test.py
@@ -65,7 +65,13 @@
 ts.Disk.remap_config.AddLine('map https://bad_bar.com/ https://127.0.0.1:{0}'.format(server_bar.Variables.SSL_Port))
 ts.Disk.remap_config.AddLine('map / https://127.0.0.1:{0}'.format(server.Variables.SSL_Port))
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 # Case 1, global config policy=permissive properties=signature
 #         override for foo.com policy=enforced properties=all
diff --git a/tests/gold_tests/tls/tls_verify_base.test.py b/tests/gold_tests/tls/tls_verify_base.test.py
index 7526cfa..7617bee 100644
--- a/tests/gold_tests/tls/tls_verify_base.test.py
+++ b/tests/gold_tests/tls/tls_verify_base.test.py
@@ -64,7 +64,13 @@
 ts.Disk.remap_config.AddLine('map https://bar.com/ https://127.0.0.1:{0}'.format(server_bar.Variables.SSL_Port))
 ts.Disk.remap_config.AddLine('map https://bad_bar.com/ https://127.0.0.1:{0}'.format(server_bar.Variables.SSL_Port))
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 # Case 1, global config policy=permissive properties=signature
 #         override for foo.com policy=enforced properties=all
diff --git a/tests/gold_tests/tls/tls_verify_ca_override.test.py b/tests/gold_tests/tls/tls_verify_ca_override.test.py
index 2de7f42..610bc01 100644
--- a/tests/gold_tests/tls/tls_verify_ca_override.test.py
+++ b/tests/gold_tests/tls/tls_verify_ca_override.test.py
@@ -71,7 +71,13 @@
     'map /badcase2 https://127.0.0.1:{0}/ @plugin=conf_remap.so @pparam=proxy.config.ssl.client.CA.cert.filename={1}/{2}'.format(
         server2.Variables.SSL_Port, ts.Variables.SSLDir, "signer.pem"))
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 # Case 1, global config policy=permissive properties=signature
 #         override for foo.com policy=enforced properties=all
diff --git a/tests/gold_tests/tls/tls_verify_not_pristine.test.py b/tests/gold_tests/tls/tls_verify_not_pristine.test.py
index 2429f25..2d7723d 100644
--- a/tests/gold_tests/tls/tls_verify_not_pristine.test.py
+++ b/tests/gold_tests/tls/tls_verify_not_pristine.test.py
@@ -53,7 +53,13 @@
 ts.Disk.remap_config.AddLine(
     'map https://foo.com:{0}/ https://bar.com:{1}'.format(ts.Variables.ssl_port, server_foo.Variables.SSL_Port))
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 # Case 1, global config policy=permissive properties=signature
 #         override for foo.com policy=enforced properties=all
diff --git a/tests/gold_tests/tls/tls_verify_override.test.py b/tests/gold_tests/tls/tls_verify_override.test.py
index 747a4d8..5fc323d 100644
--- a/tests/gold_tests/tls/tls_verify_override.test.py
+++ b/tests/gold_tests/tls/tls_verify_override.test.py
@@ -101,7 +101,13 @@
     'map /snipolicybarhost  https://bar.com:{0} @plugin=conf_remap.so @pparam=proxy.config.ssl.client.verify.server.properties=NAME @plugin=conf_remap.so @pparam=proxy.config.ssl.client.verify.server.policy=ENFORCED @plugin=conf_remap.so @pparam=proxy.config.ssl.client.sni_policy=host'
     .format(server_bar.Variables.SSL_Port))
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 # Case 1, global config policy=permissive properties=signature
 #         override for foo.com policy=enforced properties=all
diff --git a/tests/gold_tests/tls/tls_verify_override_base.test.py b/tests/gold_tests/tls/tls_verify_override_base.test.py
index be8179d..ab07dba 100644
--- a/tests/gold_tests/tls/tls_verify_override_base.test.py
+++ b/tests/gold_tests/tls/tls_verify_override_base.test.py
@@ -100,7 +100,13 @@
     'map /snipolicybarservername  https://bar.com:{0} @plugin=conf_remap.so @pparam=proxy.config.ssl.client.verify.server.properties=NAME @plugin=conf_remap.so @pparam=proxy.config.ssl.client.verify.server.policy=ENFORCED @plugin=conf_remap.so @pparam=proxy.config.ssl.client.sni_policy=server_name'
     .format(server_bar.Variables.SSL_Port))
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 # Case 1, global config policy=permissive properties=signature
 #         override for foo.com policy=enforced properties=all
diff --git a/tests/gold_tests/tls/tls_verify_override_sni.test.py b/tests/gold_tests/tls/tls_verify_override_sni.test.py
index 97391eb..f671d44 100644
--- a/tests/gold_tests/tls/tls_verify_override_sni.test.py
+++ b/tests/gold_tests/tls/tls_verify_override_sni.test.py
@@ -83,7 +83,13 @@
     'map http://foo.com/overrideproperties https://bar.com:{0} @plugin=conf_remap.so @pparam=proxy.config.ssl.client.verify.server.properties=SIGNATURE'
     .format(server_foo.Variables.SSL_Port))
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 # global config policy=permissive properties=all
 ts.Disk.records_config.update(
diff --git a/tests/gold_tests/tls_hooks/tls_hooks.test.py b/tests/gold_tests/tls_hooks/tls_hooks.test.py
index ffb2050..db73db2 100644
--- a/tests/gold_tests/tls_hooks/tls_hooks.test.py
+++ b/tests/gold_tests/tls_hooks/tls_hooks.test.py
@@ -43,7 +43,13 @@
         'proxy.config.ssl.TLSv1_3.enabled': 0,
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLine(
     'map https://example.com:{0} http://127.0.0.1:{1}'.format(ts.Variables.ssl_port, server.Variables.Port))
diff --git a/tests/gold_tests/tls_hooks/tls_hooks10.test.py b/tests/gold_tests/tls_hooks/tls_hooks10.test.py
index fb52206..0c32f87 100644
--- a/tests/gold_tests/tls_hooks/tls_hooks10.test.py
+++ b/tests/gold_tests/tls_hooks/tls_hooks10.test.py
@@ -41,7 +41,13 @@
         'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir),
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLine(
     'map https://example.com:{0} http://127.0.0.1:{1}'.format(ts.Variables.ssl_port, server.Variables.Port))
diff --git a/tests/gold_tests/tls_hooks/tls_hooks11.test.py b/tests/gold_tests/tls_hooks/tls_hooks11.test.py
index f92b7dc..2c5a6b7 100644
--- a/tests/gold_tests/tls_hooks/tls_hooks11.test.py
+++ b/tests/gold_tests/tls_hooks/tls_hooks11.test.py
@@ -42,7 +42,13 @@
         'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir),
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLine(
     'map https://example.com:{0} http://127.0.0.1:{1}'.format(ts.Variables.ssl_port, server.Variables.Port))
diff --git a/tests/gold_tests/tls_hooks/tls_hooks12.test.py b/tests/gold_tests/tls_hooks/tls_hooks12.test.py
index 78f3696..aed35b5 100644
--- a/tests/gold_tests/tls_hooks/tls_hooks12.test.py
+++ b/tests/gold_tests/tls_hooks/tls_hooks12.test.py
@@ -41,7 +41,13 @@
         'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir),
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLine(
     'map https://example.com:{0} http://127.0.0.1:{1}'.format(ts.Variables.ssl_port, server.Variables.Port))
diff --git a/tests/gold_tests/tls_hooks/tls_hooks13.test.py b/tests/gold_tests/tls_hooks/tls_hooks13.test.py
index d9820af..515fd58 100644
--- a/tests/gold_tests/tls_hooks/tls_hooks13.test.py
+++ b/tests/gold_tests/tls_hooks/tls_hooks13.test.py
@@ -41,7 +41,13 @@
         'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir),
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLine(
     'map https://example.com:{0} https://127.0.0.1:{1}'.format(ts.Variables.ssl_port, server.Variables.SSL_Port))
diff --git a/tests/gold_tests/tls_hooks/tls_hooks14.test.py b/tests/gold_tests/tls_hooks/tls_hooks14.test.py
index 8364fac..4128b58 100644
--- a/tests/gold_tests/tls_hooks/tls_hooks14.test.py
+++ b/tests/gold_tests/tls_hooks/tls_hooks14.test.py
@@ -42,7 +42,13 @@
         'proxy.config.ssl.client.verify.server.policy': 'PERMISSIVE',
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLine(
     'map https://example.com:{0} https://127.0.0.1:{1}'.format(ts.Variables.ssl_port, server.Variables.SSL_Port))
diff --git a/tests/gold_tests/tls_hooks/tls_hooks15.test.py b/tests/gold_tests/tls_hooks/tls_hooks15.test.py
index fd2aa27..e43a26f 100644
--- a/tests/gold_tests/tls_hooks/tls_hooks15.test.py
+++ b/tests/gold_tests/tls_hooks/tls_hooks15.test.py
@@ -41,7 +41,13 @@
         'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir),
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLine(
     'map https://example.com:{0} https://127.0.0.1:{1}'.format(ts.Variables.ssl_port, server.Variables.SSL_Port))
diff --git a/tests/gold_tests/tls_hooks/tls_hooks16.test.py b/tests/gold_tests/tls_hooks/tls_hooks16.test.py
index 7804292..9a8a083 100644
--- a/tests/gold_tests/tls_hooks/tls_hooks16.test.py
+++ b/tests/gold_tests/tls_hooks/tls_hooks16.test.py
@@ -44,7 +44,13 @@
         'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir),
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLine(
     'map https://example.com:{1} http://127.0.0.1:{0}'.format(server.Variables.Port, ts.Variables.ssl_port))
diff --git a/tests/gold_tests/tls_hooks/tls_hooks17.test.py b/tests/gold_tests/tls_hooks/tls_hooks17.test.py
index 34a48e9..0b54d7a 100644
--- a/tests/gold_tests/tls_hooks/tls_hooks17.test.py
+++ b/tests/gold_tests/tls_hooks/tls_hooks17.test.py
@@ -44,7 +44,13 @@
         'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir),
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLine(
     'map https://example.com:{1} http://127.0.0.1:{0}'.format(server.Variables.Port, ts.Variables.ssl_port))
diff --git a/tests/gold_tests/tls_hooks/tls_hooks18.test.py b/tests/gold_tests/tls_hooks/tls_hooks18.test.py
index 64e7b3b..c7e03fa 100644
--- a/tests/gold_tests/tls_hooks/tls_hooks18.test.py
+++ b/tests/gold_tests/tls_hooks/tls_hooks18.test.py
@@ -44,7 +44,13 @@
         'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir),
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLine(
     'map https://example.com:{1} http://127.0.0.1:{0}'.format(server.Variables.Port, ts.Variables.ssl_port))
diff --git a/tests/gold_tests/tls_hooks/tls_hooks2.test.py b/tests/gold_tests/tls_hooks/tls_hooks2.test.py
index 66afffa..0323002 100644
--- a/tests/gold_tests/tls_hooks/tls_hooks2.test.py
+++ b/tests/gold_tests/tls_hooks/tls_hooks2.test.py
@@ -42,7 +42,13 @@
         'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir),
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLine(
     'map https://example.com:{0} http://127.0.0.1:{1}'.format(ts.Variables.ssl_port, server.Variables.Port))
diff --git a/tests/gold_tests/tls_hooks/tls_hooks3.test.py b/tests/gold_tests/tls_hooks/tls_hooks3.test.py
index 006eecb..23aeaa9 100644
--- a/tests/gold_tests/tls_hooks/tls_hooks3.test.py
+++ b/tests/gold_tests/tls_hooks/tls_hooks3.test.py
@@ -42,7 +42,13 @@
         'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir),
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLine(
     'map https://example.com:{0} http://127.0.0.1:{1}'.format(ts.Variables.ssl_port, server.Variables.Port))
diff --git a/tests/gold_tests/tls_hooks/tls_hooks4.test.py b/tests/gold_tests/tls_hooks/tls_hooks4.test.py
index f7e0742..7068c83 100644
--- a/tests/gold_tests/tls_hooks/tls_hooks4.test.py
+++ b/tests/gold_tests/tls_hooks/tls_hooks4.test.py
@@ -42,7 +42,13 @@
         'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir),
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLine(
     'map https://example.com:{0} http://127.0.0.1:{1}'.format(ts.Variables.ssl_port, server.Variables.Port))
diff --git a/tests/gold_tests/tls_hooks/tls_hooks6.test.py b/tests/gold_tests/tls_hooks/tls_hooks6.test.py
index 8e943fd..1b2635f 100644
--- a/tests/gold_tests/tls_hooks/tls_hooks6.test.py
+++ b/tests/gold_tests/tls_hooks/tls_hooks6.test.py
@@ -42,7 +42,13 @@
         'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir),
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLine(
     'map https://example.com:{0} http://127.0.0.1:{1}'.format(ts.Variables.ssl_port, server.Variables.Port))
diff --git a/tests/gold_tests/tls_hooks/tls_hooks7.test.py b/tests/gold_tests/tls_hooks/tls_hooks7.test.py
index 29a4490..e664b71 100644
--- a/tests/gold_tests/tls_hooks/tls_hooks7.test.py
+++ b/tests/gold_tests/tls_hooks/tls_hooks7.test.py
@@ -42,7 +42,13 @@
         'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir),
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLine(
     'map https://example.com:{0} http://127.0.0.1:{1}'.format(ts.Variables.ssl_port, server.Variables.Port))
diff --git a/tests/gold_tests/tls_hooks/tls_hooks8.test.py b/tests/gold_tests/tls_hooks/tls_hooks8.test.py
index 721ce36..be08cc1 100644
--- a/tests/gold_tests/tls_hooks/tls_hooks8.test.py
+++ b/tests/gold_tests/tls_hooks/tls_hooks8.test.py
@@ -42,7 +42,13 @@
         'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir),
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLine(
     'map https://example.com:{0} http://127.0.0.1:{1}'.format(ts.Variables.ssl_port, server.Variables.Port))
diff --git a/tests/gold_tests/tls_hooks/tls_hooks9.test.py b/tests/gold_tests/tls_hooks/tls_hooks9.test.py
index cf77135..343c29f 100644
--- a/tests/gold_tests/tls_hooks/tls_hooks9.test.py
+++ b/tests/gold_tests/tls_hooks/tls_hooks9.test.py
@@ -42,7 +42,13 @@
         'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir),
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLine(
     'map https://example.com:{0} http://127.0.0.1:{1}'.format(ts.Variables.ssl_port, server.Variables.Port))
diff --git a/tests/gold_tests/traffic_ctl/convert_ssl_multicert/convert_ssl_multicert.test.py b/tests/gold_tests/traffic_ctl/convert_ssl_multicert/convert_ssl_multicert.test.py
new file mode 100644
index 0000000..c0ffbcf
--- /dev/null
+++ b/tests/gold_tests/traffic_ctl/convert_ssl_multicert/convert_ssl_multicert.test.py
@@ -0,0 +1,60 @@
+'''
+Test the traffic_ctl config convert ssl_multicert command.
+'''
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+Test.Summary = 'Test traffic_ctl config convert ssl_multicert command.'
+
+# Create an ATS process to get the environment with PATH set correctly.
+ts = Test.MakeATSProcess("ts", enable_cache=False)
+
+# Test 1: Basic config conversion.
+tr = Test.AddTestRun("Test basic ssl_multicert.config conversion")
+tr.Setup.Copy('legacy_config/basic.config')
+tr.Processes.Default.Command = 'traffic_ctl config convert ssl_multicert basic.config -'
+tr.Processes.Default.Streams.stdout = "gold/basic.yaml"
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.Env = ts.Env
+tr.Processes.Default.StartBefore(ts)
+tr.StillRunningAfter = ts
+
+# Test 2: Full config with all options.
+tr = Test.AddTestRun("Test full ssl_multicert.config conversion with all options")
+tr.Setup.Copy('legacy_config/full.config')
+tr.Processes.Default.Command = 'traffic_ctl config convert ssl_multicert full.config -'
+tr.Processes.Default.Streams.stdout = "gold/full.yaml"
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.Env = ts.Env
+tr.StillRunningAfter = ts
+
+# Test 3: Config with quoted values.
+tr = Test.AddTestRun("Test ssl_multicert.config with quoted values")
+tr.Setup.Copy('legacy_config/quoted.config')
+tr.Processes.Default.Command = 'traffic_ctl config convert ssl_multicert quoted.config -'
+tr.Processes.Default.Streams.stdout = "gold/quoted.yaml"
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.Env = ts.Env
+tr.StillRunningAfter = ts
+
+# Test 4: Output to file.
+tr = Test.AddTestRun("Test output to file")
+tr.Setup.Copy('legacy_config/basic.config')
+tr.Processes.Default.Command = 'traffic_ctl config convert ssl_multicert basic.config generated.yaml > /dev/null && cat generated.yaml'
+tr.Processes.Default.Streams.stdout = "gold/basic.yaml"
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.Env = ts.Env
+tr.StillRunningAfter = ts
diff --git a/tests/gold_tests/traffic_ctl/convert_ssl_multicert/gold/basic.yaml b/tests/gold_tests/traffic_ctl/convert_ssl_multicert/gold/basic.yaml
new file mode 100644
index 0000000..8cd4076
--- /dev/null
+++ b/tests/gold_tests/traffic_ctl/convert_ssl_multicert/gold/basic.yaml
@@ -0,0 +1,6 @@
+ssl_multicert:
+  - ssl_cert_name: server.pem
+    dest_ip: "*"
+    ssl_key_name: server.key
+  - ssl_cert_name: example.pem
+    dest_ip: 192.168.1.1
diff --git a/tests/gold_tests/traffic_ctl/convert_ssl_multicert/gold/full.yaml b/tests/gold_tests/traffic_ctl/convert_ssl_multicert/gold/full.yaml
new file mode 100644
index 0000000..917ad62
--- /dev/null
+++ b/tests/gold_tests/traffic_ctl/convert_ssl_multicert/gold/full.yaml
@@ -0,0 +1,20 @@
+ssl_multicert:
+  - ssl_cert_name: server.pem
+    dest_ip: "*"
+    ssl_key_name: server.key
+    ssl_ticket_enabled: 1
+    ssl_ticket_number: 3
+  - ssl_cert_name: ecdsa.pem,rsa.pem
+    dest_ip: 10.0.0.1:443
+    ssl_key_name: ecdsa.key,rsa.key
+    ssl_ca_name: ca.pem
+    ssl_ocsp_name: ocsp.der
+  - ssl_cert_name: encrypted.pem
+    dest_ip: "*"
+    ssl_key_dialog: exec:/usr/bin/getpass foo
+  - dest_ip: 192.168.1.1
+    action: tunnel
+  - ssl_cert_name: wildcard.pem
+    dest_ip: "*"
+    dest_fqdn: "*.example.com"
+    ssl_ticket_enabled: 0
diff --git a/tests/gold_tests/traffic_ctl/convert_ssl_multicert/gold/quoted.yaml b/tests/gold_tests/traffic_ctl/convert_ssl_multicert/gold/quoted.yaml
new file mode 100644
index 0000000..0567cfc
--- /dev/null
+++ b/tests/gold_tests/traffic_ctl/convert_ssl_multicert/gold/quoted.yaml
@@ -0,0 +1,6 @@
+ssl_multicert:
+  - ssl_cert_name: server.pem
+    dest_ip: "*"
+    ssl_key_dialog: exec:/usr/bin/getpass arg1 'arg 2'
+  - ssl_cert_name: another.pem
+    dest_ip: "[::1]:8443"
diff --git a/tests/gold_tests/traffic_ctl/convert_ssl_multicert/legacy_config/basic.config b/tests/gold_tests/traffic_ctl/convert_ssl_multicert/legacy_config/basic.config
new file mode 100644
index 0000000..fb686c1
--- /dev/null
+++ b/tests/gold_tests/traffic_ctl/convert_ssl_multicert/legacy_config/basic.config
@@ -0,0 +1,3 @@
+# Basic ssl_multicert.config example
+ssl_cert_name=server.pem ssl_key_name=server.key dest_ip=*
+ssl_cert_name=example.pem dest_ip=192.168.1.1
diff --git a/tests/gold_tests/traffic_ctl/convert_ssl_multicert/legacy_config/full.config b/tests/gold_tests/traffic_ctl/convert_ssl_multicert/legacy_config/full.config
new file mode 100644
index 0000000..b7cd4f5
--- /dev/null
+++ b/tests/gold_tests/traffic_ctl/convert_ssl_multicert/legacy_config/full.config
@@ -0,0 +1,7 @@
+# Full ssl_multicert.config with all options
+# This exercises all supported configuration keys
+ssl_cert_name=server.pem ssl_key_name=server.key dest_ip=* ssl_ticket_enabled=1 ssl_ticket_number=3
+ssl_cert_name=ecdsa.pem,rsa.pem ssl_key_name=ecdsa.key,rsa.key dest_ip=10.0.0.1:443 ssl_ca_name=ca.pem ssl_ocsp_name=ocsp.der
+ssl_cert_name=encrypted.pem ssl_key_dialog="exec:/usr/bin/getpass foo"
+dest_ip=192.168.1.1 action=tunnel
+ssl_cert_name=wildcard.pem dest_fqdn=*.example.com ssl_ticket_enabled=0
diff --git a/tests/gold_tests/traffic_ctl/convert_ssl_multicert/legacy_config/quoted.config b/tests/gold_tests/traffic_ctl/convert_ssl_multicert/legacy_config/quoted.config
new file mode 100644
index 0000000..af1bb09
--- /dev/null
+++ b/tests/gold_tests/traffic_ctl/convert_ssl_multicert/legacy_config/quoted.config
@@ -0,0 +1,3 @@
+# Config with quoted values
+ssl_cert_name=server.pem ssl_key_dialog="exec:/usr/bin/getpass arg1 'arg 2'"
+ssl_cert_name=another.pem dest_ip="[::1]:8443"
diff --git a/tests/gold_tests/traffic_ctl/show_ssl_multicert/gold/show_json.gold b/tests/gold_tests/traffic_ctl/show_ssl_multicert/gold/show_json.gold
new file mode 100644
index 0000000..5fff803
--- /dev/null
+++ b/tests/gold_tests/traffic_ctl/show_ssl_multicert/gold/show_json.gold
@@ -0,0 +1 @@
+{"ssl_multicert": [{"ssl_cert_name": "server.pem", "dest_ip": "*", "ssl_key_name": "server.key"}]}
diff --git a/tests/gold_tests/traffic_ctl/show_ssl_multicert/gold/show_yaml.gold b/tests/gold_tests/traffic_ctl/show_ssl_multicert/gold/show_yaml.gold
new file mode 100644
index 0000000..5c55846
--- /dev/null
+++ b/tests/gold_tests/traffic_ctl/show_ssl_multicert/gold/show_yaml.gold
@@ -0,0 +1,4 @@
+ssl_multicert:
+  - ssl_cert_name: server.pem
+    dest_ip: "*"
+    ssl_key_name: server.key
diff --git a/tests/gold_tests/traffic_ctl/show_ssl_multicert/show_ssl_multicert.test.py b/tests/gold_tests/traffic_ctl/show_ssl_multicert/show_ssl_multicert.test.py
new file mode 100644
index 0000000..984df9b
--- /dev/null
+++ b/tests/gold_tests/traffic_ctl/show_ssl_multicert/show_ssl_multicert.test.py
@@ -0,0 +1,91 @@
+'''
+Test the traffic_ctl config ssl-multicert show command.
+'''
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+Test.Summary = 'Test traffic_ctl config ssl-multicert show command.'
+
+
+class ShowSSLMulticert:
+
+    def __init__(self):
+        self.setup_ts()
+        self.setup_show_default()
+        self.setup_show_json()
+        self.setup_show_yaml()
+
+    def setup_ts(self):
+        self._ts = Test.MakeATSProcess("ts", enable_cache=False, enable_tls=True)
+        self._ts.addDefaultSSLFiles()
+        self._ts.Disk.ssl_multicert_yaml.AddLines(
+            """
+ssl_multicert:
+  - ssl_cert_name: server.pem
+    dest_ip: "*"
+    ssl_key_name: server.key
+""".split("\n"))
+        self._ts.Disk.records_config.update(
+            {
+                'proxy.config.ssl.server.cert.path': f'{self._ts.Variables.SSLDir}',
+                'proxy.config.ssl.server.private_key.path': f'{self._ts.Variables.SSLDir}',
+            })
+
+    def setup_show_default(self):
+        tr = Test.AddTestRun("Test ssl-multicert show (default YAML format)")
+        tr.Processes.Default.Command = 'traffic_ctl config ssl-multicert show'
+        tr.Processes.Default.Streams.stdout = "gold/show_yaml.gold"
+        tr.Processes.Default.ReturnCode = 0
+        tr.Processes.Default.Env = self._ts.Env
+        tr.Processes.Default.StartBefore(self._ts)
+        tr.StillRunningAfter = self._ts
+
+    def setup_show_json(self):
+        # Test with explicit --json flag.
+        tr = Test.AddTestRun("Test ssl-multicert show --json")
+        tr.Processes.Default.Command = 'traffic_ctl config ssl-multicert show --json'
+        tr.Processes.Default.Streams.stdout = "gold/show_json.gold"
+        tr.Processes.Default.ReturnCode = 0
+        tr.Processes.Default.Env = self._ts.Env
+        tr.StillRunningAfter = self._ts
+
+        # Test with short -j flag.
+        tr = Test.AddTestRun("Test ssl-multicert show -j")
+        tr.Processes.Default.Command = 'traffic_ctl config ssl-multicert show -j'
+        tr.Processes.Default.Streams.stdout = "gold/show_json.gold"
+        tr.Processes.Default.ReturnCode = 0
+        tr.Processes.Default.Env = self._ts.Env
+        tr.StillRunningAfter = self._ts
+
+    def setup_show_yaml(self):
+        # Test with --yaml flag.
+        tr = Test.AddTestRun("Test ssl-multicert show --yaml")
+        tr.Processes.Default.Command = 'traffic_ctl config ssl-multicert show --yaml'
+        tr.Processes.Default.Streams.stdout = "gold/show_yaml.gold"
+        tr.Processes.Default.ReturnCode = 0
+        tr.Processes.Default.Env = self._ts.Env
+        tr.StillRunningAfter = self._ts
+
+        # Test with short -y flag.
+        tr = Test.AddTestRun("Test ssl-multicert show -y")
+        tr.Processes.Default.Command = 'traffic_ctl config ssl-multicert show -y'
+        tr.Processes.Default.Streams.stdout = "gold/show_yaml.gold"
+        tr.Processes.Default.ReturnCode = 0
+        tr.Processes.Default.Env = self._ts.Env
+        tr.StillRunningAfter = self._ts
+
+
+ShowSSLMulticert()
diff --git a/tests/gold_tests/tunnel/tunnel_transform.test.py b/tests/gold_tests/tunnel/tunnel_transform.test.py
index dabb069..ed169c4 100644
--- a/tests/gold_tests/tunnel/tunnel_transform.test.py
+++ b/tests/gold_tests/tunnel/tunnel_transform.test.py
@@ -58,7 +58,13 @@
         'proxy.config.http.connect_ports': '{0}'.format(server.Variables.SSL_Port)
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.sni_yaml.AddLines([
     'sni:',
diff --git a/tests/gold_tests/tunnel/txn_type.test.py b/tests/gold_tests/tunnel/txn_type.test.py
index 8c79e0a..a52933f 100644
--- a/tests/gold_tests/tunnel/txn_type.test.py
+++ b/tests/gold_tests/tunnel/txn_type.test.py
@@ -63,7 +63,13 @@
         'proxy.config.http.connect_ports': '{0}'.format(server.Variables.SSL_Port)
     })
 
-ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key')
+ts.Disk.ssl_multicert_yaml.AddLines(
+    """
+ssl_multicert:
+  - dest_ip: "*"
+    ssl_cert_name: server.pem
+    ssl_key_name: server.key
+""".split("\n"))
 
 ts.Disk.remap_config.AddLine(
     'map https://http-test:{0}/ https://127.0.0.1:{1}/'.format(ts.Variables.ssl_port, server.Variables.SSL_Port))