diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index f0167a64bc91c0517739c5556ea7cb1c45a14c78..124c21bed74b324efe48d31f2964f1397f029b5c 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -887,6 +887,42 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
     host will be tried in turn until a connection is successfully established.
    </para>
    </sect3>
+
+   <sect3 id="libpq-multiple-hosts">
+     <title>Specifying Multiple Hosts</title>
+
+     <para>
+       It is possible to specify multiple hosts to connect to, so that they are 
+       tried in the given order. In the Keyword/Value format, the <literal>host</>, 
+       <literal>hostaddr</>, and <literal>port</> options accept a comma-separated
+       list of values. The same number of elements must be given in each option, such
+       that e.g. the first <literal>hostaddr</> corresponds to the first host name,
+       the second <literal>hostaddr</> corresponds to the second host name, and so
+       forth. As an exception, if only one <literal>port</literal> is specified, it
+       applies to all the hosts.
+     </para>
+
+     <para>
+       In the connection URI format, you can list multiple <literal>host:port</> pairs 
+       separated by commas, in the <literal>host</> component of the URI. In either
+       format, a single hostname can also translate to multiple network addresses. A
+       common example of this is a host that has both an IPv4 and an IPv6 address.
+     </para>
+
+     <para>
+       When multiple hosts are specified, or when a single hostname is 
+       translated to multiple addresses,  all the hosts and addresses will be 
+       tried in order, until one succeeds. If none of the hosts can be reached, 
+       the connection fails. If a connection is established successfully, but 
+       authentication fails, the remaining hosts in the list are not tried.
+     </para>
+
+     <para>
+       If a password file is used, you can have different passwords for 
+       different hosts. All the other connection options are the same for every 
+       host, it is not possible to e.g. specify a different username for 
+       different hosts.
+     </para>
   </sect2>
 
   <sect2 id="libpq-paramkeywords">
@@ -900,7 +936,7 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
       <term><literal>host</literal></term>
       <listitem>
        <para>
-        Comma-separated list of host names.<indexterm><primary>host name</></>
+        Name of host to connect to.<indexterm><primary>host name</></>
         If a host name begins with a slash, it specifies Unix-domain
         communication rather than TCP/IP communication; the value is the
         name of the directory in which the socket file is stored.  If
@@ -912,6 +948,11 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
         when <productname>PostgreSQL</> was built). On machines without
         Unix-domain sockets, the default is to connect to <literal>localhost</>.
        </para>
+       <para>
+        A comma-separated list of host names is also accepted, in which case
+        each host name in the list is tried in order. See
+        <xref linkend="libpq-multiple-hosts"> for details.
+       </para>
       </listitem>
      </varlistentry>
 
@@ -965,6 +1006,11 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
         <xref linkend="libpq-pgpass">).
        </para>
 
+       <para>
+        A comma-separated list of <literal>hostaddrs</> is also accepted, in
+        which case each host in the list is tried in order. See
+        <xref linkend="libpq-multiple-hosts"> for details.
+       </para>
        <para>
         Without either a host name or host address,
         <application>libpq</application> will connect using a
@@ -981,9 +1027,10 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
         Port number to connect to at the server host, or socket file
         name extension for Unix-domain
         connections.<indexterm><primary>port</></>
-        If the <literal>host</> parameter included multiple, comma-separated
-        hosts, this parameter may specify a list of ports of equal length,
-        or it may specify a single port number to be used for all hosts.
+        If multiple hosts were given in the <literal>host</literal> or
+        <literal>hostaddr</> parameters, this parameter may specify a list
+        of ports of equal length, or it may specify a single port number to
+        be used for all hosts.
        </para>
       </listitem>
      </varlistentry>
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 2f7b4060df06a600931a2db74b36b10623412401..e548f3f06216659383654bb81256a7ae14eaaa3d 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -827,6 +827,62 @@ connectOptions1(PGconn *conn, const char *conninfo)
 	return true;
 }
 
