diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 882dc8faf1b63161cdc353c31fb507a934eaeeb2..9d0c3893c8d565a7ed06f8aea31b6ad0ced95337 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -38,9 +38,9 @@
  *----------------------------------------------------------------
  */
 static void sendAuthRequest(Port *port, AuthRequest areq);
-static void auth_failed(Port *port, int status);
+static void auth_failed(Port *port, int status, char *logdetail);
 static char *recv_password_packet(Port *port);
-static int	recv_and_check_password_packet(Port *port);
+static int	recv_and_check_password_packet(Port *port, char **logdetail);
 
 
 /*----------------------------------------------------------------
@@ -207,10 +207,11 @@ ClientAuthentication_hook_type ClientAuthentication_hook = NULL;
  * in use, and these are items that must be presumed known to an attacker
  * anyway.
  * Note that many sorts of failure report additional information in the
- * postmaster log, which we hope is only readable by good guys.
+ * postmaster log, which we hope is only readable by good guys.  In
+ * particular, if logdetail isn't NULL, we send that string to the log.
  */
 static void
-auth_failed(Port *port, int status)
+auth_failed(Port *port, int status, char *logdetail)
 {
 	const char *errstr;
 	int			errcode_return = ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION;
@@ -273,14 +274,21 @@ auth_failed(Port *port, int status)
 	}
 
 	if (port->hba)
-		ereport(FATAL,
-				(errcode(errcode_return),
-				 errmsg(errstr, port->user_name),
-				 errdetail_log("Connection matched pg_hba.conf line %d: \"%s\"", port->hba->linenumber, port->hba->rawline)));
-	else
-		ereport(FATAL,
-				(errcode(errcode_return),
-				 errmsg(errstr, port->user_name)));
+	{
+		char	   *cdetail;
+
+		cdetail = psprintf(_("Connection matched pg_hba.conf line %d: \"%s\""),
+						   port->hba->linenumber, port->hba->rawline);
+		if (logdetail)
+			logdetail = psprintf("%s\n%s", logdetail, cdetail);
+		else
+			logdetail = cdetail;
+	}
+
+	ereport(FATAL,
+			(errcode(errcode_return),
+			 errmsg(errstr, port->user_name),
+			 logdetail ? errdetail_log("%s", logdetail) : 0));
 
 	/* doesn't return */
 }
@@ -294,6 +302,7 @@ void
 ClientAuthentication(Port *port)
 {
 	int			status = STATUS_ERROR;
+	char	   *logdetail = NULL;
 
 	/*
 	 * Get the authentication method to use for this frontend/database
@@ -507,12 +516,12 @@ ClientAuthentication(Port *port)
 						(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
 						 errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled")));
 			sendAuthRequest(port, AUTH_REQ_MD5);
-			status = recv_and_check_password_packet(port);
+			status = recv_and_check_password_packet(port, &logdetail);
 			break;
 
 		case uaPassword:
 			sendAuthRequest(port, AUTH_REQ_PASSWORD);
-			status = recv_and_check_password_packet(port);
+			status = recv_and_check_password_packet(port, &logdetail);
 			break;
 
 		case uaPAM:
@@ -552,7 +561,7 @@ ClientAuthentication(Port *port)
 	if (status == STATUS_OK)
 		sendAuthRequest(port, AUTH_REQ_OK);
 	else
-		auth_failed(port, status);
+		auth_failed(port, status, logdetail);
 
 	/* Done with authentication, so we should turn off immediate interrupts */
 	ImmediateInterruptOK = false;
@@ -680,9 +689,10 @@ recv_password_packet(Port *port)
 /*
  * Called when we have sent an authorization request for a password.
  * Get the response and check it.
+ * On error, optionally store a detail string at *logdetail.
  */
 static int
-recv_and_check_password_packet(Port *port)
+recv_and_check_password_packet(Port *port, char **logdetail)
 {
 	char	   *passwd;
 	int			result;
@@ -692,7 +702,7 @@ recv_and_check_password_packet(Port *port)
 	if (passwd == NULL)
 		return STATUS_EOF;		/* client wouldn't send password */
 
-	result = md5_crypt_verify(port, port->user_name, passwd);
+	result = md5_crypt_verify(port, port->user_name, passwd, logdetail);
 
 	pfree(passwd);
 
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 56b3ea8a21b0cba08998dec3b67114db67522e20..5451db6d97480c275d46b00c34452581ccb921b0 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -29,8 +29,14 @@
 #include "utils/timestamp.h"
 
 
+/*
+ * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
+ * In the error case, optionally store a palloc'd string at *logdetail
+ * that will be sent to the postmaster log (but not the client).
+ */
 int
-md5_crypt_verify(const Port *port, const char *role, char *client_pass)
+md5_crypt_verify(const Port *port, const char *role, char *client_pass,
+				 char **logdetail)
 {
 	int			retval = STATUS_ERROR;
 	char	   *shadow_pass,
@@ -58,6 +64,8 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass)
 	if (isnull)
 	{
 		ReleaseSysCache(roleTup);
+		*logdetail = psprintf(_("User \"%s\" has no password assigned."),
+							  role);
 		return STATUS_ERROR;	/* user has no password */
 	}
 	shadow_pass = TextDatumGetCString(datum);
@@ -148,7 +156,11 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass)
 		if (isnull)
 			retval = STATUS_OK;
 		else if (vuntil < GetCurrentTimestamp())
+		{
+			*logdetail = psprintf(_("User \"%s\" has an expired password."),
+								  role);
 			retval = STATUS_ERROR;
+		}
 		else
 			retval = STATUS_OK;
 	}
diff --git a/src/include/libpq/crypt.h b/src/include/libpq/crypt.h
index 818e57eaee77c53e0705bd7ded397972af2b762b..b91024f86cda9879ebcea52ef62ff95d8144b9e8 100644
--- a/src/include/libpq/crypt.h
+++ b/src/include/libpq/crypt.h
@@ -15,7 +15,7 @@
 
 #include "libpq/libpq-be.h"
 
-extern int md5_crypt_verify(const Port *port, const char *user,
-				 char *client_pass);
+extern int md5_crypt_verify(const Port *port, const char *role,
+				 char *client_pass, char **logdetail);
 
 #endif