diff --git a/src/sbbs3/sbbs_ini.c b/src/sbbs3/sbbs_ini.c
index f5c89121452fe15aad3445c30cb1a9183da6c216..dd12342bec5f85baf432af1ec1184423acc3faba 100644
--- a/src/sbbs3/sbbs_ini.c
+++ b/src/sbbs3/sbbs_ini.c
@@ -515,7 +515,7 @@ void sbbs_read_ini(
 		bbs->bind_retry_delay=iniGetInteger(list,section,strBindRetryDelay,global->bind_retry_delay);
 
 		bbs->login_attempt = get_login_attempt_settings(list, section, global);
-		bbs->max_concurrent_connections = iniGetInteger(list, section, strMaxConConn, 0);
+		bbs->max_concurrent_connections = iniGetUInteger(list, section, strMaxConConn, 0);
 
 		bbs->max_login_inactivity = (uint16_t)iniGetDuration(list, section, strMaxLoginInactivity, 10 * 60);
 		bbs->max_newuser_inactivity = (uint16_t)iniGetDuration(list, section, strMaxNewUserInactivity, 60 * 60);
@@ -587,7 +587,7 @@ void sbbs_read_ini(
 		ftp->bind_retry_count=iniGetInteger(list,section,strBindRetryCount,global->bind_retry_count);
 		ftp->bind_retry_delay=iniGetInteger(list,section,strBindRetryDelay,global->bind_retry_delay);
 		ftp->login_attempt = get_login_attempt_settings(list, section, global);
-		ftp->max_concurrent_connections = iniGetInteger(list, section, strMaxConConn, 0);
+		ftp->max_concurrent_connections = iniGetUInteger(list, section, strMaxConConn, 0);
 	}
 
 	/***********************************************************************/
@@ -688,7 +688,7 @@ void sbbs_read_ini(
 		mail->bind_retry_count=iniGetInteger(list,section,strBindRetryCount,global->bind_retry_count);
 		mail->bind_retry_delay=iniGetInteger(list,section,strBindRetryDelay,global->bind_retry_delay);
 		mail->login_attempt = get_login_attempt_settings(list, section, global);
-		mail->max_concurrent_connections = iniGetInteger(list, section, strMaxConConn, 0);
+		mail->max_concurrent_connections = iniGetUInteger(list, section, strMaxConConn, 0);
 		mail->spam_block_duration = (uint)iniGetDuration(list, section, "SpamBlockDuration", 0);
 		mail->notify_offline_users = iniGetBool(list, section, "NotifyOfflineUsers", false);
 	}
@@ -818,6 +818,7 @@ void sbbs_read_ini(
 		web->bind_retry_count=iniGetInteger(list,section,strBindRetryCount,global->bind_retry_count);
 		web->bind_retry_delay=iniGetInteger(list,section,strBindRetryDelay,global->bind_retry_delay);
 		web->login_attempt = get_login_attempt_settings(list, section, global);
+		web->max_concurrent_connections = iniGetUInteger(list, section, strMaxConConn, WEB_DEFAULT_MAX_CON_CONN);
 	}
 
 	free(global_interfaces);
@@ -941,7 +942,7 @@ bool sbbs_write_ini(
 			break;
 		if(!iniSetUInteger(lp,section,"OutbufDrainTimeout",bbs->outbuf_drain_timeout,&style))
 			break;
-		if(!iniSetInteger(lp,section,strMaxConConn,bbs->max_concurrent_connections,&style))
+		if(!iniSetUInteger(lp,section,strMaxConConn,bbs->max_concurrent_connections,&style))
 			break;
 		if(!iniSetDuration(lp, section, strMaxLoginInactivity, bbs->max_login_inactivity, &style))
 			break;
@@ -1048,7 +1049,7 @@ bool sbbs_write_ini(
 			break;
 		if(!iniSetDuration(lp,section,strMaxInactivity,ftp->max_inactivity,&style))
 			break;
-		if(!iniSetInteger(lp,section,strMaxConConn,ftp->max_concurrent_connections,&style))
+		if(!iniSetUInteger(lp,section,strMaxConConn,ftp->max_concurrent_connections,&style))
 			break;
 		if(!iniSetDuration(lp,section,"QwkTimeout",ftp->qwk_timeout,&style))
 			break;
@@ -1178,7 +1179,7 @@ bool sbbs_write_ini(
 			break;
 		if(!iniSetDuration(lp,section,"ConnectTimeout",mail->connect_timeout,&style))
 			break;
-		if(!iniSetInteger(lp,section,strMaxConConn,mail->max_concurrent_connections,&style))
+		if(!iniSetUInteger(lp,section,strMaxConConn,mail->max_concurrent_connections,&style))
 			break;
 
 		if(strcmp(mail->host_name,global->host_name)==0
@@ -1422,6 +1423,8 @@ bool sbbs_write_ini(
 			break;
 		if(!iniSetUInteger(lp,section,"OutbufDrainTimeout",web->outbuf_drain_timeout,&style))
 			break;
+		if(!iniSetUInteger(lp,section,strMaxConConn,web->max_concurrent_connections,&style))
+			break;
 	}
 
 	/***********************************************************************/
diff --git a/src/sbbs3/scfg/scfgsrvr.c b/src/sbbs3/scfg/scfgsrvr.c
index 32dc77a850ea29f2fcdd5a7de85b43388af0d9c8..d38c43b829f8ba06f97fd4d885d0447c1532224a 100644
--- a/src/sbbs3/scfg/scfgsrvr.c
+++ b/src/sbbs3/scfg/scfgsrvr.c
@@ -897,6 +897,7 @@ static void websrvr_cfg(void)
 		snprintf(opt[i++], MAX_OPLN, "%-30s%s", "Access Logging", startup.options & WEB_OPT_HTTP_LOGGING ? str : strDisabled);
 		snprintf(opt[i++], MAX_OPLN, "%-30s%s", "Max Clients", maximum(startup.max_clients));
 		snprintf(opt[i++], MAX_OPLN, "%-30s%s", "Max Inactivity", vduration(startup.max_inactivity));
+		snprintf(opt[i++], MAX_OPLN, "%-30s%s", "Max Concurrent Connections", maximum(startup.max_concurrent_connections));
 		snprintf(opt[i++], MAX_OPLN, "%-30s%s", "Filebase Index Script", startup.file_index_script);
 		snprintf(opt[i++], MAX_OPLN, "%-30s%s", "Filebase VPath Prefix", startup.file_vpath_prefix);
 		snprintf(opt[i++], MAX_OPLN, "%-30s%s", "Filebase VPath for VHosts"
@@ -992,39 +993,44 @@ static void websrvr_cfg(void)
 					startup.max_inactivity = (uint16_t)parse_duration(str);
 				break;
 			case 12:
+				SAFECOPY(str, maximum(startup.max_concurrent_connections));
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Maximum Concurrent Connections (0=unlimited)", str, 10, K_EDIT | K_NUMBER) > 0)
+					startup.max_concurrent_connections = atoi(str);
+				break;
+			case 13:
 				uifc.input(WIN_MID|WIN_SAV, 0, 0, "Filebase Index Script"
 					,startup.file_index_script, sizeof(startup.file_index_script)-1, K_EDIT);
 				break;
-			case 13:
+			case 14:
 				uifc.input(WIN_MID|WIN_SAV, 0, 0, "Filebase Virtual Path Prefix"
 					,startup.file_vpath_prefix, sizeof(startup.file_vpath_prefix)-1, K_EDIT);
 				break;
-			case 14:
+			case 15:
 				startup.file_vpath_for_vhosts = !startup.file_vpath_for_vhosts;
 				break;
-			case 15:
+			case 16:
 				uifc.input(WIN_MID|WIN_SAV, 0, 0, "Authentication Methods"
 					,startup.default_auth_list, sizeof(startup.default_auth_list)-1, K_EDIT);
 				break;
-			case 16:
+			case 17:
 				SAFEPRINTF(str, "%u", startup.outbuf_drain_timeout);
 				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Output Buffer Drain Timeout (milliseconds)"
 					,str, 5, K_NUMBER|K_EDIT) > 0)
 					startup.outbuf_drain_timeout = atoi(str);
 				break;
-			case 17:
+			case 18:
 				startup.options ^= BBS_OPT_NO_HOST_LOOKUP;
 				break;
-			case 18:
+			case 19:
 				startup.options ^= WEB_OPT_NO_CGI;
 				break;
-			case 19:
+			case 20:
 				if(startup.options & WEB_OPT_NO_CGI)
 					break;
 				uifc.input(WIN_MID|WIN_SAV, 0, 0, "CGI Directory"
 					,startup.cgi_dir, sizeof(startup.cgi_dir)-1, K_EDIT);
 				break;
-			case 20:
+			case 21:
 				if(startup.options & WEB_OPT_NO_CGI)
 					break;
 				strListCombine(startup.cgi_ext, str, sizeof(str), ", ");
@@ -1034,26 +1040,26 @@ static void websrvr_cfg(void)
 					uifc.changes = true;
 				}
 				break;
-			case 21:
+			case 22:
 				if(startup.options & WEB_OPT_NO_CGI)
 					break;
 				uifc.input(WIN_MID|WIN_SAV, 0, 0, "Default CGI MIME Content-Type"
 					,startup.default_cgi_content, sizeof(startup.default_cgi_content)-1, K_EDIT);
 				break;
-			case 22:
+			case 23:
 				if(startup.options & WEB_OPT_NO_CGI)
 					break;
 				duration_to_str(startup.max_cgi_inactivity, str, sizeof(str));
 				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Maximum CGI Inactivity", str, 10, K_EDIT) > 0)
 					startup.max_cgi_inactivity = (uint16_t)parse_duration(str);
 				break;
-			case 23:
+			case 24:
 				getar("Web Server Login", startup.login_ars);
 				break;
-			case 24:
+			case 25:
 				js_startup_cfg(&startup.js);
 				break;
-			case 25:
+			case 26:
 				login_attempt_cfg(&startup.login_attempt);
 				break;
 			default:
@@ -1241,7 +1247,7 @@ static void ftpsrvr_cfg(void)
 				break;
 			case 11:
 				SAFECOPY(str, maximum(startup.max_concurrent_connections));
-				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Maximum Concurrent (Unauthenticated) Connections", str, 10, K_EDIT) > 0)
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Maximum Concurrent Connections (0=unlimited)", str, 10, K_EDIT | K_NUMBER) > 0)
 					startup.max_concurrent_connections = atoi(str);
 				break;
 			case 12:
diff --git a/src/sbbs3/websrvr.c b/src/sbbs3/websrvr.c
index d39e51baa3f2a55cd7d8ef2c3e2c20e597c1b6d7..8b0e3a110c5ce9f61f29378553093ef9eda191f6 100644
--- a/src/sbbs3/websrvr.c
+++ b/src/sbbs3/websrvr.c
@@ -82,6 +82,7 @@ static const char*	error_302="302 Moved Temporarily";
 static const char*	error_307="307 Temporary Redirect";
 static const char*	error_404="404 Not Found";
 static const char*	error_416="416 Requested Range Not Satisfiable";
+static const char*	error_429="429 Too Many Requests";
 static const char*	error_500="500 Internal Server Error";
 static const char*	error_503="503 Service Unavailable\r\nConnection: close\r\nContent-Length: 0\r\n\r\n";
 static const char*	unknown=STR_UNKNOWN_USER;
@@ -125,6 +126,7 @@ static str_list_t shutdown_semfiles;
 static str_list_t pause_semfiles;
 static str_list_t cgi_env;
 static struct mqtt mqtt;
+static link_list_t current_connections;
 
 static named_string_t** mime_types;
 static named_string_t** cgi_handlers;
@@ -770,6 +772,8 @@ static void update_clients(void)
 
 static void client_on(SOCKET sock, client_t* client, bool update)
 {
+	if(!update)
+		listAddNodeData(&current_connections, client->addr, strlen(client->addr) + 1, sock, LAST_NODE);
 	if(startup!=NULL && startup->client_on!=NULL)
 		startup->client_on(startup->cbdata,true,sock,client,update);
 	mqtt_client_on(&mqtt, true, sock, client, update);
@@ -777,6 +781,7 @@ static void client_on(SOCKET sock, client_t* client, bool update)
 
 static void client_off(SOCKET sock)
 {
+	listRemoveTaggedNode(&current_connections, sock, /* free_data */true);
 	if(startup!=NULL && startup->client_on!=NULL)
 		startup->client_on(startup->cbdata,false,sock,NULL,false);
 	mqtt_client_on(&mqtt, false, sock, NULL, false);
@@ -6968,6 +6973,8 @@ static void cleanup(int code)
 
 	update_clients();	/* active_clients is destroyed below */
 
+	listFree(&current_connections);
+
 	if(protected_uint32_value(active_clients))
 		lprintf(LOG_WARNING,"!!!! Terminating with %u active clients", protected_uint32_value(active_clients));
 	else
@@ -7175,6 +7182,7 @@ void web_server(void* arg)
 	protected_uint32_init(&thread_count, 0);
 
 	do {
+		listInit(&current_connections, LINK_LIST_MUTEX);
 		protected_uint32_init(&active_clients,0);
 
 		/* Setup intelligent defaults */
@@ -7418,6 +7426,23 @@ void web_server(void* arg)
 
 			inet_addrtop(&client_addr, host_ip, sizeof(host_ip));
 
+			if(startup->max_concurrent_connections > 0) {
+				int ip_len = strlen(host_ip) + 1;
+				uint connections = listCountMatches(&current_connections, host_ip, ip_len);
+				if(connections >= startup->max_concurrent_connections
+					&& !is_host_exempt(&scfg, host_ip, /* host_name */NULL)) {
+					lprintf(LOG_NOTICE, "%04d [%s] !Maximum concurrent connections (%u) exceeded"
+						,client_socket, host_ip, startup->max_concurrent_connections);
+					static int len_429;
+					if(len_429 < 1)
+						len_429 = strlen(error_429);
+					if(sendsocket(client_socket, error_429, len_429) != len_429)
+						lprintf(LOG_ERR, "%04d FAILED sending error 429", client_socket);
+					close_socket(&client_socket);
+					continue;
+				}
+			}
+
 			if(trashcan(&scfg,host_ip,"ip-silent")) {
 				close_socket(&client_socket);
 				continue;
diff --git a/src/sbbs3/websrvr.h b/src/sbbs3/websrvr.h
index 8ce722d10db5bf8286970bdfbf3f7b84568997f2..4073c0607be9ee57da160834afb005d48805d792 100644
--- a/src/sbbs3/websrvr.h
+++ b/src/sbbs3/websrvr.h
@@ -29,12 +29,14 @@
 typedef struct {
 
 	STARTUP_COMMON_ELEMENTS
-	uint16_t	max_clients;
+	uint		max_clients;
 #define WEB_DEFAULT_MAX_CLIENTS			100	/* 0=unlimited */
-	uint16_t	max_inactivity;
+	uint		max_inactivity;
 #define WEB_DEFAULT_MAX_INACTIVITY		120	/* seconds */
-	uint16_t	max_cgi_inactivity;
+	uint		max_cgi_inactivity;
 #define WEB_DEFAULT_MAX_CGI_INACTIVITY	120	/* seconds */
+	uint	max_concurrent_connections;
+#define WEB_DEFAULT_MAX_CON_CONN		10	/* 0=unlimited */
 	uint16_t	port;
 	uint16_t	tls_port;
     str_list_t	interfaces;
@@ -56,7 +58,7 @@ typedef struct {
 	int		tls_error_level;		/* Cap the severity of TLS error log messages */
 	char	default_cgi_content[128];
 	char	default_auth_list[128];
-	uint16_t	outbuf_drain_timeout;
+	uint	outbuf_drain_timeout;
 
 	/* JavaScript operating parameters */
 	js_startup_t js;