diff --git a/src/sbbs3/js_global.c b/src/sbbs3/js_global.c
index 9ee3956cce5062efc38592d7cb2b0e916cc9974e..dc3c1e1fe24cbacbe4a7a2b7fe1a19477b2a26a8 100644
--- a/src/sbbs3/js_global.c
+++ b/src/sbbs3/js_global.c
@@ -5080,6 +5080,7 @@ BOOL DLLCALL js_CreateGlobalObject(JSContext* cx, scfg_t* cfg, jsSyncMethodSpec*
 	p->methods = methods;
 	p->startup = startup;
 	p->exit_func=NULL;
+	p->onexit = NULL;
 
 	if((*glob = JS_NewCompartmentAndGlobalObject(cx, &js_global_class, NULL)) ==NULL) {
 		free(p);
diff --git a/src/sbbs3/js_internal.c b/src/sbbs3/js_internal.c
index ebe7f8f58bb46f5d14c0ab33bae9a4d7e8c3bbbd..afa5cd907b8637de6daf64be2ee05edb6d6ceeb5 100644
--- a/src/sbbs3/js_internal.c
+++ b/src/sbbs3/js_internal.c
@@ -59,11 +59,6 @@ enum {
 	,PROP_GLOBAL
 };
 
-struct onexit_private {
-	char signature[7];
-	str_list_t list;
-};
-
 static JSBool js_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
 {
 	jsval idval;
@@ -606,45 +601,41 @@ js_on_exit(JSContext *cx, uintN argc, jsval *arglist)
 	JSObject *glob=JS_GetGlobalObject(cx);
 	jsval *argv=JS_ARGV(cx, arglist);
 	global_private_t*	pd;
-	struct onexit_private *oep = NULL;
+	struct js_onexit_scope *oes = NULL;
 	str_list_t	oldlist;
 	str_list_t	list;
 	char		*p = NULL;
 
 	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
 
+	if((pd=(global_private_t*)JS_GetPrivate(cx,glob))==NULL)
+		return(JS_FALSE);
 	if(glob==scope) {
-		if((pd=(global_private_t*)JS_GetPrivate(cx,glob))==NULL)
-			return(JS_FALSE);
 		if(pd->exit_func==NULL)
 			pd->exit_func=strListInit();
 		list=pd->exit_func;
 	}
 	else {
-		oep=(struct onexit_private *)JS_GetPrivate(cx,scope);
-		if(oep==NULL) {
-			oep = malloc(sizeof(*oep));
-			if (oep == NULL) {
-				JS_ReportError(cx, "Unable to allocate memory for on_exit() list");
-				return JS_FALSE;
-			}
-			memcpy(oep->signature, "onexit", 7);
-			oep->list=strListInit();
-			JS_SetPrivate(cx,scope,oep);
+		/* First, look for an existing onexit scope for this scope */
+		for (oes = pd->onexit; oes; oes = oes->next) {
+			if (oes->scope == scope)
+				break;
 		}
-		else {
-			if (oep->signature[0] != 'o'
-			    || oep->signature[1] != 'n'
-			    || oep->signature[2] != 'e'
-			    || oep->signature[3] != 'x'
-			    || oep->signature[4] != 'i'
-			    || oep->signature[5] != 't'
-			    || oep->signature[6] != 0) {
-				JS_ReportError(cx, "on_exit not available in %s scopes", JS_GetClass(cx, scope)->name);
+
+		/* If one isn't found, insert it */
+		if (oes == NULL) {
+			oes = malloc(sizeof(*oes));
+			if (oes == NULL) {
+				JS_ReportError(cx, "Unable to allocate memory for onexit scope");
 				return JS_FALSE;
-			 }
+			}
+			oes->next = pd->onexit;
+			pd->onexit = oes;
+			oes->scope = scope;
+			oes->onexit = strListInit();
+			JS_AddObjectRoot(cx, &oes->scope);
 		}
-		list = oep->list;
+		list = oes->onexit;
 	}
 
 	JSVALUE_TO_MSTRING(cx, argv[0], p, NULL);
@@ -658,7 +649,7 @@ js_on_exit(JSContext *cx, uintN argc, jsval *arglist)
 		if(glob==scope)
 			pd->exit_func=list;
 		else
-			oep->list = list;
+			oes->onexit = list;
 	}
 	return(JS_TRUE);
 }
@@ -800,25 +791,43 @@ void DLLCALL js_EvalOnExit(JSContext *cx, JSObject *obj, js_callback_t* cb)
 	JSObject	*glob=JS_GetGlobalObject(cx);
 	global_private_t *pt;
 	str_list_t	list = NULL;
+	struct js_onexit_scope **prev_oes_next = NULL;
+	struct js_onexit_scope *oes = NULL;
 
+	pt=(global_private_t *)JS_GetPrivate(cx,JS_GetGlobalObject(cx));
 	if(glob==obj) {
-		pt=(global_private_t *)JS_GetPrivate(cx,JS_GetGlobalObject(cx));		
+		/* Yes, this is recursive to one level */
+		while (pt->onexit) {
+			if (pt->onexit->scope == glob) {
+				// This shouldn't happen, but let's not go inifinite eh?
+				JS_ReportError(cx, "js_EvalOnExit() extra scope is global");
+				return;
+			}
+			else {
+				oes = pt->onexit;
+				js_EvalOnExit(cx, pt->onexit->scope, cb);
+				if (oes == pt->onexit) {
+					// This *really* shouldn't happen...
+					JS_ReportError(cx, "js_EvalOnExit() did not pop on_exit stack");
+					return;
+				}
+			}
+		}
 		list=pt->exit_func;
 	}
 	else {
-		struct onexit_private *oep = JS_GetPrivate(cx,obj);
-		if (oep != NULL) {
-			if (oep->signature[0] == 'o'
-			    || oep->signature[1] == 'n'
-			    || oep->signature[2] == 'e'
-			    || oep->signature[3] == 'x'
-			    || oep->signature[4] == 'i'
-			    || oep->signature[5] == 't'
-			    || oep->signature[6] == 0) {
-				list = oep->list;
-				free(oep);
+		/* Find this scope in onexit list */
+		for (prev_oes_next = &pt->onexit, oes = pt->onexit; oes; prev_oes_next = &(oes->next), oes = oes->next) {
+			if (oes->scope == obj) {
+				(*prev_oes_next) = oes->next;
+				list = oes->onexit;
+				JS_RemoveObjectRoot(cx, &oes->scope);
+				free(oes);
+				break;
 			}
 		}
+		if (oes == NULL)
+			return;
 	}
 
 	cb->auto_terminate=FALSE;
@@ -831,9 +840,7 @@ void DLLCALL js_EvalOnExit(JSContext *cx, JSObject *obj, js_callback_t* cb)
 	}
 
 	strListFree(&list);
-	if(glob != obj)
-		JS_SetPrivate(cx,obj,NULL);
-	else
+	if(glob == obj)
 		pt->exit_func=NULL;
 
 	if(auto_terminate)
diff --git a/src/sbbs3/sbbs.h b/src/sbbs3/sbbs.h
index 82385ab19992ab4c0b6d5a8287253336f53dafbe..46ff730d9f58dd663fcce7b5b819660b8341446e 100644
--- a/src/sbbs3/sbbs.h
+++ b/src/sbbs3/sbbs.h
@@ -1295,6 +1295,12 @@ extern "C" {
 										,js_server_props_t* props);
 
 	/* js_global.c */
+	struct js_onexit_scope {
+		JSObject *scope;
+		str_list_t onexit;
+		struct js_onexit_scope *next;
+	};
+
 	typedef struct {
 		scfg_t*				cfg;
 		jsSyncMethodSpec*	methods;
@@ -1302,6 +1308,7 @@ extern "C" {
 		unsigned			bg_count;
 		sem_t				bg_sem;
 		str_list_t			exit_func;
+		struct js_onexit_scope	*onexit;
 	} global_private_t;
 	DLLEXPORT BOOL DLLCALL js_argc(JSContext *cx, unsigned argc, unsigned min);
 	DLLEXPORT BOOL DLLCALL js_CreateGlobalObject(JSContext* cx, scfg_t* cfg, jsSyncMethodSpec* methods, js_startup_t*, JSObject**);