diff --git a/src/server.h b/src/server.h
index 51d52d443..6b48d929d 100644
--- a/src/server.h
+++ b/src/server.h
@@ -35,6 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "util/metricsbackend.h"
 #include "serverenvironment.h"
 #include "server/clientiface.h"
+#include "threading/ordered_mutex.h"
 #include "chatmessage.h"
 #include "sound.h"
 #include "translation.h"
@@ -430,7 +431,7 @@ public:
 		EnvAutoLock(Server *server): m_lock(server->m_env_mutex) {}
 
 	private:
-		MutexAutoLock m_lock;
+		std::lock_guard<ordered_mutex> m_lock;
 	};
 
 protected:
@@ -608,7 +609,7 @@ private:
 	*/
 
 	// Environment mutex (envlock)
-	std::mutex m_env_mutex;
+	ordered_mutex m_env_mutex;
 
 	// World directory
 	std::string m_path_world;
diff --git a/src/threading/ordered_mutex.h b/src/threading/ordered_mutex.h
new file mode 100644
index 000000000..f7fb4d309
--- /dev/null
+++ b/src/threading/ordered_mutex.h
@@ -0,0 +1,46 @@
+// Minetest
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#pragma once
+
+#include <cstdint>
+#include <condition_variable>
+
+/*
+	Fair mutex based on ticketing approach.
+	Satisfies `Mutex` C++11 requirements.
+*/
+class ordered_mutex {
+public:
+	ordered_mutex() : next_ticket(0), counter(0) {}
+
+	void lock()
+	{
+		std::unique_lock autolock(cv_lock);
+		const auto ticket = next_ticket++;
+		cv.wait(autolock, [&] { return counter == ticket; });
+	}
+
+	bool try_lock()
+	{
+		std::lock_guard autolock(cv_lock);
+		if (counter != next_ticket)
+			return false;
+		next_ticket++;
+		return true;
+	}
+
+	void unlock()
+	{
+		{
+			std::lock_guard autolock(cv_lock);
+			counter++;
+		}
+		cv.notify_all(); // intentionally outside lock
+	}
+
+private:
+	std::condition_variable cv;
+	std::mutex cv_lock;
+	uint_fast32_t next_ticket, counter;
+};