+/*
+ * Count the number of elements in a simple comma-separated list.
+ */
+static int
+count_comma_separated_elems(const char *input)
+{
+	int			n;
+
+	n = 1;
+	for (; *input != '\0'; input++)
+	{
+		if (*input == ',')
+			n++;
+	}
+
+	return n;
+}
+
+/*
+ * Parse a simple comma-separated list.
+ *
+ * On each call, returns a malloc'd copy of the next element, and sets *more
+ * to indicate whether there are any more elements in the list after this,
+ * and updates *startptr to point to the next element, if any.
+ *
+ * On out of memory, returns NULL.
+ */
+static char *
+parse_comma_separated_list(char **startptr, bool *more)
+{
+	char	   *p;
+	char	   *s = *startptr;
+	char	   *e;
+	int			len;
+
+	/*
+	 * Search for the end of the current element; a comma or end-of-string
+	 * acts as a terminator.
+	 */
+	e = s;
+	while (*e != '\0' && *e != ',')
+		++e;
+	*more = (*e == ',');
+
+	len = e - s;
+	p = (char *) malloc(sizeof(char) * (len + 1));
+	if (p)
+	{
+		memcpy(p, s, len);
+		p[len] = '\0';
+	}
+	*startptr = e + 1;
+
+	return p;
+}
+
 /*
  *		connectOptions2
  *
@@ -840,21 +896,16 @@ connectOptions2(PGconn *conn)
 {
 	/*
 	 * Allocate memory for details about each host to which we might possibly
-	 * try to connect.  If pghostaddr is set, we're only going to try to
-	 * connect to that one particular address.  If it's not, we'll use pghost,
-	 * which may contain multiple, comma-separated names.
+	 * try to connect.  For that, count the number of elements in the hostaddr
+	 * or host options.  If neither is given, assume one host.
 	 */
-	conn->nconnhost = 1;
 	conn->whichhost = 0;
-	if ((conn->pghostaddr == NULL || conn->pghostaddr[0] == '\0')
-		&& conn->pghost != NULL)
-	{
-		char	   *s;
-
-		for (s = conn->pghost; *s != '\0'; ++s)
-			if (*s == ',')
-				conn->nconnhost++;
-	}
+	if (conn->pghostaddr && conn->pghostaddr[0] != '\0')
+		conn->nconnhost = count_comma_separated_elems(conn->pghostaddr);
+	else if (conn->pghost && conn->pghost[0] != '\0')
+		conn->nconnhost = count_comma_separated_elems(conn->pghost);
+	else
+		conn->nconnhost = 1;
 	conn->connhost = (pg_conn_host *)
 		calloc(conn->nconnhost, sizeof(pg_conn_host));
 	if (conn->connhost == NULL)
@@ -866,51 +917,67 @@ connectOptions2(PGconn *conn)
 	 */
 	if (conn->pghostaddr != NULL && conn->pghostaddr[0] != '\0')
 	{
-		conn->connhost[0].host = strdup(conn->pghostaddr);
-		if (conn->connhost[0].host == NULL)
-			goto oom_error;
-		conn->connhost[0].type = CHT_HOST_ADDRESS;
+		int			i;
+		char	   *s = conn->pghostaddr;
+		bool		more = true;
+
+		for (i = 0; i < conn->nconnhost && more; i++)
+		{
+			conn->connhost[i].hostaddr = parse_comma_separated_list(&s, &more);
+			if (conn->connhost[i].hostaddr == NULL)
+				goto oom_error;
+
+			conn->connhost[i].type = CHT_HOST_ADDRESS;
+		}
+
+		/*
+		 * If hostaddr was given, the array was allocated according to the
+		 * number of elements in the hostaddr list, so it really should be the
+		 * right size.
+		 */
+		Assert(!more);
+		Assert(i == conn->nconnhost);
 	}
