Extended test coverage and docs for SSL passphrase commands

Started by Daniel Gustafsson2 months ago12 messages
#1Daniel Gustafsson
daniel@yesql.se
2 attachment(s)

When I was writing tests for the SSL SNI patch [0] I realized that the current
tests for ssl passphrase commands aren't fully exercising the feature, so I
extended them to better understand how it works. Attached is an extended set
of tests for passphrase protected keys where connection and reloads are tested
as well as their different characteristics on Windows.

The patchset also contains a small doc addition which documents the fact that
passphrase command reloading must be on when running on Windows (EXEC_BACKEND)
since every backend will issue a SSL configuration reload.

--
Daniel Gustafsson

Attachments:

v1-0002-ssl-Add-connection-and-reload-tests-for-key-passp.patchapplication/octet-stream; name=v1-0002-ssl-Add-connection-and-reload-tests-for-key-passp.patch; x-unix-mode=0644Download
From 971b29c33d0ac2f81f4dc90190cc2fc89f4c7e90 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <dgustafsson@postgresql.org>
Date: Fri, 7 Nov 2025 13:48:07 +0100
Subject: [PATCH v1 2/2] ssl: Add connection and reload tests for key
 passphrases

ssl_passphrase_command_supports_reload was not covered by the SSL
testsuite,  and connection tests after unlocking secrets with the
passphrase was also missing.  This adds test coverage for reloads
of passphrase commands as well as connection attempts which tests
the different codepaths for Windows and non-EXEC_BACKEND builds.

Author: Daniel Gustafsson <daniel@yesql.se>
Reviewed-by: ...
Discussion: https://postgr.es/m/...
---
 src/test/ssl/t/001_ssltests.pl | 87 +++++++++++++++++++++++++++++-----
 src/test/ssl/t/SSL/Server.pm   | 14 +++++-
 2 files changed, 87 insertions(+), 14 deletions(-)

diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 310d70a4c08..1bbe086501b 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -51,8 +51,15 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
 my $supports_sslcertmode_require =
   check_pg_config("#define HAVE_SSL_CTX_SET_CERT_CB 1");
 
+# Set of default settings for SSL parameters in connection string.  This
+# makes the tests protected against any defaults the environment may have
+# in ~/.postgresql/.
+my $default_ssl_connstr =
+  "sslkey=invalid sslcert=invalid sslrootcert=invalid sslcrl=invalid sslcrldir=invalid";
+
 # Allocation of base connection string shared among multiple tests.
-my $common_connstr;
+my $common_connstr =
+  "$default_ssl_connstr user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
 
 #### Set up the server.
 
