diff --git a/src/script/cpp_api/s_env.cpp b/src/script/cpp_api/s_env.cpp
index 82d0d4f0e2062e69ec99c79fe3321357cece117d..913d8539d30f2cfd4008dc53b44dbbeecefc8ef7 100644
--- a/src/script/cpp_api/s_env.cpp
+++ b/src/script/cpp_api/s_env.cpp
@@ -212,11 +212,13 @@ void ScriptApiEnv::on_emerge_area_completion(
 {
 	Server *server = getServer();
 
+	// This function should be executed with envlock held.
+	// The caller (LuaEmergeAreaCallback in src/script/lua_api/l_env.cpp)
+	// should have obtained the lock.
 	// Note that the order of these locks is important!  Envlock must *ALWAYS*
 	// be acquired before attempting to acquire scriptlock, or else ServerThread
 	// will try to acquire scriptlock after it already owns envlock, thus
 	// deadlocking EmergeThread and ServerThread
-	MutexAutoLock envlock(server->m_env_mutex);
 
 	SCRIPTAPI_PRECHECKHEADER
 
diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp
index f6ea23e95826857012b88f971f89a37576de0023..68d10308c9ba14f132b0a6a87b29e82317cd9eba 100644
--- a/src/script/lua_api/l_env.cpp
+++ b/src/script/lua_api/l_env.cpp
@@ -137,6 +137,10 @@ void LuaEmergeAreaCallback(v3s16 blockpos, EmergeAction action, void *param)
 	assert(state->script != NULL);
 	assert(state->refcount > 0);
 
+	// state must be protected by envlock
+	Server *server = state->script->getServer();
+	MutexAutoLock envlock(server->m_env_mutex);
+
 	state->refcount--;
 
 	state->script->on_emerge_area_completion(blockpos, action, state);