</listitem>
      </varlistentry>
 
-     <varlistentry id="guc-ssl-ecdh-curve" xreflabel="ssl_ecdh_curve">
-      <term><varname>ssl_ecdh_curve</varname> (<type>string</type>)
+     <varlistentry id="guc-ssl-groups" xreflabel="ssl_groups">
+      <term><varname>ssl_groups</varname> (<type>string</type>)
       <indexterm>
-       <primary><varname>ssl_ecdh_curve</varname> configuration parameter</primary>
+       <primary><varname>ssl_groups</varname> configuration parameter</primary>
       </indexterm>
       </term>
       <listitem>
        <para>
         Specifies the name of the curve to use in <acronym>ECDH</acronym> key
         exchange.  It needs to be supported by all clients that connect.
+        Multiple curves can be specified by using a colon-separated list.
         It does not need to be the same curve used by the server's Elliptic
-        Curve key.
-        This parameter can only be set in the <filename>postgresql.conf</filename>
-        file or on the server command line.
+        Curve key.  This parameter can only be set in the
+        <filename>postgresql.conf</filename> file or on the server command line.
         The default is <literal>prime256v1</literal>.
        </para>
 
         <literal>prime256v1</literal> (NIST P-256),
         <literal>secp384r1</literal> (NIST P-384),
         <literal>secp521r1</literal> (NIST P-521).
-        The full list of available curves can be shown with the command
-        <command>openssl ecparam -list_curves</command>.  Not all of them
-        are usable in <acronym>TLS</acronym> though.
+        An incomplete list of available groups can be shown with the command
+        <command>openssl ecparam -list_curves</command>.  Not all of them are
+        usable with <acronym>TLS</acronym> though, and many supported group
+        names and aliases are omitted.
+       </para>
+
+       <para>
+        In <productname>PostgreSQL</productname> versions before 18.0 this
+        setting was named <literal>ssl_ecdh_curve</literal> and only accepted
+        a single value.
        </para>
       </listitem>
      </varlistentry>
 
                    void *userdata);
 static bool initialize_dh(SSL_CTX *context, bool isServerStart);
 static bool initialize_ecdh(SSL_CTX *context, bool isServerStart);
+static const char *SSLerrmessageExt(unsigned long ecode, const char *replacement);
 static const char *SSLerrmessage(unsigned long ecode);
 
 static char *X509_NAME_to_cstring(X509_NAME *name);
 initialize_ecdh(SSL_CTX *context, bool isServerStart)
 {
 #ifndef OPENSSL_NO_ECDH
-   EC_KEY     *ecdh;
-   int         nid;
-
-   nid = OBJ_sn2nid(SSLECDHCurve);
-   if (!nid)
-   {
-       ereport(isServerStart ? FATAL : LOG,
-               (errcode(ERRCODE_CONFIG_FILE_ERROR),
-                errmsg("ECDH: unrecognized curve name: %s", SSLECDHCurve)));
-       return false;
-   }
-
-   ecdh = EC_KEY_new_by_curve_name(nid);
-   if (!ecdh)
+   if (SSL_CTX_set1_groups_list(context, SSLECDHCurve) != 1)
    {
+       /*
+        * OpenSSL 3.3.0 introduced proper error messages for group parsing
+        * errors, earlier versions returns "no SSL error reported" which is
+        * far from helpful. For older versions, we replace with a better
+        * error message. Injecting the error into the OpenSSL error queue
+        * need APIs from OpenSSL 3.0.
+        */
        ereport(isServerStart ? FATAL : LOG,
-               (errcode(ERRCODE_CONFIG_FILE_ERROR),
-                errmsg("ECDH: could not create key")));
+               errcode(ERRCODE_CONFIG_FILE_ERROR),
+               errmsg("failed to set group names specified in ssl_groups: %s",
+                      SSLerrmessageExt(ERR_get_error(),
+                                       _("No valid groups found"))),
+               errhint("Ensure that each group name is spelled correctly and supported by the installed version of OpenSSL"));
        return false;
    }