@@ -77,6 +84,8 @@ $ssl_server->configure_test_server_for_ssl($node, $SERVERHOSTADDR,
 
 note "testing password-protected keys";
 
+# Test a passphrase command which fails to unlock the private key, the server
+# should not start at all.
 switch_server_cert(
 	$node,
 	certfile => 'server-cn-only',
@@ -85,20 +94,83 @@ switch_server_cert(
 	passphrase_cmd => 'echo wrongpassword',
 	restart => 'no');
 
-$result = $node->restart(fail_ok => 1);
+$result = $node->restart(
+	fail_ok => 1,
+	log_like => qr/could not load private key file/);
 is($result, 0,
 	'restart fails with password-protected key file with wrong password');
 
+# Test a passphrase command which successfully unlocks the private key but
+# which doesn't support reloading.  Unlocking the private key will fail when
+# reloading and the already existing SSL context will remain in place, with
+# connections still accepted.  On Windows connections will however fail since
+# EXEC_BACKEND builds will reload the SSL context on each backend startup, so
+# command reloading must be enabled.
 switch_server_cert(
 	$node,
 	certfile => 'server-cn-only',
 	cafile => 'root+client_ca',
 	keyfile => 'server-password',
 	passphrase_cmd => 'echo secret1',
+	passphrase_cmd_reload => 'off',
 	restart => 'no');
 
-$result = $node->restart(fail_ok => 1);
+$result = $node->restart(
+	fail_ok => 1,
+	log_unlike => qr/could not load private key file/);
+is($result, 1, 'restart succeeds with password-protected key file');
+
+if ($windows_os)
+{
+	$node->connect_fails(
+		"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+		"connect with correct server CA cert file sslmode=require",
+		expected_stderr => qr/\Qthe expected err\E/);
+}
+else
+{
+	$node->connect_ok(
+		"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+		"connect with correct server CA cert file sslmode=require");
+}
+
+# Reloading should fail since we cannot execute the passphrase command
+$node->reload();
+my $log_start = $node->wait_for_log(
+	qr/cannot be reloaded because it requires a passphrase/);
+
+# Test a passphrase command which successfully unlocks the private key, and
+# which can be reloaded.  The server should start and connections be accepted.
+switch_server_cert(
+	$node,
+	certfile => 'server-cn-only',
+	cafile => 'root+client_ca',
+	keyfile => 'server-password',
+	passphrase_cmd => 'echo secret1',
+	passphrase_cmd_reload => 'on',
+	restart => 'no');
+
+$result = $node->restart(
+	fail_ok => 1,
+	log_unlike => qr/could not load private key file/);
 is($result, 1, 'restart succeeds with password-protected key file');
+$node->connect_ok(
+	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+	"connect with correct server CA cert file sslmode=require");
+
+# Reloading the config should execute the passphrase reload command and
+# successfully reload the private key.
+$node->reload();
+$log_start =
+  $node->wait_for_log(qr/reloading configuration files/, $log_start);
+$node->log_check(
+	"passhprase could reload private key",
+	$log_start,
+	log_unlike => [ qr/cannot be reloaded because it requires a passphrase/, ]
+);
+$node->connect_ok(
+	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+	"connect with correct server CA cert file sslmode=require");
 
 # Test compatibility of SSL protocols.
 # TLSv1.1 is lower than TLSv1.2, so it won't work.
@@ -139,15 +211,6 @@ note "running client tests";
 
 switch_server_cert($node, certfile => 'server-cn-only');
 
-# Set of default settings for SSL parameters in connection string.  This
-# makes the tests protected against any defaults the environment may have
-# in ~/.postgresql/.
-my $default_ssl_connstr =
-  "sslkey=invalid sslcert=invalid sslrootcert=invalid sslcrl=invalid sslcrldir=invalid";
-
-$common_connstr =
-  "$default_ssl_connstr user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
-
 SKIP:
 {
 	skip "Keylogging is not supported with LibreSSL", 5 if $libressl;
diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm
index efbd0dafaf6..a0a786c2ef2 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -296,6 +296,11 @@ The CRL directory to use. Implementation is SSL backend specific.
 The passphrase command to use. If not set, an empty passphrase command will
 be set.
 
+=item passphrase_cmd_reload => B<value>
+
+Whether or not to allow passphrase command reloading. If set the passphrase
+command reload configuration setting will be set to the value.
+
 =item restart => B<value>
 
 If set to 'no', the server won't be restarted after updating the settings.
@@ -315,7 +320,7 @@ sub switch_server_cert
 	my $pgdata = $node->data_dir;
 
 	ok(unlink($node->data_dir . '/sslconfig.conf'));
-	$node->append_conf('sslconfig.conf', "ssl=on");
+	$node->append_conf('sslconfig.conf', 'ssl=on');
 	$node->append_conf('sslconfig.conf', $backend->set_server_cert(\%params));
 	# use lists of ECDH curves and cipher suites for syntax testing
 	$node->append_conf('sslconfig.conf',
@@ -324,9 +329,14 @@ sub switch_server_cert
 		'ssl_tls13_ciphers=TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256');
 
 	$node->append_conf('sslconfig.conf',
-		"ssl_passphrase_command='" . $params{passphrase_cmd} . "'")
+		'ssl_passphrase_command=\'' . $params{passphrase_cmd} . '\'')
 	  if defined $params{passphrase_cmd};
 
+	$node->append_conf('sslconfig.conf',
+		'ssl_passphrase_command_supports_reload=\''
+		  . $params{passphrase_cmd_reload} . '\'')
+	  if defined $params{passphrase_cmd_reload};
+
 	return if (defined($params{restart}) && $params{restart} eq 'no');
 
 	$node->restart;
-- 
2.39.3 (Apple Git-146)

v1-0001-doc-Clarify-passphrase-command-reloading-on-Windo.patchapplication/octet-stream; name=v1-0001-doc-Clarify-passphrase-command-reloading-on-Windo.patch; x-unix-mode=0644Download
From b956b71ee6831d1917bf05644cfbf5c512e490ba Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <dgustafsson@postgresql.org>
Date: Fri, 7 Nov 2025 10:35:21 +0100
Subject: [PATCH v1 1/2] doc: Clarify passphrase command reloading on Windows

When running on Windows (or EXEC_BACKEND) the SSL configuration will
be reloaded on each backend start, so the passphrase command will be
reloaded along with it.  This implies that passphrase command reload
must be enabled on Windows for connections to work at all.  Document
this since it wasn't mentioned explicitly, and will there add markup
for parameter value to match the rest of the docs.

Backpatch to all supported versions.

Author: Daniel Gustafsson <daniel@yesql.se>
Reviewed-by: ...
Discussion: https://postgr.es/m/...
Backpatch-through: 13
---
 doc/src/sgml/config.sgml | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index d8a9f14b618..4a7a46c16d6 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1680,7 +1680,7 @@ include_dir 'conf.d'
         This parameter determines whether the passphrase command set by
         <varname>ssl_passphrase_command</varname> will also be called during a
         configuration reload if a key file needs a passphrase.  If this
-        parameter is off (the default), then
+        parameter is <literal>off</literal> (the default), then
         <varname>ssl_passphrase_command</varname> will be ignored during a
         reload and the SSL configuration will not be reloaded if a passphrase
         is needed.  That setting is appropriate for a command that requires a
@@ -1688,6 +1688,12 @@ include_dir 'conf.d'
         running.  Setting this parameter to on might be appropriate if the
         passphrase is obtained from a file, for example.
        </para>
+       <para>
+        This parameter must be set to <literal>on</literal> when running on
+        <systemitem class="osname">Windows</systemitem> since all connections
+        will perform a configuration reload due to the different process model
+        of that platform.
+       </para>
        <para>
         This parameter can only be set in the <filename>postgresql.conf</filename>
         file or on the server command line.
-- 
2.39.3 (Apple Git-146)

#2Peter Eisentraut
peter@eisentraut.org
In reply to: Daniel Gustafsson (#1)
Re: Extended test coverage and docs for SSL passphrase commands

On 07.11.25 21:26, Daniel Gustafsson wrote:

When I was writing tests for the SSL SNI patch [0] I realized that the current
tests for ssl passphrase commands aren't fully exercising the feature, so I
extended them to better understand how it works. Attached is an extended set
of tests for passphrase protected keys where connection and reloads are tested
as well as their different characteristics on Windows.

The patchset also contains a small doc addition which documents the fact that
passphrase command reloading must be on when running on Windows (EXEC_BACKEND)
since every backend will issue a SSL configuration reload.

Your test code conflates $windows_os with EXEC_BACKEND. It should work
to enable EXEC_BACKEND on a non-Windows system and have everything work.
So I think that code needs to extract the actual EXEC_BACKEND setting
somehow, instead of using the OS identity as a proxy.

About the behavior that your documentation patch describes, I would like
to have some kind of reflection of that in the code as well. At least a
comment near default_openssl_tls_init() maybe? I haven't traced the
code through, but I would be curious about what is different in an
EXEC_BACKEND environment. For example, is the argument isServerStart
also true if it's not a server start? Or should the setting actually be
enforced directly on the GUC system?

#3Daniel Gustafsson
daniel@yesql.se
In reply to: Peter Eisentraut (#2)
Re: Extended test coverage and docs for SSL passphrase commands

On 12 Nov 2025, at 15:15, Peter Eisentraut <peter@eisentraut.org> wrote:

On 07.11.25 21:26, Daniel Gustafsson wrote:

When I was writing tests for the SSL SNI patch [0] I realized that the current
tests for ssl passphrase commands aren't fully exercising the feature, so I
extended them to better understand how it works. Attached is an extended set
of tests for passphrase protected keys where connection and reloads are tested
as well as their different characteristics on Windows.
The patchset also contains a small doc addition which documents the fact that
passphrase command reloading must be on when running on Windows (EXEC_BACKEND)
since every backend will issue a SSL configuration reload.

Your test code conflates $windows_os with EXEC_BACKEND. It should work to enable EXEC_BACKEND on a non-Windows system and have everything work. So I think that code needs to extract the actual EXEC_BACKEND setting somehow, instead of using the OS identity as a proxy.

As far as I know the only way to programmatically learn that from the Perl
testcode would be to check for the presence of the CONFIG_EXEC_PARAMS file in
$self->data_dir, which should be easy enough to do. Do you know of a better
way?

About the behavior that your documentation patch describes, I would like to have some kind of reflection of that in the code as well. At least a comment near default_openssl_tls_init() maybe? I haven't traced the code through, but I would be curious about what is different in an EXEC_BACKEND environment. For example, is the argument isServerStart also true if it's not a server start? Or should the setting actually be enforced directly on the GUC system?

It is documented in src/backend/tcop/backend_startup.c with the following
comment in BackendMain():

#ifdef EXEC_BACKEND

/*
* Need to reinitialize the SSL library in the backend, since the context
* structures contain function pointers and cannot be passed through the
* parameter file.
*
* If for some reason reload fails (maybe the user installed broken key
* files), soldier on without SSL; that's better than all connections
* becoming impossible.
*
* XXX should we do this in all child processes? For the moment it's
* enough to do it in backend children.
*/
#ifdef USE_SSL
if (EnableSSL)
{
if (secure_initialize(false) == 0)
LoadedSSL = true;

Calling secure_initialize with isServerStart == false will force a reload which
in turn requires the passphrase command to be reloadable if it is to work at
all.

Not sure if we need too much more than that, but maybe a note could be added to
be_tls_init that isServerStart will reflect config reloads as well as
EXEC_BACKEND?

--
Daniel Gustafsson

#4Álvaro Herrera
alvherre@kurilemu.de
In reply to: Daniel Gustafsson (#3)
Re: Extended test coverage and docs for SSL passphrase commands

On 2025-Nov-12, Daniel Gustafsson wrote:

As far as I know the only way to programmatically learn that from the Perl
testcode would be to check for the presence of the CONFIG_EXEC_PARAMS file in
$self->data_dir, which should be easy enough to do. Do you know of a better
way?

We have check_pg_config(), which reads pg_config.h. For EXEC_BACKEND
you need pg_config_manual.h, but I think you could go roughly in the
same direction ... for instance you could add an argument to
check_pg_config() to say which file to read -- though I think the
easiest is to read both files always and return the grep count in both.

--
Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/
Officer Krupke, what are we to do?
Gee, officer Krupke, Krup you! (West Side Story, "Gee, Officer Krupke")

#5Daniel Gustafsson
daniel@yesql.se
In reply to: Álvaro Herrera (#4)
Re: Extended test coverage and docs for SSL passphrase commands

On 12 Nov 2025, at 18:47, Álvaro Herrera <alvherre@kurilemu.de> wrote:

On 2025-Nov-12, Daniel Gustafsson wrote:

As far as I know the only way to programmatically learn that from the Perl
testcode would be to check for the presence of the CONFIG_EXEC_PARAMS file in
$self->data_dir, which should be easy enough to do. Do you know of a better
way?

We have check_pg_config(), which reads pg_config.h. For EXEC_BACKEND
you need pg_config_manual.h,

Right, but they can't be treated the same since EXEC_BACKEND will always be
matched by such a grep and the presence of WIN32 and !__CYGWIN__ mst be tested
for. Or am I thinking about it the wrong way? This is why I figured checking
for the exec_params file could be an option, but with the drawback that it
would only work for a running cluster so it wouldn't be a generic function but
coded directly in the SSL TAP test file.

--
Daniel Gustafsson

#6Daniel Gustafsson
daniel@yesql.se
In reply to: Daniel Gustafsson (#5)
3 attachment(s)
Re: Extended test coverage and docs for SSL passphrase commands

On 13 Nov 2025, at 00:12, Daniel Gustafsson <daniel@yesql.se> wrote:

On 12 Nov 2025, at 18:47, Álvaro Herrera <alvherre@kurilemu.de> wrote:

On 2025-Nov-12, Daniel Gustafsson wrote:

As far as I know the only way to programmatically learn that from the Perl
testcode would be to check for the presence of the CONFIG_EXEC_PARAMS file in
$self->data_dir, which should be easy enough to do. Do you know of a better
way?

We have check_pg_config(), which reads pg_config.h. For EXEC_BACKEND
you need pg_config_manual.h,

Right, but they can't be treated the same since EXEC_BACKEND will always be
matched by such a grep and the presence of WIN32 and !__CYGWIN__ mst be tested
for.

The attached v2 adds a GUC debug_exec_backend which can be used to get the
state of the running cluster, much like how debug_assertions will tell whether
or not assertions were compiled in or not. (Per an idea off-list conversation
about this.) This will be operating system independent and reusable in other
tests as well.

The rest of the patches are the same, just adapted to use this GUC in the SSL
test.

--
Daniel Gustafsson

Attachments:

v2-0001-Add-GUC-to-show-EXEC_BACKEND-state.patchapplication/octet-stream; name=v2-0001-Add-GUC-to-show-EXEC_BACKEND-state.patch; x-unix-mode=0644Download
From 652c117efa13d395225e73555fa761544829cf60 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <dgustafsson@postgresql.org>
Date: Fri, 21 Nov 2025 13:47:00 +0100
Subject: [PATCH v2 1/3] Add GUC to show EXEC_BACKEND state

There is no straightforward way to determine if a cluster is running
in EXEC_BACKEND mode or not, which is useful for tests to know. This
adds a GUC debug_exec_backend similar to debug_assertions which will
be true when the server is running in EXEC_BACKEND mode.

Author: Daniel Gustafsson <daniel@yesql.se>
Reviewed-by: ...
Discussion: https://postgr.es/m/..
---
 doc/src/sgml/config.sgml                  | 17 +++++++++++++++++
 src/backend/utils/misc/guc_parameters.dat |  7 +++++++
 src/backend/utils/misc/guc_tables.c       |  7 +++++++
 3 files changed, 31 insertions(+)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 023b3f03ba9..b8d6f43d5ff 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -11827,6 +11827,23 @@ dynamic_library_path = '/usr/local/lib/postgresql:$libdir'
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-debug-exec-backend" xreflabel="debug_exec_backend">
+      <term><varname>debug_exec_backend</varname> (<type>boolean</type>)
+      <indexterm>
+       <primary><varname>debug_exec_backend</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        Reports whether <productname>PostgreSQL</productname> has been built
+        with <literal>EXEC_BACKEND</literal> enabled. That is the case on
+        <systemitem class="osname">Windows</systemitem> or if the
+        macro <symbol>EXEC_BACKEND</symbol> is defined
+        when <productname>PostgreSQL</productname> is built.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-huge-pages-status" xreflabel="huge_pages_status">
       <term><varname>huge_pages_status</varname> (<type>enum</type>)
       <indexterm>
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 1128167c025..e6bb0ec8478 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -617,6 +617,13 @@
   max => 'MAX_DEBUG_DISCARD_CACHES',
 },
 
+{ name => 'debug_exec_backend', type => 'bool', context => 'PGC_INTERNAL', group => 'PRESET_OPTIONS',
+  short_desc => 'Shows whether the running server is running in EXEC_BACKEND mode.',
+  flags => 'GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE',
+  variable => 'exec_backend_enabled',
+  boot_val => 'DEFAULT_EXEC_BACKEND_ENABLED',
+},
+
 { name => 'debug_io_direct', type => 'string', context => 'PGC_POSTMASTER', group => 'DEVELOPER_OPTIONS',
   short_desc => 'Use direct I/O for file access.',
   long_desc => 'An empty string disables direct I/O.',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0209b2067a2..b0b10890b55 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -627,6 +627,13 @@ static bool integer_datetimes;
 #endif
 static bool assert_enabled = DEFAULT_ASSERT_ENABLED;
 
+#ifdef EXEC_BACKEND
+#define DEFAULT_EXEC_BACKEND_ENABLED true
+#else
+#define DEFAULT_EXEC_BACKEND_ENABLED false
+#endif
+static bool exec_backend_enabled = DEFAULT_EXEC_BACKEND_ENABLED;
+
 static char *recovery_target_timeline_string;
 static char *recovery_target_string;
 static char *recovery_target_xid_string;
-- 
2.39.3 (Apple Git-146)

v2-0002-doc-Clarify-passphrase-command-reloading-on-Windo.patchapplication/octet-stream; name=v2-0002-doc-Clarify-passphrase-command-reloading-on-Windo.patch; x-unix-mode=0644Download
From 76982ec375c341ff2bf5d07489841489ab6ccb1f Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <dgustafsson@postgresql.org>
Date: Fri, 7 Nov 2025 10:35:21 +0100
Subject: [PATCH v2 2/3] doc: Clarify passphrase command reloading on Windows

When running on Windows (or EXEC_BACKEND) the SSL configuration will
be reloaded on each backend start, so the passphrase command will be
reloaded along with it.  This implies that passphrase command reload
must be enabled on Windows for connections to work at all.  Document
this since it wasn't mentioned explicitly, and will there add markup
for parameter value to match the rest of the docs.

Backpatch to all supported versions.

Author: Daniel Gustafsson <daniel@yesql.se>
Reviewed-by: ...
Discussion: https://postgr.es/m/...
Backpatch-through: 13
---
 doc/src/sgml/config.sgml | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index b8d6f43d5ff..737b90736bf 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1680,7 +1680,7 @@ include_dir 'conf.d'
         This parameter determines whether the passphrase command set by
         <varname>ssl_passphrase_command</varname> will also be called during a
         configuration reload if a key file needs a passphrase.  If this
-        parameter is off (the default), then
+        parameter is <literal>off</literal> (the default), then
         <varname>ssl_passphrase_command</varname> will be ignored during a
         reload and the SSL configuration will not be reloaded if a passphrase
         is needed.  That setting is appropriate for a command that requires a
@@ -1688,6 +1688,12 @@ include_dir 'conf.d'
         running.  Setting this parameter to on might be appropriate if the
         passphrase is obtained from a file, for example.
        </para>
+       <para>
+        This parameter must be set to <literal>on</literal> when running on
+        <systemitem class="osname">Windows</systemitem> since all connections
+        will perform a configuration reload due to the different process model
+        of that platform.
+       </para>
        <para>
         This parameter can only be set in the <filename>postgresql.conf</filename>
         file or on the server command line.
-- 
2.39.3 (Apple Git-146)

v2-0003-ssl-Add-connection-and-reload-tests-for-key-passp.patchapplication/octet-stream; name=v2-0003-ssl-Add-connection-and-reload-tests-for-key-passp.patch; x-unix-mode=0644Download
From 38dce3a27cbdb1009f8ad1b762290aab9f636d9d Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <dgustafsson@postgresql.org>
Date: Fri, 7 Nov 2025 13:48:07 +0100
Subject: [PATCH v2 3/3] ssl: Add connection and reload tests for key
 passphrases

ssl_passphrase_command_supports_reload was not covered by the SSL
testsuite,  and connection tests after unlocking secrets with the
passphrase was also missing.  This adds test coverage for reloads
of passphrase commands as well as connection attempts which tests
the different codepaths for Windows and non-EXEC_BACKEND builds.

Author: Daniel Gustafsson <daniel@yesql.se>
Reviewed-by: ...
Discussion: https://postgr.es/m/...
---
 src/test/ssl/t/001_ssltests.pl | 90 +++++++++++++++++++++++++++++-----
 src/test/ssl/t/SSL/Server.pm   | 14 +++++-
 2 files changed, 90 insertions(+), 14 deletions(-)

diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 310d70a4c08..b7f616ba6cb 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -51,8 +51,15 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
 my $supports_sslcertmode_require =
   check_pg_config("#define HAVE_SSL_CTX_SET_CERT_CB 1");
 
+# Set of default settings for SSL parameters in connection string.  This
+# makes the tests protected against any defaults the environment may have
+# in ~/.postgresql/.
+my $default_ssl_connstr =
+  "sslkey=invalid sslcert=invalid sslrootcert=invalid sslcrl=invalid sslcrldir=invalid";
+
 # Allocation of base connection string shared among multiple tests.
-my $common_connstr;
+my $common_connstr =
+  "$default_ssl_connstr user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
 
 #### Set up the server.
 
@@ -72,11 +79,16 @@ $node->start;
 my $result = $node->safe_psql('postgres', "SHOW ssl_library");
 is($result, $ssl_server->ssl_library(), 'ssl_library parameter');
 
+my $exec_backend = $node->safe_psql('postgres', 'SHOW debug_exec_backend');
+chomp($exec_backend);
+
 $ssl_server->configure_test_server_for_ssl($node, $SERVERHOSTADDR,
 	$SERVERHOSTCIDR, 'trust');
 
 note "testing password-protected keys";
 
+# Test a passphrase command which fails to unlock the private key, the server
+# should not start at all.
 switch_server_cert(
 	$node,
 	certfile => 'server-cn-only',
@@ -85,21 +97,84 @@ switch_server_cert(
 	passphrase_cmd => 'echo wrongpassword',
 	restart => 'no');
 
-$result = $node->restart(fail_ok => 1);
+$result = $node->restart(
+	fail_ok => 1,
+	log_like => qr/could not load private key file/);
 is($result, 0,
 	'restart fails with password-protected key file with wrong password');
 
+# Test a passphrase command which successfully unlocks the private key but
+# which doesn't support reloading.  Unlocking the private key will fail when
+# reloading and the already existing SSL context will remain in place, with
+# connections still accepted.  EXEC_BACKEND builds will reload the SSL context
+# on each backend startup, so command reloading must be enabled or else
+# connections will fail.
 switch_server_cert(
 	$node,
 	certfile => 'server-cn-only',
 	cafile => 'root+client_ca',
 	keyfile => 'server-password',
 	passphrase_cmd => 'echo secret1',
+	passphrase_cmd_reload => 'off',
 	restart => 'no');
 
-$result = $node->restart(fail_ok => 1);
+$result = $node->restart(
+	fail_ok => 1,
+	log_unlike => qr/could not load private key file/);
 is($result, 1, 'restart succeeds with password-protected key file');
 
+if ($exec_backend =~ /on/)
+{
+	$node->connect_fails(
+		"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+		"connect with correct server CA cert file sslmode=require",
+		expected_stderr => qr/\Qserver does not support SSL\E/);
+}
+else
+{
+	$node->connect_ok(
+		"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+		"connect with correct server CA cert file sslmode=require");
+}
+
+# Reloading should fail since we cannot execute the passphrase command
+$node->reload();
+my $log_start = $node->wait_for_log(
+	qr/cannot be reloaded because it requires a passphrase/);
+
+# Test a passphrase command which successfully unlocks the private key, and
+# which can be reloaded.  The server should start and connections be accepted.
+switch_server_cert(
+	$node,
+	certfile => 'server-cn-only',
+	cafile => 'root+client_ca',
+	keyfile => 'server-password',
+	passphrase_cmd => 'echo secret1',
+	passphrase_cmd_reload => 'on',
+	restart => 'no');
+
+$result = $node->restart(
+	fail_ok => 1,
+	log_unlike => qr/could not load private key file/);
+is($result, 1, 'restart succeeds with password-protected key file');
+$node->connect_ok(
+	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+	"connect with correct server CA cert file sslmode=require");
+
+# Reloading the config should execute the passphrase reload command and
+# successfully reload the private key.
+$node->reload();
+$log_start =
+  $node->wait_for_log(qr/reloading configuration files/, $log_start);
+$node->log_check(
+	"passhprase could reload private key",
+	$log_start,
+	log_unlike => [ qr/cannot be reloaded because it requires a passphrase/, ]
+);
+$node->connect_ok(
+	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+	"connect with correct server CA cert file sslmode=require");
+
 # Test compatibility of SSL protocols.
 # TLSv1.1 is lower than TLSv1.2, so it won't work.
 $node->append_conf(
@@ -139,15 +214,6 @@ note "running client tests";
 
 switch_server_cert($node, certfile => 'server-cn-only');
 
-# Set of default settings for SSL parameters in connection string.  This
-# makes the tests protected against any defaults the environment may have
-# in ~/.postgresql/.
-my $default_ssl_connstr =
-  "sslkey=invalid sslcert=invalid sslrootcert=invalid sslcrl=invalid sslcrldir=invalid";
-
-$common_connstr =
-  "$default_ssl_connstr user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
-
 SKIP:
 {
 	skip "Keylogging is not supported with LibreSSL", 5 if $libressl;
diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm
index efbd0dafaf6..a0a786c2ef2 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -296,6 +296,11 @@ The CRL directory to use. Implementation is SSL backend specific.
 The passphrase command to use. If not set, an empty passphrase command will
 be set.
 
+=item passphrase_cmd_reload => B<value>
+
+Whether or not to allow passphrase command reloading. If set the passphrase
+command reload configuration setting will be set to the value.
+
 =item restart => B<value>
 
 If set to 'no', the server won't be restarted after updating the settings.
@@ -315,7 +320,7 @@ sub switch_server_cert
 	my $pgdata = $node->data_dir;
 
 	ok(unlink($node->data_dir . '/sslconfig.conf'));
-	$node->append_conf('sslconfig.conf', "ssl=on");
+	$node->append_conf('sslconfig.conf', 'ssl=on');
 	$node->append_conf('sslconfig.conf', $backend->set_server_cert(\%params));
 	# use lists of ECDH curves and cipher suites for syntax testing
 	$node->append_conf('sslconfig.conf',
@@ -324,9 +329,14 @@ sub switch_server_cert
 		'ssl_tls13_ciphers=TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256');
 
 	$node->append_conf('sslconfig.conf',
-		"ssl_passphrase_command='" . $params{passphrase_cmd} . "'")
+		'ssl_passphrase_command=\'' . $params{passphrase_cmd} . '\'')
 	  if defined $params{passphrase_cmd};
 
+	$node->append_conf('sslconfig.conf',
+		'ssl_passphrase_command_supports_reload=\''
+		  . $params{passphrase_cmd_reload} . '\'')
+	  if defined $params{passphrase_cmd_reload};
+
 	return if (defined($params{restart}) && $params{restart} eq 'no');
 
 	$node->restart;
-- 
2.39.3 (Apple Git-146)

#7Chao Li
li.evan.chao@gmail.com
In reply to: Daniel Gustafsson (#6)
Re: Extended test coverage and docs for SSL passphrase commands

Hi Daniel,

I just reviewed the patch and got a few comments.

On Nov 22, 2025, at 06:38, Daniel Gustafsson <daniel@yesql.se> wrote:

The attached v2 adds a GUC debug_exec_backend which can be used to get the
state of the running cluster, much like how debug_assertions will tell whether
or not assertions were compiled in or not. (Per an idea off-list conversation
about this.) This will be operating system independent and reusable in other
tests as well.

The rest of the patches are the same, just adapted to use this GUC in the SSL
test.

--
Daniel Gustafsson

<v2-0001-Add-GUC-to-show-EXEC_BACKEND-state.patch><v2-0002-doc-Clarify-passphrase-command-reloading-on-Windo.patch><v2-0003-ssl-Add-connection-and-reload-tests-for-key-passp.patch>

1 - 0001
```
+ short_desc => 'Shows whether the running server is running in EXEC_BACKEND mode.',
```

The GUC is added like a mirror of debug_assertions. However, I think a small difference is that, assertions will impact everything at runtime, while EXEC_BACKEND don’t really impact PG’s behavior, instead it only impacts how backend processes are spawned. Thus, I feel “running server is EXEC_BACKEND mode” is a little bit inaccurate, maybe just say “show whether the running server is built with EXEC_BACKEND”.

2 - 0002
```
+        This parameter must be set to <literal>on</literal> when running on
+        <systemitem class="osname">Windows</systemitem> since all connections
```

This is not a comment. I’m just thinking that, as EXEC_BACKEND is compile flag, when a server is started, it knows if EXEC_BACKEND is enabled or not. So that, if ssl_passphrase_command must be turned on, why cannot we automatically turn on it?

 3 - 0003
```
+$node->log_check(
+	"passhprase could reload private key",

```

Typo: passhprase -> passphrase

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/

#8Álvaro Herrera
alvherre@kurilemu.de
In reply to: Daniel Gustafsson (#6)
Re: Extended test coverage and docs for SSL passphrase commands

On 2025-Nov-21, Daniel Gustafsson wrote:

The attached v2 adds a GUC debug_exec_backend which can be used to get the
state of the running cluster,

Nice idea.

I think the parallel to debug_assertions is not perfect, because you can
turn off debug_assertions in a server built with them included, but you
cannot turn off EXEC_BACKEND. This says we shouldn't name the symbol
with the DEFAULT word: it should just be "EXEC_BACKEND_ENABLED". The
value already cannot be changed, which is good, and there are tests for
this behavior of PGC_INTERNAL gucs in src/test/modules/unsafe_tests/sql/guc_privs.sql,
so I think we don't need anything additional.

--
Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/
"Once again, thank you and all of the developers for your hard work on
PostgreSQL. This is by far the most pleasant management experience of
any database I've worked on." (Dan Harris)
http://archives.postgresql.org/pgsql-performance/2006-04/msg00247.php

#9Daniel Gustafsson
daniel@yesql.se
In reply to: Chao Li (#7)
Re: Extended test coverage and docs for SSL passphrase commands

On 22 Nov 2025, at 10:30, Chao Li <li.evan.chao@gmail.com> wrote:

I just reviewed the patch and got a few comments.

Thanks! An updated version will come downthread.

The GUC is added like a mirror of debug_assertions. However, I think a small difference is that, assertions will impact everything at runtime, while EXEC_BACKEND don’t really impact PG’s behavior, instead it only impacts how backend processes are spawned. Thus, I feel “running server is EXEC_BACKEND mode” is a little bit inaccurate, maybe just say “show whether the running server is built with EXEC_BACKEND”.

That's a good point, the docementation hunk had it right where this part got it
wrong. Fixed.

This is not a comment. I’m just thinking that, as EXEC_BACKEND is compile flag, when a server is started, it knows if EXEC_BACKEND is enabled or not. So that, if ssl_passphrase_command must be turned on, why cannot we automatically turn on it?

Technically we might be able to, but I don't want to override the administrator
when it comes to sensitive configuration settings. Better to document what
needs to be done and have the user make informed decisions.

Typo: passhprase -> passphrase

Fixed.

--
Daniel Gustafsson

#10Daniel Gustafsson
daniel@yesql.se
In reply to: Álvaro Herrera (#8)
3 attachment(s)
Re: Extended test coverage and docs for SSL passphrase commands

On 22 Nov 2025, at 14:00, Álvaro Herrera <alvherre@kurilemu.de> wrote:

On 2025-Nov-21, Daniel Gustafsson wrote:

The attached v2 adds a GUC debug_exec_backend which can be used to get the
state of the running cluster,

Nice idea.

I think the parallel to debug_assertions is not perfect, because you can
turn off debug_assertions in a server built with them included, but you
cannot turn off EXEC_BACKEND.

debug_assertions cannot be disabled anymore, since 3bdcf6a5a755.

This says we shouldn't name the symbol
with the DEFAULT word: it should just be "EXEC_BACKEND_ENABLED".

Regardless, I totally agree with that, fixed in the attached along with the
review comments from upthread.

--
Daniel Gustafsson

Attachments:

v2-0002-Add-GUC-to-show-EXEC_BACKEND-state.patchapplication/octet-stream; name=v2-0002-Add-GUC-to-show-EXEC_BACKEND-state.patch; x-unix-mode=0644Download
From 204ca80f5bf8ebdcb24bf9df29c5ff2ebfdfa7a7 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <dgustafsson@postgresql.org>
Date: Sun, 23 Nov 2025 20:51:57 +0100
Subject: [PATCH v2 2/3] Add GUC to show EXEC_BACKEND state
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

There is no straightforward way to determine if a cluster is running
in EXEC_BACKEND mode or not, which is useful for tests to know. This
adds a GUC debug_exec_backend similar to debug_assertions which will
be true when the server is running in EXEC_BACKEND mode.

Author: Daniel Gustafsson <daniel@yesql.se>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Reviewed-by: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/5F301096-921A-427D-8EC1-EBAEC2A35082@yesql.se
---
 doc/src/sgml/config.sgml                  | 17 +++++++++++++++++
 src/backend/utils/misc/guc_parameters.dat |  7 +++++++
 src/backend/utils/misc/guc_tables.c       |  7 +++++++
 3 files changed, 31 insertions(+)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 07ff5873a97..737b90736bf 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -11833,6 +11833,23 @@ dynamic_library_path = '/usr/local/lib/postgresql:$libdir'
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-debug-exec-backend" xreflabel="debug_exec_backend">
+      <term><varname>debug_exec_backend</varname> (<type>boolean</type>)
+      <indexterm>
+       <primary><varname>debug_exec_backend</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        Reports whether <productname>PostgreSQL</productname> has been built
+        with <literal>EXEC_BACKEND</literal> enabled. That is the case on
+        <systemitem class="osname">Windows</systemitem> or if the
+        macro <symbol>EXEC_BACKEND</symbol> is defined
+        when <productname>PostgreSQL</productname> is built.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-huge-pages-status" xreflabel="huge_pages_status">
       <term><varname>huge_pages_status</varname> (<type>enum</type>)
       <indexterm>
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 1128167c025..3b9d8349078 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -617,6 +617,13 @@
   max => 'MAX_DEBUG_DISCARD_CACHES',
 },
 
+{ name => 'debug_exec_backend', type => 'bool', context => 'PGC_INTERNAL', group => 'PRESET_OPTIONS',
+  short_desc => 'Shows whether the running server is built with EXEC_BACKEND enabled.',
+  flags => 'GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE',
+  variable => 'exec_backend_enabled',
+  boot_val => 'EXEC_BACKEND_ENABLED',
+},
+
 { name => 'debug_io_direct', type => 'string', context => 'PGC_POSTMASTER', group => 'DEVELOPER_OPTIONS',
   short_desc => 'Use direct I/O for file access.',
   long_desc => 'An empty string disables direct I/O.',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 0209b2067a2..f87b558c2c6 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -627,6 +627,13 @@ static bool integer_datetimes;
 #endif
 static bool assert_enabled = DEFAULT_ASSERT_ENABLED;
 
+#ifdef EXEC_BACKEND
+#define EXEC_BACKEND_ENABLED true
+#else
+#define EXEC_BACKEND_ENABLED false
+#endif
+static bool exec_backend_enabled = EXEC_BACKEND_ENABLED;
+
 static char *recovery_target_timeline_string;
 static char *recovery_target_string;
 static char *recovery_target_xid_string;
-- 
2.39.3 (Apple Git-146)

v2-0003-ssl-Add-connection-and-reload-tests-for-key-passp.patchapplication/octet-stream; name=v2-0003-ssl-Add-connection-and-reload-tests-for-key-passp.patch; x-unix-mode=0644Download
From d186bbb9c6000cb9e946ff6594495f8e7d56a274 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <dgustafsson@postgresql.org>
Date: Sun, 23 Nov 2025 20:52:23 +0100
Subject: [PATCH v2 3/3] ssl: Add connection and reload tests for key
 passphrases
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

ssl_passphrase_command_supports_reload was not covered by the SSL
testsuite,  and connection tests after unlocking secrets with the
passphrase was also missing.  This adds test coverage for reloads
of passphrase commands as well as connection attempts which tests
the different codepaths for Windows and non-EXEC_BACKEND builds.

Author: Daniel Gustafsson <daniel@yesql.se>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Reviewed-by: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/5F301096-921A-427D-8EC1-EBAEC2A35082@yesql.se
---
 src/test/ssl/t/001_ssltests.pl | 90 +++++++++++++++++++++++++++++-----
 src/test/ssl/t/SSL/Server.pm   | 14 +++++-
 2 files changed, 90 insertions(+), 14 deletions(-)

diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 310d70a4c08..fc7c35ef879 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -51,8 +51,15 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
 my $supports_sslcertmode_require =
   check_pg_config("#define HAVE_SSL_CTX_SET_CERT_CB 1");
 
+# Set of default settings for SSL parameters in connection string.  This
+# makes the tests protected against any defaults the environment may have
+# in ~/.postgresql/.
+my $default_ssl_connstr =
+  "sslkey=invalid sslcert=invalid sslrootcert=invalid sslcrl=invalid sslcrldir=invalid";
+
 # Allocation of base connection string shared among multiple tests.
-my $common_connstr;
+my $common_connstr =
+  "$default_ssl_connstr user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
 
 #### Set up the server.
 
@@ -72,11 +79,16 @@ $node->start;
 my $result = $node->safe_psql('postgres', "SHOW ssl_library");
 is($result, $ssl_server->ssl_library(), 'ssl_library parameter');
 
+my $exec_backend = $node->safe_psql('postgres', 'SHOW debug_exec_backend');
+chomp($exec_backend);
+
 $ssl_server->configure_test_server_for_ssl($node, $SERVERHOSTADDR,
 	$SERVERHOSTCIDR, 'trust');
 
 note "testing password-protected keys";
 
+# Test a passphrase command which fails to unlock the private key, the server
+# should not start at all.
 switch_server_cert(
 	$node,
 	certfile => 'server-cn-only',
@@ -85,21 +97,84 @@ switch_server_cert(
 	passphrase_cmd => 'echo wrongpassword',
 	restart => 'no');
 
-$result = $node->restart(fail_ok => 1);
+$result = $node->restart(
+	fail_ok => 1,
+	log_like => qr/could not load private key file/);
 is($result, 0,
 	'restart fails with password-protected key file with wrong password');
 
+# Test a passphrase command which successfully unlocks the private key but
+# which doesn't support reloading.  Unlocking the private key will fail when
+# reloading and the already existing SSL context will remain in place, with
+# connections still accepted.  EXEC_BACKEND builds will reload the SSL context
+# on each backend startup, so command reloading must be enabled or else
+# connections will fail.
 switch_server_cert(
 	$node,
 	certfile => 'server-cn-only',
 	cafile => 'root+client_ca',
 	keyfile => 'server-password',
 	passphrase_cmd => 'echo secret1',
+	passphrase_cmd_reload => 'off',
 	restart => 'no');
 
-$result = $node->restart(fail_ok => 1);
+$result = $node->restart(
+	fail_ok => 1,
+	log_unlike => qr/could not load private key file/);
 is($result, 1, 'restart succeeds with password-protected key file');
 
+if ($exec_backend =~ /on/)
+{
+	$node->connect_fails(
+		"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+		"connect with correct server CA cert file sslmode=require",
+		expected_stderr => qr/\Qserver does not support SSL\E/);
+}
+else
+{
+	$node->connect_ok(
+		"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+		"connect with correct server CA cert file sslmode=require");
+}
+
+# Reloading should fail since we cannot execute the passphrase command
+$node->reload();
+my $log_start = $node->wait_for_log(
+	qr/cannot be reloaded because it requires a passphrase/);
+
+# Test a passphrase command which successfully unlocks the private key, and
+# which can be reloaded.  The server should start and connections be accepted.
+switch_server_cert(
+	$node,
+	certfile => 'server-cn-only',
+	cafile => 'root+client_ca',
+	keyfile => 'server-password',
+	passphrase_cmd => 'echo secret1',
+	passphrase_cmd_reload => 'on',
+	restart => 'no');
+
+$result = $node->restart(
+	fail_ok => 1,
+	log_unlike => qr/could not load private key file/);
+is($result, 1, 'restart succeeds with password-protected key file');
+$node->connect_ok(
+	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+	"connect with correct server CA cert file sslmode=require");
+
+# Reloading the config should execute the passphrase reload command and
+# successfully reload the private key.
+$node->reload();
+$log_start =
+  $node->wait_for_log(qr/reloading configuration files/, $log_start);
+$node->log_check(
+	"passphrase could reload private key",
+	$log_start,
+	log_unlike => [ qr/cannot be reloaded because it requires a passphrase/, ]
+);
+$node->connect_ok(
+	"$common_connstr sslrootcert=ssl/root+server_ca.crt sslmode=require",
+	"connect with correct server CA cert file sslmode=require");
+
 # Test compatibility of SSL protocols.
 # TLSv1.1 is lower than TLSv1.2, so it won't work.
 $node->append_conf(
@@ -139,15 +214,6 @@ note "running client tests";
 
 switch_server_cert($node, certfile => 'server-cn-only');
 
-# Set of default settings for SSL parameters in connection string.  This
-# makes the tests protected against any defaults the environment may have
-# in ~/.postgresql/.
-my $default_ssl_connstr =
-  "sslkey=invalid sslcert=invalid sslrootcert=invalid sslcrl=invalid sslcrldir=invalid";
-
-$common_connstr =
-  "$default_ssl_connstr user=ssltestuser dbname=trustdb hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
-
 SKIP:
 {
 	skip "Keylogging is not supported with LibreSSL", 5 if $libressl;
diff --git a/src/test/ssl/t/SSL/Server.pm b/src/test/ssl/t/SSL/Server.pm
index efbd0dafaf6..a0a786c2ef2 100644
--- a/src/test/ssl/t/SSL/Server.pm
+++ b/src/test/ssl/t/SSL/Server.pm
@@ -296,6 +296,11 @@ The CRL directory to use. Implementation is SSL backend specific.
 The passphrase command to use. If not set, an empty passphrase command will
 be set.
 
+=item passphrase_cmd_reload => B<value>
+
+Whether or not to allow passphrase command reloading. If set the passphrase
+command reload configuration setting will be set to the value.
+
 =item restart => B<value>
 
 If set to 'no', the server won't be restarted after updating the settings.
@@ -315,7 +320,7 @@ sub switch_server_cert
 	my $pgdata = $node->data_dir;
 
 	ok(unlink($node->data_dir . '/sslconfig.conf'));
-	$node->append_conf('sslconfig.conf', "ssl=on");
+	$node->append_conf('sslconfig.conf', 'ssl=on');
 	$node->append_conf('sslconfig.conf', $backend->set_server_cert(\%params));
 	# use lists of ECDH curves and cipher suites for syntax testing
 	$node->append_conf('sslconfig.conf',
@@ -324,9 +329,14 @@ sub switch_server_cert
 		'ssl_tls13_ciphers=TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256');
 
 	$node->append_conf('sslconfig.conf',
-		"ssl_passphrase_command='" . $params{passphrase_cmd} . "'")
+		'ssl_passphrase_command=\'' . $params{passphrase_cmd} . '\'')
 	  if defined $params{passphrase_cmd};
 
+	$node->append_conf('sslconfig.conf',
+		'ssl_passphrase_command_supports_reload=\''
+		  . $params{passphrase_cmd_reload} . '\'')
+	  if defined $params{passphrase_cmd_reload};
+
 	return if (defined($params{restart}) && $params{restart} eq 'no');
 
 	$node->restart;
-- 
2.39.3 (Apple Git-146)

v2-0001-doc-Clarify-passphrase-command-reloading-on-Windo.patchapplication/octet-stream; name=v2-0001-doc-Clarify-passphrase-command-reloading-on-Windo.patch; x-unix-mode=0644Download
From 9cc13b3e18f2ab82c4b9cfc1505054f7990e50d8 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <dgustafsson@postgresql.org>
Date: Sun, 23 Nov 2025 20:48:46 +0100
Subject: [PATCH v2 1/3] doc: Clarify passphrase command reloading on Windows
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

When running on Windows (or EXEC_BACKEND) the SSL configuration will
be reloaded on each backend start, so the passphrase command will be
reloaded along with it.  This implies that passphrase command reload
must be enabled on Windows for connections to work at all.  Document
this since it wasn't mentioned explicitly, and will there add markup
for parameter value to match the rest of the docs.

Backpatch to all supported versions.

Author: Daniel Gustafsson <daniel@yesql.se>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Reviewed-by: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/5F301096-921A-427D-8EC1-EBAEC2A35082@yesql.se
Backpatch-through: 13
---
 doc/src/sgml/config.sgml | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 023b3f03ba9..07ff5873a97 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1680,7 +1680,7 @@ include_dir 'conf.d'
         This parameter determines whether the passphrase command set by
         <varname>ssl_passphrase_command</varname> will also be called during a
         configuration reload if a key file needs a passphrase.  If this
-        parameter is off (the default), then
+        parameter is <literal>off</literal> (the default), then
         <varname>ssl_passphrase_command</varname> will be ignored during a
         reload and the SSL configuration will not be reloaded if a passphrase
         is needed.  That setting is appropriate for a command that requires a
@@ -1688,6 +1688,12 @@ include_dir 'conf.d'
         running.  Setting this parameter to on might be appropriate if the
         passphrase is obtained from a file, for example.
        </para>
+       <para>
+        This parameter must be set to <literal>on</literal> when running on
+        <systemitem class="osname">Windows</systemitem> since all connections
+        will perform a configuration reload due to the different process model
+        of that platform.
+       </para>
        <para>
         This parameter can only be set in the <filename>postgresql.conf</filename>
         file or on the server command line.
-- 
2.39.3 (Apple Git-146)

#11Álvaro Herrera
alvherre@kurilemu.de
In reply to: Daniel Gustafsson (#10)
Re: Extended test coverage and docs for SSL passphrase commands

On 2025-Nov-23, Daniel Gustafsson wrote:

On 22 Nov 2025, at 14:00, Álvaro Herrera <alvherre@kurilemu.de> wrote:

I think the parallel to debug_assertions is not perfect, because you
can turn off debug_assertions in a server built with them included,
but you cannot turn off EXEC_BACKEND.

debug_assertions cannot be disabled anymore, since 3bdcf6a5a755.

Hah, 2014 -- goes to show that I don't try very often ;-) (I do have a
separate build tree for no-assertions, which I don't use very often
either.)

This says we shouldn't name the symbol with the DEFAULT word: it
should just be "EXEC_BACKEND_ENABLED".

Regardless, I totally agree with that, fixed in the attached along
with the review comments from upthread.

This looks reasonable to me, thanks.

--
Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/
"No nos atrevemos a muchas cosas porque son difíciles,
pero son difíciles porque no nos atrevemos a hacerlas" (Séneca)

#12Peter Eisentraut
peter@eisentraut.org
In reply to: Daniel Gustafsson (#10)
Re: Extended test coverage and docs for SSL passphrase commands

On 23.11.25 20:56, Daniel Gustafsson wrote:

On 22 Nov 2025, at 14:00, Álvaro Herrera <alvherre@kurilemu.de> wrote:

On 2025-Nov-21, Daniel Gustafsson wrote:

The attached v2 adds a GUC debug_exec_backend which can be used to get the
state of the running cluster,

Nice idea.

I think the parallel to debug_assertions is not perfect, because you can
turn off debug_assertions in a server built with them included, but you
cannot turn off EXEC_BACKEND.

debug_assertions cannot be disabled anymore, since 3bdcf6a5a755.

This says we shouldn't name the symbol
with the DEFAULT word: it should just be "EXEC_BACKEND_ENABLED".

Regardless, I totally agree with that, fixed in the attached along with the
review comments from upthread.

This looks good to me.