diff --git a/src/network/core/tcp_coordinator.cpp b/src/network/core/tcp_coordinator.cpp
index 1cf5cd676f..bbcb59b14d 100644
--- a/src/network/core/tcp_coordinator.cpp
+++ b/src/network/core/tcp_coordinator.cpp
@@ -31,6 +31,8 @@ bool NetworkCoordinatorSocketHandler::HandlePacket(Packet *p)
 		case PACKET_COORDINATOR_SERVER_REGISTER: return this->Receive_SERVER_REGISTER(p);
 		case PACKET_COORDINATOR_GC_REGISTER_ACK: return this->Receive_GC_REGISTER_ACK(p);
 		case PACKET_COORDINATOR_SERVER_UPDATE:   return this->Receive_SERVER_UPDATE(p);
+		case PACKET_COORDINATOR_CLIENT_LISTING:  return this->Receive_CLIENT_LISTING(p);
+		case PACKET_COORDINATOR_GC_LISTING:      return this->Receive_GC_LISTING(p);
 
 		default:
 			Debug(net, 0, "[tcp/coordinator] Received invalid packet type {}", type);
@@ -78,3 +80,5 @@ bool NetworkCoordinatorSocketHandler::Receive_GC_ERROR(Packet *p) { return this-
 bool NetworkCoordinatorSocketHandler::Receive_SERVER_REGISTER(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_SERVER_REGISTER); }
 bool NetworkCoordinatorSocketHandler::Receive_GC_REGISTER_ACK(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_REGISTER_ACK); }
 bool NetworkCoordinatorSocketHandler::Receive_SERVER_UPDATE(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_SERVER_UPDATE); }
+bool NetworkCoordinatorSocketHandler::Receive_CLIENT_LISTING(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_CLIENT_LISTING); }
+bool NetworkCoordinatorSocketHandler::Receive_GC_LISTING(Packet *p) { return this->ReceiveInvalidPacket(PACKET_COORDINATOR_GC_LISTING); }
diff --git a/src/network/core/tcp_coordinator.h b/src/network/core/tcp_coordinator.h
index a438f07aa5..e95916816f 100644
--- a/src/network/core/tcp_coordinator.h
+++ b/src/network/core/tcp_coordinator.h
@@ -29,6 +29,8 @@ enum PacketCoordinatorType {
 	PACKET_COORDINATOR_SERVER_REGISTER, ///< Server registration.
 	PACKET_COORDINATOR_GC_REGISTER_ACK, ///< Game Coordinator accepts the registration.
 	PACKET_COORDINATOR_SERVER_UPDATE,   ///< Server sends an set intervals an update of the server.
+	PACKET_COORDINATOR_CLIENT_LISTING,  ///< Client is requesting a listing of all public servers.
+	PACKET_COORDINATOR_GC_LISTING,      ///< Game Coordinator returns a listing of all public servers.
 	PACKET_COORDINATOR_END,             ///< Must ALWAYS be on the end of this list!! (period).
 };
 
@@ -101,6 +103,33 @@ protected:
 	 */
 	virtual bool Receive_SERVER_UPDATE(Packet *p);
 