-	else if (conn->pghost != NULL && conn->pghost[0] != '\0')
+
+	if (conn->pghost != NULL && conn->pghost[0] != '\0')
 	{
-		int			i = 0;
+		int			i;
 		char	   *s = conn->pghost;
+		bool		more = true;
 
-		while (1)
+		for (i = 0; i < conn->nconnhost && more; i++)
 		{
-			char	   *e = s;
-
-			/*
-			 * Search for the end of the current hostname; a comma or
-			 * end-of-string acts as a terminator.
-			 */
-			while (*e != '\0' && *e != ',')
-				++e;
-
-			/* Copy the hostname whose bounds we just identified. */
-			conn->connhost[i].host =
-				(char *) malloc(sizeof(char) * (e - s + 1));
+			conn->connhost[i].host = parse_comma_separated_list(&s, &more);
 			if (conn->connhost[i].host == NULL)
 				goto oom_error;
-			memcpy(conn->connhost[i].host, s, e - s);
-			conn->connhost[i].host[e - s] = '\0';
 
 			/* Identify the type of host. */
-			conn->connhost[i].type = CHT_HOST_NAME;
+			if (conn->pghostaddr == NULL || conn->pghostaddr[0] == '\0')
+			{
+				conn->connhost[i].type = CHT_HOST_NAME;
 #ifdef HAVE_UNIX_SOCKETS
-			if (is_absolute_path(conn->connhost[i].host))
-				conn->connhost[i].type = CHT_UNIX_SOCKET;
+				if (is_absolute_path(conn->connhost[i].host))
+					conn->connhost[i].type = CHT_UNIX_SOCKET;
 #endif
-
-			/* Prepare to find the next host (if any). */
-			if (*e == '\0')
-				break;
-			s = e + 1;
-			i++;
+			}
+		}
+		if (more || i != conn->nconnhost)
+		{
+			conn->status = CONNECTION_BAD;
+			printfPQExpBuffer(&conn->errorMessage,
+			libpq_gettext("could not match %d host names to %d hostaddrs\n"),
+				 count_comma_separated_elems(conn->pghost), conn->nconnhost);
+			return false;
 		}
 	}
-	else
+
+	/*
+	 * If neither host or hostaddr options was given, connect to default host.
+	 */
+	if ((conn->pghostaddr == NULL || conn->pghostaddr[0] == '\0') &&
+		(conn->pghost == NULL || conn->pghost[0] == '\0'))
 	{
+		Assert(conn->nconnhost == 1);
 #ifdef HAVE_UNIX_SOCKETS
 		conn->connhost[0].host = strdup(DEFAULT_PGSOCKET_DIR);
 		conn->connhost[0].type = CHT_UNIX_SOCKET;
@@ -927,54 +994,36 @@ connectOptions2(PGconn *conn)
 	 */
 	if (conn->pgport != NULL && conn->pgport[0] != '\0')
 	{
-		int			i = 0;
+		int			i;
 		char	   *s = conn->pgport;
-		int			nports = 1;
+		bool		more = true;
 
-		for (i = 0; i < conn->nconnhost; ++i)
+		for (i = 0; i < conn->nconnhost && more; i++)
 		{
-			char	   *e = s;
-
-			/* Search for the end of the current port number. */
-			while (*e != '\0' && *e != ',')
-				++e;
+			conn->connhost[i].port = parse_comma_separated_list(&s, &more);
+			if (conn->connhost[i].port == NULL)
+				goto oom_error;
+		}
 
-			/*
-			 * If we found a port number of non-zero length, copy it.
-			 * Otherwise, insert the default port number.
-			 */
-			if (e > s)
+		/*
+		 * If exactly one port was given, use it for every host.  Otherwise,
+		 * there must be exactly as many ports as there were hosts.
+		 */
+		if (i == 1 && !more)
+		{
+			for (i = 1; i < conn->nconnhost; i++)
 			{
-				conn->connhost[i].port =
-					(char *) malloc(sizeof(char) * (e - s + 1));
+				conn->connhost[i].port = strdup(conn->connhost[0].port);
 				if (conn->connhost[i].port == NULL)
 					goto oom_error;
-				memcpy(conn->connhost[i].port, s, e - s);
-				conn->connhost[i].port[e - s] = '\0';
-			}
-
-			/*
-			 * Move on to the next port number, unless there are no more. (If
-			 * only one part number is specified, we reuse it for every host.)
-			 */
-			if (*e != '\0')
-			{
-				s = e + 1;
-				++nports;
 			}
 		}
-
-		/*
-		 * If multiple ports were specified, there must be exactly as many
-		 * ports as there were hosts.  Otherwise, we do not know how to match
-		 * them up.
-		 */
-		if (nports != 1 && nports != conn->nconnhost)
+		else if (more || i != conn->nconnhost)
 		{
 			conn->status = CONNECTION_BAD;
 			printfPQExpBuffer(&conn->errorMessage,
 							  libpq_gettext("could not match %d port numbers to %d hosts\n"),
-							  nports, conn->nconnhost);
+							  count_comma_separated_elems(conn->pgport), conn->nconnhost);
 			return false;
 		}
 	}
