diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 9a636dd45555b901ef415ba56f4b74928fc3653c..bfe9788e350b155399ba877f891c8b264f9bdb35 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -3161,6 +3161,8 @@ static void CL_RemovePlayer(INT32 playernum, kickreason_t reason)
 	// Reset the name
 	sprintf(player_names[playernum], "Player %d", playernum+1);
 
+	player_name_changes[playernum] = 0;
+
 	if (IsPlayerAdmin(playernum))
 	{
 		RemoveAdminPlayer(playernum); // don't stay admin after you're gone
@@ -3777,6 +3779,8 @@ void SV_ResetServer(void)
 		adminplayers[i] = -1; // Populate the entire adminplayers array with -1.
 	}
 
+	memset(player_name_changes, 0, sizeof player_name_changes);
+
 	mynode = 0;
 	cl_packetmissed = false;
 
diff --git a/src/d_clisrv.h b/src/d_clisrv.h
index ce8d87adb53079d2eb25e1a29aadd70a65eebac2..11fc2f6f44b5d26a38ede357be75cc526708d460 100644
--- a/src/d_clisrv.h
+++ b/src/d_clisrv.h
@@ -526,6 +526,10 @@ typedef enum
 
 } kickreason_t;
 
+/* the max number of name changes in some time period */
+#define MAXNAMECHANGES (5)
+#define NAMECHANGERATE (60*TICRATE)
+
 extern boolean server;
 extern boolean serverrunning;
 #define client (!server)
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 68b8ecfc1ad1ec65d56cd19de46bb62781a9917e..62b93bba262592d605b3921be2d0034d3473fdbc 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -1126,6 +1126,8 @@ static void SetPlayerName(INT32 playernum, char *newname)
 			if (netgame)
 				HU_AddChatText(va("\x82*%s renamed to %s", player_names[playernum], newname), false);
 
+			player_name_changes[playernum]++;
+
 			strcpy(player_names[playernum], newname);
 		}
 	}
@@ -1302,7 +1304,12 @@ static void SendNameAndColor(void)
 	snacpending++;
 
 	// Don't change name if muted
-	if (cv_mute.value && !(server || IsPlayerAdmin(consoleplayer)))
+	if (player_name_changes[consoleplayer] >= MAXNAMECHANGES)
+	{
+		CV_StealthSet(&cv_playername, player_names[consoleplayer]);
+		HU_AddChatText("\x85*You must wait to change your name again", false);
+	}
+	else if (cv_mute.value && !(server || IsPlayerAdmin(consoleplayer)))
 		CV_StealthSet(&cv_playername, player_names[consoleplayer]);
 	else // Cleanup name if changing it
 		CleanupPlayerName(consoleplayer, cv_playername.zstring);
@@ -1463,8 +1470,11 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum)
 	skin = READUINT8(*cp);
 
 	// set name
-	if (strcasecmp(player_names[playernum], name) != 0)
-		SetPlayerName(playernum, name);
+	if (player_name_changes[playernum] < MAXNAMECHANGES)
+	{
+		if (strcasecmp(player_names[playernum], name) != 0)
+			SetPlayerName(playernum, name);
+	}
 
 	// set color
 	p->skincolor = color % numskincolors;
diff --git a/src/g_game.c b/src/g_game.c
index b969eb4a4f2627c04f8e215dea33c71d3893cb9e..15d48e2f549e42dd408dd0c5e29de8ebc0a358e9 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -452,6 +452,8 @@ player_t *seenplayer; // player we're aiming at right now
 // so that it doesn't have to be updated depending on the value of MAXPLAYERS
 char player_names[MAXPLAYERS][MAXPLAYERNAME+1];
 
+INT32 player_name_changes[MAXPLAYERS];
+
 INT16 rw_maximums[NUM_WEAPONS] =
 {
 	800, // MAX_INFINITY
@@ -2345,6 +2347,11 @@ void G_Ticker(boolean run)
 
 		if (camtoggledelay2)
 			camtoggledelay2--;
+
+		if (gametic % NAMECHANGERATE == 0)
+		{
+			memset(player_name_changes, 0, sizeof player_name_changes);
+		}
 	}
 }
 
diff --git a/src/g_game.h b/src/g_game.h
index c8abe560c629a607f6e3d1f24895abfc3a281a64..e3a1faf1f38b17e288c3eb9c189a345d5eaea5c9 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -27,7 +27,8 @@ extern char customversionstring[32];
 #ifdef SEENAMES
 extern player_t *seenplayer;
 #endif
-extern char player_names[MAXPLAYERS][MAXPLAYERNAME+1];
+extern char  player_names[MAXPLAYERS][MAXPLAYERNAME+1];
+extern INT32 player_name_changes[MAXPLAYERS];
 
 extern player_t players[MAXPLAYERS];
 extern boolean playeringame[MAXPLAYERS];