+	/**
+	 * Client requests a list of all public servers.
+	 *
+	 *  uint8   Game Coordinator protocol version.
+	 *  uint8   Game-info version used by this client.
+	 *  string  Revision of the client.
+	 *
+	 * @param p The packet that was just received.
+	 * @return True upon success, otherwise false.
+	 */
+	virtual bool Receive_CLIENT_LISTING(Packet *p);
+
+	/**
+	 * Game Coordinator replies with a list of all public servers. Multiple
+	 * of these packets are received after a request till all servers are
+	 * sent over. Last packet will have server count of 0.
+	 *
+	 *  uint16  Amount of public servers in this packet.
+	 *  For each server:
+	 *    string  Connection string for this server.
+	 *    Serialized NetworkGameInfo. See game_info.hpp for details.
+	 *
+	 * @param p The packet that was just received.
+	 * @return True upon success, otherwise false.
+	 */
+	virtual bool Receive_GC_LISTING(Packet *p);
+
 	bool HandlePacket(Packet *p);
 public:
 	/**
diff --git a/src/network/network_coordinator.cpp b/src/network/network_coordinator.cpp
index 3e40797407..b9299d549f 100644
--- a/src/network/network_coordinator.cpp
+++ b/src/network/network_coordinator.cpp
@@ -19,6 +19,7 @@
 #include "network.h"
 #include "network_coordinator.h"
 #include "network_gamelist.h"
+#include "network_internal.h"
 #include "table/strings.h"
 
 #include "../safeguards.h"
@@ -47,6 +48,7 @@ public:
 		assert(_network_coordinator_client.sock == INVALID_SOCKET);
 
 		_network_coordinator_client.sock = s;
+		_network_coordinator_client.last_activity = std::chrono::steady_clock::now();
 		_network_coordinator_client.connecting = false;
 	}
 };
@@ -111,6 +113,42 @@ bool ClientNetworkCoordinatorSocketHandler::Receive_GC_REGISTER_ACK(Packet *p)
 	return true;
 }
 
+bool ClientNetworkCoordinatorSocketHandler::Receive_GC_LISTING(Packet *p)
+{
+	uint8 servers = p->Recv_uint16();
+
+	/* End of list; we can now remove all expired items from the list. */
+	if (servers == 0) {
+		NetworkGameListRemoveExpired();
+		return true;
+	}
+
+	for (; servers > 0; servers--) {
+		std::string connection_string = p->Recv_string(NETWORK_HOSTNAME_PORT_LENGTH);
+
+		/* Read the NetworkGameInfo from the packet. */
+		NetworkGameInfo ngi = {};
+		DeserializeNetworkGameInfo(p, &ngi);
+
+		/* Now we know the join-key, we can add it to our list. */
+		NetworkGameList *item = NetworkGameListAddItem(connection_string);
+
+		/* Clear any existing GRFConfig chain. */
+		ClearGRFConfigList(&item->info.grfconfig);
+		/* Copy the new NetworkGameInfo info. */
+		item->info = ngi;
+		/* Check for compatability with the client. */
+		CheckGameCompatibility(item->info);
+		/* Mark server as online. */
+		item->online = true;
+		/* Mark the item as up-to-date. */
+		item->version = _network_game_list_version;
+	}
+
+	UpdateNetworkGameWindow();
+	return true;
+}
+
 void ClientNetworkCoordinatorSocketHandler::Connect()
 {
 	/* We are either already connected or are trying to connect. */
@@ -119,6 +157,8 @@ void ClientNetworkCoordinatorSocketHandler::Connect()
 	this->Reopen();
 
 	this->connecting = true;
+	this->last_activity = std::chrono::steady_clock::now();
+
 	new NetworkCoordinatorConnecter(NETWORK_COORDINATOR_SERVER_HOST);
 }
 
@@ -172,6 +212,23 @@ void ClientNetworkCoordinatorSocketHandler::SendServerUpdate()
 	this->SendPacket(p);
 }
 
+/**
+ * Request a listing of all public servers.
+ */
+void ClientNetworkCoordinatorSocketHandler::GetListing()
+{
+	this->Connect();
+
+	_network_game_list_version++;
+
+	Packet *p = new Packet(PACKET_COORDINATOR_CLIENT_LISTING);
+	p->Send_uint8(NETWORK_COORDINATOR_VERSION);
+	p->Send_uint8(NETWORK_GAME_INFO_VERSION);
+	p->Send_string(_openttd_revision);
+
+	this->SendPacket(p);
+}
+
 /**
  * Check whether we received/can send some data from/to the Game Coordinator server and
  * when that's the case handle it appropriately.
@@ -226,8 +283,15 @@ void ClientNetworkCoordinatorSocketHandler::SendReceive()
 		this->SendServerUpdate();
 	}
 
+	if (!_network_server && std::chrono::steady_clock::now() > this->last_activity + IDLE_TIMEOUT) {
+		this->CloseConnection();
+		return;
+	}
+
 	if (this->CanSendReceive()) {
-		this->ReceivePackets();
+		if (this->ReceivePackets()) {
+			this->last_activity = std::chrono::steady_clock::now();
+		}
 	}
 
 	this->SendPackets();
diff --git a/src/network/network_coordinator.h b/src/network/network_coordinator.h
index 1dbce063b5..7399ce1fc1 100644
--- a/src/network/network_coordinator.h
+++ b/src/network/network_coordinator.h
@@ -21,6 +21,10 @@
  *  - Game Coordinator probes server to check if it can directly connect.
  *  - Game Coordinator sends GC_REGISTER_ACK with type of connection.
  *  - Server sends every 30 seconds SERVER_UPDATE.
+ *
+ * For clients (listing):
+ *  - Client sends CLIENT_LISTING.
+ *  - Game Coordinator returns the full list of public servers via GC_LISTING (multiple packets).
  */
 
 /** Class for handling the client side of the Game Coordinator connection. */
@@ -31,8 +35,13 @@ private:
 protected:
 	bool Receive_GC_ERROR(Packet *p) override;
 	bool Receive_GC_REGISTER_ACK(Packet *p) override;
+	bool Receive_GC_LISTING(Packet *p) override;
 
 public:
+	/** The idle timeout; when to close the connection because it's idle. */
+	static constexpr std::chrono::seconds IDLE_TIMEOUT = std::chrono::seconds(60);
+
+	std::chrono::steady_clock::time_point last_activity;  ///< The last time there was network activity.
 	bool connecting; ///< Are we connecting to the Game Coordinator?
 
 	ClientNetworkCoordinatorSocketHandler() : connecting(false) {}
