diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 789c505ec8ca9e8e7c91274d95d4cfd20778cf9d..451e976246bdfe9b9b116ef269ba97167f956e88 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -535,6 +535,7 @@ void D_RegisterServerCommands(void)
 	RegisterNetXCmd(XD_PICKVOTE, Got_PickVotecmd);
 
 	// Remote Administration
+	CV_RegisterVar(&cv_dummyjoinpassword);
 	COM_AddCommand("joinpassword", Command_ChangeJoinPassword_f);
 	COM_AddCommand("password", Command_Changepassword_f);
 	RegisterNetXCmd(XD_LOGIN, Got_Login);
@@ -3706,9 +3707,11 @@ static void Got_Removal(UINT8 **cp, INT32 playernum)
 }
 
 // Join password stuff
+consvar_t cv_dummyjoinpassword = {"dummyjoinpassword", "", CV_HIDEN|CV_NOSHOWHELP, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
+
 #define NUMJOINCHALLENGES 32
 static UINT8 joinpassmd5[17];
-static boolean joinpasswordset = false;
+boolean joinpasswordset = false;
 static UINT8 joinpasschallenges[NUMJOINCHALLENGES][17];
 static tic_t joinpasschallengeson[NUMJOINCHALLENGES];
 
diff --git a/src/d_netcmd.h b/src/d_netcmd.h
index 438ba3ff155381c28e49d34c40f84788922aba88..166c5e008b2a1fb5c670202abf3573a553355b67 100644
--- a/src/d_netcmd.h
+++ b/src/d_netcmd.h
@@ -248,6 +248,8 @@ void RemoveAdminPlayer(INT32 playernum);
 void ItemFinder_OnChange(void);
 void D_SetPassword(const char *pw);
 
+extern consvar_t cv_dummyjoinpassword;
+extern boolean joinpasswordset;
 boolean D_IsJoinPasswordOn(void);
 void D_ComputeChallengeAnswer(UINT8 *question, const char *pw, UINT8 *answer);
 void D_SetJoinPassword(const char *pw);
diff --git a/src/m_menu.c b/src/m_menu.c
index 23e6ede48b33014b139f88ef85ccb314b69e8211..83bec981e81aaa6364252d2382d76b32d6931abe 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -948,14 +948,15 @@ static menuitem_t MP_MainMenu[] =
 
 static menuitem_t MP_ServerMenu[] =
 {
-	{IT_STRING|IT_CVAR,              NULL, "Max. Player Count",     &cv_maxplayers,      10},
-	{IT_STRING|IT_CALL,              NULL, "Room...",               M_RoomMenu,          20},
-	{IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Server Name",           &cv_servername,      30},
+	{IT_STRING|IT_CVAR,                NULL, "Max. Player Count",     &cv_maxplayers,         0},
+	{IT_STRING|IT_CALL,                NULL, "Room...",               M_RoomMenu,            10},
+	{IT_STRING|IT_CVAR|IT_CV_STRING,   NULL, "Server Name",           &cv_servername,        20},
+	{IT_STRING|IT_CVAR|IT_CV_PASSWORD, NULL, "Password",              &cv_dummyjoinpassword, 44},
 
-	{IT_STRING|IT_CVAR,              NULL, "Game Type",             &cv_newgametype,     68},
-	{IT_STRING|IT_CVAR,              NULL, "Level",                 &cv_nextmap,         78},
+	{IT_STRING|IT_CVAR,                NULL, "Game Type",             &cv_newgametype,       68},
+	{IT_STRING|IT_CVAR,                NULL, "Level",                 &cv_nextmap,           78},
 
-	{IT_WHITESTRING|IT_CALL,         NULL, "Start",                 M_StartServer,      130},
+	{IT_WHITESTRING|IT_CALL,           NULL, "Start",                 M_StartServer,        130},
 };
 
 #endif
@@ -2682,7 +2683,7 @@ boolean M_Responder(event_t *ev)
 	// BP: one of the more big hack i have never made
 	if (routine && (currentMenu->menuitems[itemOn].status & IT_TYPE) == IT_CVAR)
 	{
-		if ((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_STRING)
+		if ((currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_STRING || (currentMenu->menuitems[itemOn].status & IT_CVARTYPE) == IT_CV_PASSWORD)
 		{
 			if (shiftdown && ch >= 32 && ch <= 127)
 				ch = shiftxform[ch];
@@ -3557,6 +3558,8 @@ static void M_DrawGenericMenu(void)
 					case IT_CVAR:
 					{
 						consvar_t *cv = (consvar_t *)currentMenu->menuitems[i].itemaction;
+						char asterisks[MAXSTRINGLENGTH+1];
+						size_t sl;
 						switch (currentMenu->menuitems[i].status & IT_CVARTYPE)
 						{
 							case IT_CV_SLIDER:
@@ -3572,6 +3575,18 @@ static void M_DrawGenericMenu(void)
 										'_' | 0x80, false);
 								y += 16;
 								break;
+							case IT_CV_PASSWORD:
+								sl = strlen(cv->string);
+								memset(asterisks, '*', sl);
+								memset(asterisks + sl, 0, MAXSTRINGLENGTH+1-sl);
+
+								M_DrawTextBox(x, y + 4, MAXSTRINGLENGTH, 1);
+								V_DrawString(x + 8, y + 12, V_ALLOWLOWERCASE, asterisks);
+								if (skullAnimCounter < 4 && i == itemOn)
+									V_DrawCharacter(x + 8 + V_StringWidth(asterisks, 0), y + 12,
+										'_' | 0x80, false);
+								y += 16;
+								break;
 							default:
 								w = V_StringWidth(cv->string, 0);
 								V_DrawString(BASEVIDWIDTH - x - w, y,
@@ -7540,6 +7555,11 @@ static void M_StartServer(INT32 choice)
 	// Still need to reset devmode
 	cv_debug = 0;
 
+	if (strlen(cv_dummyjoinpassword.string) > 0)
+		D_SetJoinPassword(cv_dummyjoinpassword.string);
+	else
+		joinpasswordset = false;
+
 	if (demoplayback)
 		G_StopDemo();
 	if (metalrecording)
diff --git a/src/m_menu.h b/src/m_menu.h
index 864f4cacc32983e659e8d2dc9f18d1b68076161e..06ae7114afaef9dd8d87f7a8fc4354f9a9c0117d 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -104,6 +104,7 @@ boolean M_CanShowLevelInList(INT32 mapnum, INT32 gt);
 #define IT_CV_NOPRINT     1536
 #define IT_CV_NOMOD       2048
 #define IT_CV_INVISSLIDER 2560
+#define IT_CV_PASSWORD    3072
 
 //call/submenu specific
 // There used to be a lot more here but ...