diff --git a/src/netcode/d_clisrv.c b/src/netcode/d_clisrv.c
index d84759b4236eb8de12102783c4422fe8b5057c78..f4251ef08a92f89a1f8898e2644c953723086f78 100644
--- a/src/netcode/d_clisrv.c
+++ b/src/netcode/d_clisrv.c
@@ -1280,6 +1280,7 @@ static void UpdatePingTable(void)
 	}
 }
 
+// Handle idle and disconnected player timers
 static void IdleUpdate(void)
 {
 	INT32 i;
@@ -1302,7 +1303,26 @@ static void IdleUpdate(void)
 			}
 		}
 		else
+		{
 			players[i].lastinputtime = 0;
+
+			if (players[i].quittime && playeringame[i])
+			{
+				players[i].quittime++;
+
+				if (players[i].quittime == 30 * TICRATE && G_TagGametype())
+					P_CheckSurvivors();
+
+				if (server && players[i].quittime >= (tic_t)FixedMul(cv_rejointimeout.value, 60 * TICRATE)
+				&& !(players[i].quittime % TICRATE))
+				{
+					if (D_NumNodes(true) > 0)
+						SendKick(i, KICK_MSG_PLAYER_QUIT);
+					else // If the server is empty, don't send a NetXCmd - that would wake an idling dedicated server
+						CL_RemovePlayer(i, KICK_MSG_PLAYER_QUIT);
+				}
+			}
+		}
 	}
 }
 
@@ -1623,12 +1643,15 @@ INT32 D_NumPlayers(void)
 	return num;
 }
 
-/** Returns the number of nodes on the server.
+/** Returns the number of currently-connected nodes in a netgame.
+  * Not necessarily equivalent to D_NumPlayers() minus D_NumBots().
+  *
+  * \param skiphost Skip the server's own node.
   */
-INT32 D_NumNodes(void)
+INT32 D_NumNodes(boolean skiphost)
 {
 	INT32 num = 0;
-	for (INT32 ix = 0; ix < MAXNETNODES; ix++)
+	for (INT32 ix = skiphost ? 1 : 0; ix < MAXNETNODES; ix++)
 		if (netnodes[ix].ingame)
 			num++;
 	return num;
diff --git a/src/netcode/d_clisrv.h b/src/netcode/d_clisrv.h
index f9cbf8876833486c232e7f51bbf8ae699ba6bd96..61823e65d29f3856a807877478490d9e91084c1a 100644
--- a/src/netcode/d_clisrv.h
+++ b/src/netcode/d_clisrv.h
@@ -121,7 +121,7 @@ extern char motd[254], server_context[8];
 extern UINT8 playernode[MAXPLAYERS];
 
 INT32 D_NumPlayers(void);
-INT32 D_NumNodes(void);
+INT32 D_NumNodes(boolean skiphost);
 INT32 D_NumBots(void);
 
 tic_t GetLag(INT32 node);
diff --git a/src/netcode/server_connection.c b/src/netcode/server_connection.c
index bfbe30a08ed89aba1af4bb5ef6f2a51448d00c1c..376700f0db1a300fee1abf21b48462dab1103df5 100644
--- a/src/netcode/server_connection.c
+++ b/src/netcode/server_connection.c
@@ -109,7 +109,7 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime)
 	netbuffer->u.serverinfo.leveltime = (tic_t)LONG(leveltime);
 
 	// Exclude bots from both counts
-	netbuffer->u.serverinfo.numberofplayer = (UINT8)(D_NumNodes() - (dedicated ? 1 : 0));
+	netbuffer->u.serverinfo.numberofplayer = (UINT8)D_NumNodes(dedicated);
 	netbuffer->u.serverinfo.maxplayer = (UINT8)(cv_maxplayers.value - D_NumBots());
 
 	netbuffer->u.serverinfo.refusereason = GetRefuseReason(node);
diff --git a/src/p_tick.c b/src/p_tick.c
index 143fa7e36844ca01d8a6bd2b6f41aed3bb04df1e..4ab388486db62be9d1fa36d9289ac2b043219e47 100644
--- a/src/p_tick.c
+++ b/src/p_tick.c
@@ -701,25 +701,11 @@ void P_Ticker(boolean run)
 {
 	INT32 i;
 
-	// Increment jointime and quittime even if paused
+	// Increment jointime even if paused
 	for (i = 0; i < MAXPLAYERS; i++)
 		if (playeringame[i])
-		{
 			players[i].jointime++;
 
-			if (players[i].quittime)
-			{
-				players[i].quittime++;
-
-				if (players[i].quittime == 30 * TICRATE && G_TagGametype())
-					P_CheckSurvivors();
-
-				if (server && players[i].quittime >= (tic_t)FixedMul(cv_rejointimeout.value, 60 * TICRATE)
-				&& !(players[i].quittime % TICRATE))
-					SendKick(i, KICK_MSG_PLAYER_QUIT);
-			}
-		}
-
 	if (objectplacing)
 	{
 		if (OP_FreezeObjectplace())