@@ -44,6 +53,7 @@ public:
 
 	void Register();
 	void SendServerUpdate();
+	void GetListing();
 };
 
 extern ClientNetworkCoordinatorSocketHandler _network_coordinator_client;
diff --git a/src/network/network_gui.cpp b/src/network/network_gui.cpp
index 6f48bf022c..24884ed165 100644
--- a/src/network/network_gui.cpp
+++ b/src/network/network_gui.cpp
@@ -18,6 +18,7 @@
 #include "network_base.h"
 #include "network_content.h"
 #include "network_server.h"
+#include "network_coordinator.h"
 #include "../gui.h"
 #include "network_udp.h"
 #include "../window_func.h"
@@ -54,6 +55,8 @@
 static void ShowNetworkStartServerWindow();
 static void ShowNetworkLobbyWindow(NetworkGameList *ngl);
 
+static const int NETWORK_LIST_REFRESH_DELAY = 30; ///< Time, in seconds, between updates of the network list.
+
 static ClientID _admin_client_id = INVALID_CLIENT_ID; ///< For what client a confirmation window is open.
 static CompanyID _admin_company_id = INVALID_COMPANY; ///< For what company a confirmation window is open.
 
@@ -219,14 +222,15 @@ protected:
 	static GUIGameServerList::SortFunction * const sorter_funcs[];
 	static GUIGameServerList::FilterFunction * const filter_funcs[];
 
-	NetworkGameList *server;      ///< selected server
-	NetworkGameList *last_joined; ///< the last joined server
-	GUIGameServerList servers;    ///< list with game servers.
-	ServerListPosition list_pos;  ///< position of the selected server
-	Scrollbar *vscroll;           ///< vertical scrollbar of the list of servers
-	QueryString name_editbox;     ///< Client name editbox.
-	QueryString filter_editbox;   ///< Editbox for filter on servers
-	GUITimer requery_timer;       ///< Timer for network requery
+	NetworkGameList *server;        ///< Selected server.
+	NetworkGameList *last_joined;   ///< The last joined server.
+	GUIGameServerList servers;      ///< List with game servers.
+	ServerListPosition list_pos;    ///< Position of the selected server.
+	Scrollbar *vscroll;             ///< Vertical scrollbar of the list of servers.
+	QueryString name_editbox;       ///< Client name editbox.
+	QueryString filter_editbox;     ///< Editbox for filter on servers.
+	GUITimer requery_timer;         ///< Timer for network requery.
+	bool searched_internet = false; ///< Did we ever press "Search Internet" button?
 
 	int lock_offset; ///< Left offset for lock icon.
 	int blot_offset; ///< Left offset for green/yellow/red compatibility icon.
@@ -244,8 +248,18 @@ protected:
 		/* Create temporary array of games to use for listing */
 		this->servers.clear();
 
+		bool found_current_server = false;
 		for (NetworkGameList *ngl = _network_game_list; ngl != nullptr; ngl = ngl->next) {
 			this->servers.push_back(ngl);
+			if (ngl == this->server) {
+				found_current_server = true;
+			}
+		}
+		/* A refresh can cause the current server to be delete; so unselect. */
+		if (!found_current_server) {
+			if (this->server == this->last_joined) this->last_joined = nullptr;
+			this->server = nullptr;
+			this->list_pos = SLP_INVALID;
 		}
 
 		/* Apply the filter condition immediately, if a search string has been provided. */
@@ -479,7 +493,7 @@ public:
 		this->last_joined = NetworkAddServer(_settings_client.network.last_joined, false);
 		this->server = this->last_joined;
 
-		this->requery_timer.SetInterval(MILLISECONDS_PER_TICK);
+		this->requery_timer.SetInterval(NETWORK_LIST_REFRESH_DELAY * 1000);
 
 		this->servers.SetListing(this->last_sorting);
 		this->servers.SetSortFuncs(this->sorter_funcs);
@@ -725,7 +739,8 @@ public:
 			}
 
 			case WID_NG_SEARCH_INTERNET:
-				NetworkUDPQueryMasterServer();
+				_network_coordinator_client.GetListing();
+				this->searched_internet = true;
 				break;
 
 			case WID_NG_SEARCH_LAN:
@@ -841,10 +856,11 @@ public:
 
 	void OnRealtimeTick(uint delta_ms) override
 	{
+		if (!this->searched_internet) return;
 		if (!this->requery_timer.Elapsed(delta_ms)) return;
-		this->requery_timer.SetInterval(MILLISECONDS_PER_TICK);
+		this->requery_timer.SetInterval(NETWORK_LIST_REFRESH_DELAY * 1000);
 
-		NetworkGameListRequery();
+		_network_coordinator_client.GetListing();
 	}
 };