@@ -1048,8 +1097,8 @@ connectOptions2(PGconn *conn)
 			char	   *pwhost = conn->connhost[i].host;
 
 			if (conn->connhost[i].type == CHT_HOST_ADDRESS &&
-				conn->pghost != NULL && conn->pghost[0] != '\0')
-				pwhost = conn->pghost;
+			conn->connhost[i].host != NULL && conn->connhost[i].host != '\0')
+				pwhost = conn->connhost[i].hostaddr;
 
 			conn->connhost[i].password =
 				passwordFromFile(pwhost,
@@ -1399,8 +1448,8 @@ connectFailureMessage(PGconn *conn, int errorno)
 		 * Optionally display the network address with the hostname. This is
 		 * useful to distinguish between IPv4 and IPv6 connections.
 		 */
-		if (conn->pghostaddr != NULL)
-			strlcpy(host_addr, conn->pghostaddr, NI_MAXHOST);
+		if (conn->connhost[conn->whichhost].type == CHT_HOST_ADDRESS)
+			strlcpy(host_addr, conn->connhost[conn->whichhost].hostaddr, NI_MAXHOST);
 		else if (addr->ss_family == AF_INET)
 		{
 			if (inet_net_ntop(AF_INET,
@@ -1423,7 +1472,10 @@ connectFailureMessage(PGconn *conn, int errorno)
 			strcpy(host_addr, "???");
 
 		/* To which host and port were we actually connecting? */
-		displayed_host = conn->connhost[conn->whichhost].host;
+		if (conn->connhost[conn->whichhost].type == CHT_HOST_ADDRESS)
+			displayed_host = conn->connhost[conn->whichhost].hostaddr;
+		else
+			displayed_host = conn->connhost[conn->whichhost].host;
 		displayed_port = conn->connhost[conn->whichhost].port;
 		if (displayed_port == NULL || displayed_port[0] == '\0')
 			displayed_port = DEF_PGPORT_STR;
@@ -1433,8 +1485,8 @@ connectFailureMessage(PGconn *conn, int errorno)
 		 * 'host' was missing or does not match our lookup, display the
 		 * looked-up IP address.
 		 */
-		if ((conn->pghostaddr == NULL) &&
-			(conn->pghost == NULL || strcmp(conn->pghost, host_addr) != 0))
+		if (conn->connhost[conn->whichhost].type != CHT_HOST_ADDRESS &&
+			strcmp(displayed_host, host_addr) != 0)
 			appendPQExpBuffer(&conn->errorMessage,
 							  libpq_gettext("could not connect to server: %s\n"
 											"\tIs the server running on host \"%s\" (%s) and accepting\n"
@@ -1659,7 +1711,7 @@ connectDBStart(PGconn *conn)
 		hint.ai_family = AF_UNSPEC;
 
 		/* Figure out the port number we're going to use. */
-		if (ch->port == NULL)
+		if (ch->port == NULL || ch->port[0] == '\0')
 			thisport = DEF_PGPORT;
 		else
 		{
@@ -1689,7 +1741,7 @@ connectDBStart(PGconn *conn)
 
 			case CHT_HOST_ADDRESS:
 				hint.ai_flags = AI_NUMERICHOST;
-				ret = pg_getaddrinfo_all(ch->host, portstr, &hint, &ch->addrlist);
+				ret = pg_getaddrinfo_all(ch->hostaddr, portstr, &hint, &ch->addrlist);
 				if (ret || !ch->addrlist)
 					appendPQExpBuffer(&conn->errorMessage,
 									  libpq_gettext("could not parse network address \"%s\": %s\n"),
@@ -3041,6 +3093,9 @@ keep_going:						/* We will come back to here until there is
 			}
 		case CONNECTION_CHECK_WRITABLE:
 			{
+				const char *displayed_host;
+				const char *displayed_port;
+
 				if (!saveErrorMessage(conn, &savedMessage))
 					goto error_return;
 
@@ -3067,6 +3122,17 @@ keep_going:						/* We will come back to here until there is
 					val = PQgetvalue(res, 0, 0);
 					if (strncmp(val, "on", 2) == 0)
 					{
+						const char *displayed_host;
+						const char *displayed_port;
+
+						if (conn->connhost[conn->whichhost].type == CHT_HOST_ADDRESS)
+							displayed_host = conn->connhost[conn->whichhost].hostaddr;
+						else
+							displayed_host = conn->connhost[conn->whichhost].host;
+						displayed_port = conn->connhost[conn->whichhost].port;
+						if (displayed_port == NULL || displayed_port[0] == '\0')
+							displayed_port = DEF_PGPORT_STR;
+
 						PQclear(res);
 						restoreErrorMessage(conn, &savedMessage);
 
@@ -3075,8 +3141,7 @@ keep_going:						/* We will come back to here until there is
 										  libpq_gettext("could not make a writable "
 														"connection to server "
 														"\"%s:%s\"\n"),
-										  conn->connhost[conn->whichhost].host,
-										  conn->connhost[conn->whichhost].port);
+										  displayed_host, displayed_port);
 						conn->status = CONNECTION_OK;
 						sendTerminateConn(conn);
 						pqDropConnection(conn, true);
@@ -3113,11 +3178,18 @@ keep_going:						/* We will come back to here until there is
 				if (res)
 					PQclear(res);
 				restoreErrorMessage(conn, &savedMessage);
+
+				if (conn->connhost[conn->whichhost].type == CHT_HOST_ADDRESS)
+					displayed_host = conn->connhost[conn->whichhost].hostaddr;
+				else
+					displayed_host = conn->connhost[conn->whichhost].host;
+				displayed_port = conn->connhost[conn->whichhost].port;
+				if (displayed_port == NULL || displayed_port[0] == '\0')
+					displayed_port = DEF_PGPORT_STR;
 				appendPQExpBuffer(&conn->errorMessage,
 								  libpq_gettext("test \"SHOW transaction_read_only\" failed "
 												"on server \"%s:%s\"\n"),
-								  conn->connhost[conn->whichhost].host,
-								  conn->connhost[conn->whichhost].port);
+								  displayed_host, displayed_port);
 				conn->status = CONNECTION_OK;
 				sendTerminateConn(conn);
 				pqDropConnection(conn, true);
@@ -3350,6 +3422,8 @@ freePGconn(PGconn *conn)
 		{
 			if (conn->connhost[i].host != NULL)
 				free(conn->connhost[i].host);
+			if (conn->connhost[i].hostaddr != NULL)
+				free(conn->connhost[i].hostaddr);
 			if (conn->connhost[i].port != NULL)
 				free(conn->connhost[i].port);
 			if (conn->connhost[i].password != NULL)
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index ff5020fc0c55e895643e951aadbc1cab6b62667b..42913604e3969c8ece18e2160f0242e97ebace5d 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -304,8 +304,9 @@ typedef enum pg_conn_host_type
  */
 typedef struct pg_conn_host
 {
-	char	   *host;			/* host name or address, or socket path */
 	pg_conn_host_type type;		/* type of host */
+	char	   *host;			/* host name or socket path */
+	char	   *hostaddr;		/* host address */
 	char	   *port;			/* port number for this host; if not NULL,
 								 * overrides the PGConn's pgport */
 	char	   *password;		/* password for this host, read from the