-
-   SSL_CTX_set_options(context, SSL_OP_SINGLE_ECDH_USE);
-   SSL_CTX_set_tmp_ecdh(context, ecdh);
-   EC_KEY_free(ecdh);
 #endif
 
    return true;
 }
 
+/*
+ * Obtain reason string for passed SSL errcode with replacement
+ *
+ * The error message supplied in replacement will be used in case the error
+ * code from OpenSSL is 0, else the error message from SSLerrmessage() will
+ * be returned.
+ *
+ * Not all versions of OpenSSL place an error on the queue even for failing
+ * operations, which will yield "no SSL error reported" by SSLerrmessage. This
+ * function can be used to ensure that a proper error message is displayed for
+ * versions reporting no error, while using the OpenSSL error via SSLerrmessage
+ * for versions where there is one.
+ */
+static const char *
+SSLerrmessageExt(unsigned long ecode, const char *replacement)
+{
+   if (ecode == 0)
+       return replacement;
+   else
+       return SSLerrmessage(ecode);
+}
+
 /*
  * Obtain reason string for passed SSL errcode
  *
 
 static const char *const map_old_guc_names[] = {
    "sort_mem", "work_mem",
    "vacuum_mem", "maintenance_work_mem",
+   "ssl_ecdh_curve", "ssl_groups",
    NULL
 };
 
 
    },
 
    {
-       {"ssl_ecdh_curve", PGC_SIGHUP, CONN_AUTH_SSL,
-           gettext_noop("Sets the curve to use for ECDH."),
-           NULL,
+       {"ssl_groups", PGC_SIGHUP, CONN_AUTH_SSL,
+           gettext_noop("Sets the group(s) to use for Diffie-Hellman key exchange."),
+           gettext_noop("Multiple groups can be specified using colon-separated list."),
            GUC_SUPERUSER_ONLY
        },
        &SSLECDHCurve,
 
 #ssl_key_file = 'server.key'
 #ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL'  # allowed SSL ciphers
 #ssl_prefer_server_ciphers = on
-#ssl_ecdh_curve = 'prime256v1'
+#ssl_groups = 'prime256v1'
 #ssl_min_protocol_version = 'TLSv1.2'
 #ssl_max_protocol_version = ''
 #ssl_dh_params_file = ''
 
 $result = $node->restart(fail_ok => 1);
 is($result, 1, 'restart succeeds with correct SSL protocol bounds');
 
+# Test parsing colon-separated groups. Resetting to a default value to clear
+# the error is fine since the call to switch_server_cert in the client side
+# tests will overwrite ssl_groups with a known set of groups.
+$node->append_conf('sslconfig.conf', qq{ssl_groups='bad:value'});
+my $log_size = -s $node->logfile;
+$result = $node->restart(fail_ok => 1);
+is($result, 0, 'restart fails with incorrect groups');
+ok($node->log_contains(qr/no SSL error reported/) == 0,
+   'error message translated');
+$node->append_conf('ssl_config.conf', qq{ssl_groups='prime256v1'});
+$result = $node->restart(fail_ok => 1);
+
 ### Run client-side tests.
 ###
 ### Test that libpq accepts/rejects the connection correctly, depending
 
    ok(unlink($node->data_dir . '/sslconfig.conf'));
    $node->append_conf('sslconfig.conf', "ssl=on");
    $node->append_conf('sslconfig.conf', $backend->set_server_cert(\%params));
+   # use lists of ECDH curves for syntax testing
+   $node->append_conf('sslconfig.conf', 'ssl_groups=prime256v1:secp521r1');
+
    $node->append_conf('sslconfig.conf',
        "ssl_passphrase_command='" . $params{passphrase_cmd} . "'")
      if defined $params{passphrase_cmd};