diff --git a/src/sbbs3/echocfg.c b/src/sbbs3/echocfg.c
index 7e8e6496979a9f500c4bbe6990c4e1cd4bc14def..5832c1f46f5d1a61baee77a9172c521df2689c38 100644
--- a/src/sbbs3/echocfg.c
+++ b/src/sbbs3/echocfg.c
@@ -60,11 +60,20 @@ void global_settings(void)
 	while(1) {
 		int i = 0;
 		char str[128];
+		char tmp[128];
 		char duration[64];
 		sprintf(opt[i++], "%-30s %s", "Mailer Type"
 			,cfg.flo_mailer ? "Binkley/FLO":"ArcMail/Attach");
 		sprintf(opt[i++], "%-30s %s", "Log Level", logLevelStringList[cfg.log_level]);
 		sprintf(opt[i++], "%-30s %s", "Log Timestamp Format", cfg.logtime);
+		if(cfg.max_log_size) {
+			SAFEPRINTF2(str, "%s bytes, keep %lu"
+				,byte_count_to_str(cfg.max_log_size, tmp, sizeof(tmp))
+				,cfg.max_logs_kept);
+		} else {
+			SAFECOPY(str, "Unlimited");
+		}
+		sprintf(opt[i++], "%-30s %s", "Maximum Log File Size", str);
 		sprintf(opt[i++], "%-30s %s", "Strict Packet Passwords", cfg.strict_packet_passwords ? "Enabled" : "Disabled");
 		sprintf(opt[i++], "%-30s %u", "Config File Backups", cfg.cfgfile_backups);
 		sprintf(opt[i++], "%-30s %s bytes", "Minimum Free Disk Space"
@@ -212,22 +221,34 @@ void global_settings(void)
 				break;
 
 			case 3:
-				cfg.strict_packet_passwords = !cfg.strict_packet_passwords;
+				byte_count_to_str(cfg.max_log_size, str, sizeof(str));
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Maximum Log File Size (in bytes, 0=unlimited)", str, 10, K_EDIT|K_UPPER) > 0) {
+					cfg.max_log_size = parse_byte_count(str, 1);
+					if(cfg.max_log_size) {
+						SAFEPRINTF(str, "%lu", cfg.max_logs_kept);
+						if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Maximum Number of old Log Files to Keep", str, 5, K_EDIT|K_NUMBER) > 0)
+							cfg.max_logs_kept = strtoul(str, NULL, 10);
+					}
+				}
 				break;
 
 			case 4:
+				cfg.strict_packet_passwords = !cfg.strict_packet_passwords;
+				break;
+
+			case 5:
 				sprintf(str, "%u", cfg.cfgfile_backups);
 				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Configuration File Backups", str, 5, K_EDIT|K_NUMBER) > 0)
 					cfg.cfgfile_backups = atoi(str);
 				break;
 
-			case 5:
+			case 6:
 				byte_count_to_str(cfg.min_free_diskspace, str, sizeof(str));
-				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Minimum Free Disk Space (in bytes)", str, 10, K_EDIT) > 0)
+				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Minimum Free Disk Space (in bytes)", str, 10, K_EDIT|K_UPPER) > 0)
 					cfg.min_free_diskspace = parse_byte_count(str, 1);
 				break;
 
-			case 6:
+			case 7:
 			{
 				int k = !cfg.strip_soft_cr;
 				switch(uifc.list(WIN_MID|WIN_SAV,0,0,0,&k,0
@@ -237,7 +258,7 @@ void global_settings(void)
 				}
 				break;
 			}
-			case 7:
+			case 8:
 			{
 				int k = !cfg.strip_lf;
 				switch(uifc.list(WIN_MID|WIN_SAV,0,0,0,&k,0
@@ -247,7 +268,7 @@ void global_settings(void)
 				}
 				break;
 			}
-			case 8:
+			case 9:
 			{
 				int k = !cfg.auto_utf8;
 				switch(uifc.list(WIN_MID|WIN_SAV,0,0,0,&k,0
@@ -257,7 +278,7 @@ void global_settings(void)
 				}
 				break;
 			}
-			case 9:
+			case 10:
 			{
 				int k = !cfg.use_outboxes;
 				switch(uifc.list(WIN_MID|WIN_SAV,0,0,0,&k,0
@@ -267,35 +288,35 @@ void global_settings(void)
 				}
 				break;
 			}
-			case 10:
+			case 11:
 				duration_to_vstr(cfg.bsy_timeout, duration, sizeof(duration));
 				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "BSY Mutex File Timeout", duration, 10, K_EDIT) > 0)
 					cfg.bsy_timeout = (ulong)parse_duration(duration);
 				break;
 
-			case 11:
+			case 12:
 				duration_to_vstr(cfg.bso_lock_delay, duration, sizeof(duration));
 				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Delay Between BSO Lock Attempts", duration, 10, K_EDIT) > 0)
 					cfg.bso_lock_delay = (ulong)parse_duration(duration);
 				break;
 
-			case 12:
+			case 13:
 				sprintf(str, "%lu", cfg.bso_lock_attempts);
 				if(uifc.input(WIN_MID|WIN_SAV, 0, 0, "Maximum BSO Lock Attempts", str, 5, K_EDIT|K_NUMBER) > 0)
 					cfg.bso_lock_attempts = atoi(str);
 				break;
 
-			case 13:
+			case 14:
 				uifc.input(WIN_MID|WIN_SAV,0,0
 					,"BinkP Capabilities (BinkIT)", cfg.binkp_caps, sizeof(cfg.binkp_caps)-1, K_EDIT);
 				break;
 
-			case 14:
+			case 15:
 				uifc.input(WIN_MID|WIN_SAV,0,0
 					,"BinkP Sysop Name (BinkIT)", cfg.binkp_sysop, sizeof(cfg.binkp_sysop)-1, K_EDIT);
 				break;
 
-			case 15:
+			case 16:
 			{
 				int k = !cfg.binkp_plainAuthOnly;
 				strcpy(opt[0], "Plain-Password Only");
@@ -313,7 +334,7 @@ void global_settings(void)
 				break;
 			}
 
-			case 16:
+			case 17:
 			{
 				if(cfg.binkp_plainAuthOnly) {
 					uifc.msg("CRAM-MD5 authentication/encryption has been disabled globally");
diff --git a/src/sbbs3/rechocfg.c b/src/sbbs3/rechocfg.c
index 19079bf54f8f0c800b4f74ee39ab16a7f4170199..3596bb4c430bfacea3e2b8b9cb7b5ef0ccee35a8 100644
--- a/src/sbbs3/rechocfg.c
+++ b/src/sbbs3/rechocfg.c
@@ -218,6 +218,8 @@ void get_default_echocfg(sbbsecho_cfg_t* cfg)
 	cfg->auto_utf8					= true;
 	cfg->strip_soft_cr				= true;
 	cfg->min_free_diskspace			= 10*1024*1024;
+	cfg->max_logs_kept				= 10;
+	cfg->max_log_size				= 10*1024*1024;
 }
 
 char* pktTypeStringList[] = {"2+", "2e", "2.2", "2", NULL};		// Must match enum pkt_type
@@ -265,6 +267,8 @@ bool sbbsecho_read_ini(sbbsecho_cfg_t* cfg)
 	cfg->umask					= iniGetInteger(ini, ROOT_SECTION, "umask", cfg->umask);
 	cfg->areafile_backups		= iniGetInteger(ini, ROOT_SECTION, "AreaFileBackups", cfg->areafile_backups);
 	cfg->cfgfile_backups		= iniGetInteger(ini, ROOT_SECTION, "CfgFileBackups", cfg->cfgfile_backups);
+	cfg->max_logs_kept			= iniGetInteger(ini, ROOT_SECTION, "MaxLogsKept", cfg->max_logs_kept);
+	cfg->max_log_size			= iniGetBytes(ini, ROOT_SECTION, "MaxLogSize", 1, cfg->max_log_size);
 	cfg->min_free_diskspace		= iniGetBytes(ini, ROOT_SECTION, "MinFreeDiskSpace", 1, cfg->min_free_diskspace);
 	cfg->strip_lf				= iniGetBool(ini, ROOT_SECTION, "StripLineFeeds", cfg->strip_lf);
 	cfg->strip_soft_cr			= iniGetBool(ini, ROOT_SECTION, "StripSoftCRs", cfg->strip_soft_cr);
@@ -513,6 +517,8 @@ bool sbbsecho_write_ini(sbbsecho_cfg_t* cfg)
 	iniSetString(&ini,		ROOT_SECTION, "AreaFile"				,cfg->areafile					,style);
 	iniSetInteger(&ini,		ROOT_SECTION, "AreaFileBackups"			,cfg->areafile_backups			,style);
 	iniSetInteger(&ini,		ROOT_SECTION, "CfgFileBackups"			,cfg->cfgfile_backups			,style);
+	iniSetInteger(&ini,		ROOT_SECTION, "MaxLogsKept"				,cfg->max_logs_kept				,style);
+	iniSetBytes(&ini,		ROOT_SECTION, "MaxLogSize"				,1,cfg->max_log_size			,style);
 	iniSetBytes(&ini,		ROOT_SECTION, "MinFreeDiskSpace"		,1,cfg->min_free_diskspace		,style);
 	iniSetString(&ini,		ROOT_SECTION, "BadAreaFile"				,cfg->badareafile				,style);
 	iniSetString(&ini,		ROOT_SECTION, "EchoStats"				,cfg->echostats					,style);
diff --git a/src/sbbs3/sbbsecho.c b/src/sbbs3/sbbsecho.c
index 9c8c6d895d87efa29f7cb5d5db084ce37f778747..9cf5b05c44bf95e85598f257366cb8dac22c905c 100644
--- a/src/sbbs3/sbbsecho.c
+++ b/src/sbbs3/sbbsecho.c
@@ -6380,6 +6380,26 @@ int main(int argc, char **argv)
 			check_free_diskspace(cfg.nodecfg[u].outbox);
 	}
 
+	SAFEPRINTF(path,"%ssbbsecho.bsy", scfg.ctrl_dir);
+	if(!fmutex(path, program_id(), cfg.bsy_timeout)) {
+		lprintf(LOG_WARNING, "Mutex file exists (%s): SBBSecho appears to be already running", path);
+		bail(1);
+	}
+	mtxfile_locked = true;
+	atexit(cleanup);
+
+	if(cfg.max_log_size && ftello(fidologfile) >= cfg.max_log_size) {
+		lprintf(LOG_INFO, "Maximum log file size reached: %"PRId64" bytes", cfg.max_log_size);
+		fclose(fidologfile);
+
+		backup(cfg.logfile, cfg.max_logs_kept, /* rename: */true);
+		if((fidologfile = fopen(cfg.logfile,"a")) == NULL) {
+			fprintf(stderr,"ERROR %u (%s) line %d opening %s\n", errno, strerror(errno), __LINE__, cfg.logfile);
+			bail(1);
+			return -1;
+		}
+	}
+
 	truncsp(cmdline);
 	lprintf(LOG_DEBUG,"%s (PID %u) invoked with options: %s", sbbsecho_pid(), getpid(), cmdline);
 	lprintf(LOG_DEBUG,"Configured: %u archivers, %u linked-nodes, %u echolists", cfg.arcdefs, cfg.nodecfgs, cfg.listcfgs);
@@ -6390,14 +6410,6 @@ int main(int argc, char **argv)
 	if(cfg.ignore_netmail_sent_attr && !cfg.delete_netmail)
 		lprintf(LOG_WARNING, "Ignore NetMail 'Sent' Attribute is enabled with Delete NetMail disabled: Duplicate NetMail msgs may be sent!");
 
-	SAFEPRINTF(path,"%ssbbsecho.bsy", scfg.ctrl_dir);
-	if(!fmutex(path, program_id(), cfg.bsy_timeout)) {
-		lprintf(LOG_WARNING, "Mutex file exists (%s): SBBSecho appears to be already running", path);
-		bail(1);
-	}
-	mtxfile_locked = true;
-	atexit(cleanup);
-
 	/******* READ IN AREAS.BBS FILE *********/
 	cfg.areas=0;		/* Total number of areas in AREAS.BBS */
 	cfg.area=NULL;
diff --git a/src/sbbs3/sbbsecho.h b/src/sbbs3/sbbsecho.h
index 234eaf03427ece5b4e39210b51bbd33e48d4e9c9..fe5acdf99c780d34909fbe6729b3414fbd8780be 100644
--- a/src/sbbs3/sbbsecho.h
+++ b/src/sbbs3/sbbsecho.h
@@ -29,7 +29,7 @@
 #include "ini_file.h"
 
 #define SBBSECHO_VERSION_MAJOR		3
-#define SBBSECHO_VERSION_MINOR		13
+#define SBBSECHO_VERSION_MINOR		14
 
 #define SBBSECHO_PRODUCT_CODE		0x12FF	/* from http://ftsc.org/docs/ftscprod.013 */
 
@@ -215,6 +215,8 @@ typedef struct {
 	ulong		bso_lock_delay;			/* in seconds */
 	ulong		max_netmail_age;
 	ulong		max_echomail_age;
+	ulong		max_logs_kept;
+	int64_t		max_log_size;
 	int64_t		min_free_diskspace;
 	struct fido_domain* domain_list;
 	unsigned	domain_count;