diff --git a/keyserver/src/fetchers/user-fetchers.js b/keyserver/src/fetchers/user-fetchers.js
--- a/keyserver/src/fetchers/user-fetchers.js
+++ b/keyserver/src/fetchers/user-fetchers.js
@@ -6,6 +6,10 @@
   directedStatus,
   userRelationshipStatus,
 } from 'lib/types/relationship-types';
+import {
+  communityThreadTypes,
+  threadPermissions,
+} from 'lib/types/thread-types';
 import type {
   UserInfos,
   CurrentUserInfo,
@@ -265,6 +269,27 @@
   return row.username;
 }
 
+async function fetchKeyserverAdminID(): Promise<?string> {
+  const changeRoleExtractString = `$.${threadPermissions.CHANGE_ROLE}`;
+  const query = SQL`
+    SELECT m.user FROM memberships m
+    INNER JOIN roles r ON m.role = r.id
+    INNER JOIN threads t ON r.thread = t.id
+    WHERE r.name = "Admins" AND 
+      t.type IN (${communityThreadTypes}) AND 
+      JSON_EXTRACT(r.permissions, ${changeRoleExtractString}) IS TRUE
+  `;
+  const [result] = await dbQuery(query);
+  if (result.length === 0) {
+    return null;
+  }
+  if (result.length > 1) {
+    console.warn('more than one community admin found');
+  }
+
+  return result[0].user;
+}
+
 export {
   fetchUserInfos,
   fetchLoggedInUserInfo,
@@ -274,4 +299,5 @@
   fetchAllUserIDs,
   fetchUsername,
   fetchKnownUserInfos,
+  fetchKeyserverAdminID,
 };
diff --git a/lib/types/thread-types.js b/lib/types/thread-types.js
--- a/lib/types/thread-types.js
+++ b/lib/types/thread-types.js
@@ -61,13 +61,14 @@
   );
   return threadType;
 }
+export const communityThreadTypes: Array<number> = Object.freeze([
+  threadTypes.COMMUNITY_ROOT,
+  threadTypes.COMMUNITY_ANNOUNCEMENT_ROOT,
+  threadTypes.GENESIS,
+]);
 
 export function threadTypeIsCommunityRoot(threadType: ThreadType): boolean {
-  return (
-    threadType === threadTypes.COMMUNITY_ROOT ||
-    threadType === threadTypes.COMMUNITY_ANNOUNCEMENT_ROOT ||
-    threadType === threadTypes.GENESIS
-  );
+  return communityThreadTypes.includes(threadType);
 }
 
 export const threadPermissions = Object.freeze({