# src/backend/libpq/be-secure-openssl.c
# sni_clienthello_cb() chose between 'SNI off: use default_host unconditionally' and 'SNI on: match the
#   pg_hosts.conf table' by reading the live ssl_sni GUC, while the table it operates on (SSL_hosts) is only
#   replaced when be_tls_init() succeeds; a SIGHUP that flips ssl_sni on->off but then fails to load the
#   postgresql.conf certificates leaves ssl_sni=false paired with a pg_hosts.conf table whose default_host is
#   legitimately NULL, so every subsequent ClientHello dereferences NULL in ssl_update_ssl(). The fix records
#   the ssl_sni value in struct hosts when the tentative table is built in be_tls_init() (sni_enabled) and
#   makes the callback consult that instead of the GUC, so the selection logic and the table it selects from
#   can never disagree; this is the place the invariant ('SNI-off tables always have default_host') is
#   established, and the callback now asserts it. I checked the other ssl_sni uses (init_host_context's
#   init-hook handling and the load-path branching) — they run at build time inside be_tls_init() and are
#   therefore consistent with the table by construction; the LibreSSL path (no client-hello callback) can only
#   have ssl_sni off via the check hook and is unaffected. The patch also adds a TAP case to
#   src/test/ssl/t/004_sni.pl (requires PG_TEST_EXTRA=ssl) that performs exactly this failed reload and checks
#   the handshakes still behave as before; it fails with SIGSEGV on the unpatched tree and passes with the
#   fix, and the full ssl suite passes. Behaviour change: after a failed reload the server keeps serving with
#   the previously loaded SNI mode (matching the 'SSL configuration was not reloaded' message) instead of
#   half-applying the new ssl_sni value; pgindent reports no changes for the C file.
# reviewer: good — Fixes the root cause (decision keyed to the live GUC instead of the installed table) by
#   snapshotting ssl_sni into struct hosts at build time; complete (the only connection-time reader of
#   ssl_sni), preserves 'old config stays in effect' semantics, adds a TAP case; applied here it stops the
#   crash, control behaviour unchanged, ssl/001 and ssl/004 (106 subtests incl. the 4 new ones) pass.
# our check: FIXED · confidence high
#
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index b7ded8a0250..fda00334151 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -125,6 +125,14 @@ static struct hosts
 	 * matches the supplied hostname in the SNI extension.
 	 */
 	HostsLine  *default_host;
+
+	/*
+	 * Whether the configuration was loaded with ssl_sni enabled.  The ssl_sni
+	 * GUC can change on reload without the configuration being replaced, in
+	 * case loading the new configuration fails, so connection handling must
+	 * consult this rather than the GUC.
+	 */
+	bool		sni_enabled;
 }		   *SSL_hosts;
 
 static bool dummy_ssl_passwd_cb_called = false;
@@ -177,6 +185,7 @@ be_tls_init(bool isServerStart)
 
 	/* Allocate a tentative replacement for SSL_hosts. */
 	new_hosts = palloc0_object(struct hosts);
+	new_hosts->sni_enabled = ssl_sni;
 
 	/*
 	 * Register a reset callback for the memory context which is responsible
@@ -1934,9 +1943,16 @@ sni_clienthello_cb(SSL *ssl, int *al, void *arg)
 				len;
 	HostsLine  *install_config = NULL;
 
-	if (!ssl_sni)
+	/*
+	 * Use the SNI setting which the installed configuration was loaded with
+	 * rather than the current value of the ssl_sni GUC, since the two can
+	 * disagree after a failed reload.  A configuration loaded with SNI
+	 * disabled always has a default host.
+	 */
+	if (!SSL_hosts->sni_enabled)
 	{
 		install_config = SSL_hosts->default_host;
+		Assert(install_config != NULL);
 		goto found;
 	}
 
diff --git a/src/test/ssl/t/004_sni.pl b/src/test/ssl/t/004_sni.pl
index f1a135bb3c8..1c0e19c404a 100644
--- a/src/test/ssl/t/004_sni.pl
+++ b/src/test/ssl/t/004_sni.pl
@@ -263,6 +263,26 @@ $node->connect_fails(
 	"pg_hosts.conf: connect to 'example' with sslmode=require",
 	expected_stderr => qr/unrecognized name/);
 
+# Turn off SNI while the postgresql.conf configuration cannot be loaded, such
+# that the reload fails to replace the SSL configuration.  The pg_hosts.conf
+# configuration without a default host must remain in effect and connections
+# must behave as before the reload.
+my $log_offset = -s $node->logfile;
+$node->append_conf('postgresql.conf',
+	"ssl_sni = off\nssl_cert_file = 'nonexistent.crt'");
+$node->reload;
+$node->wait_for_log(qr/SSL configuration was not reloaded/, $log_offset);
+$node->connect_ok(
+	"$connstr sslrootcert=ssl/root+server_ca.crt sslmode=require host=example.org",
+	"pg_hosts.conf: connect to example.org after failed reload with ssl_sni off"
+);
+$node->connect_fails(
+	"$connstr sslrootcert=ssl/root+server_ca.crt sslmode=require sslsni=0",
+	"pg_hosts.conf: connect to default after failed reload with ssl_sni off",
+	expected_stderr => qr/handshake failure/);
+$node->append_conf('postgresql.conf',
+	"ssl_sni = on\nssl_cert_file = 'server-cn-only.crt'");
+
 # Reconfigure with broken configuration for the key passphrase, the server
 # should not start up
 ok(unlink($node->data_dir . '/pg_hosts.conf'));
