diff --git a/packages/backend/src/core/AccountMoveService.ts b/packages/backend/src/core/AccountMoveService.ts
index 3f2a19b771..78d12ae705 100644
--- a/packages/backend/src/core/AccountMoveService.ts
+++ b/packages/backend/src/core/AccountMoveService.ts
@@ -3,27 +3,43 @@ import { IsNull } from 'typeorm';
 
 import { bindThis } from '@/decorators.js';
 import { DI } from '@/di-symbols.js';
+import type { Config } from '@/config.js';
 import type { LocalUser } from '@/models/entities/User.js';
-import { User } from '@/models/entities/User.js';
-import type { FollowingsRepository, UsersRepository } from '@/models/index.js';
+import type { BlockingsRepository, FollowingsRepository, Muting, MutingsRepository, UsersRepository } from '@/models/index.js';
+import type { RelationshipJobData } from '@/queue/types.js';
 
+import { User } from '@/models/entities/User.js';
+
+import { AccountUpdateService } from '@/core/AccountUpdateService.js';
 import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { QueueService } from '@/core/QueueService.js';
+import { RelayService } from '@/core/RelayService.js';
 import { UserFollowingService } from '@/core/UserFollowingService.js';
 import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js';
 import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
-import { AccountUpdateService } from '@/core/AccountUpdateService.js';
-import { RelayService } from '@/core/RelayService.js';
+import { IdService } from '@/core/IdService.js';
+import { CacheService } from '@/core/CacheService';
 
 @Injectable()
 export class AccountMoveService {
 	constructor(
+		@Inject(DI.config)
+		private config: Config,
+
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
 
 		@Inject(DI.followingsRepository)
 		private followingsRepository: FollowingsRepository,
 
+		@Inject(DI.blockingsRepository)
+		private blockingsRepository: BlockingsRepository,
+
+		@Inject(DI.mutingsRepository)
+		private mutingsRepository: MutingsRepository,
+
+		private idService: IdService,
 		private userEntityService: UserEntityService,
 		private apRendererService: ApRendererService,
 		private apDeliverManagerService: ApDeliverManagerService,
@@ -31,18 +47,18 @@ export class AccountMoveService {
 		private userFollowingService: UserFollowingService,
 		private accountUpdateService: AccountUpdateService,
 		private relayService: RelayService,
+		private cacheService: CacheService,
+		private queueService: QueueService,
 	) {
 	}
 
 	/**
-	 * Move a local account to a remote account.
+	 * Move a local account to a new account.
 	 *
 	 * After delivering Move activity, its local followers unfollow the old account and then follow the new one.
 	 */
 	@bindThis
-	public async moveToRemote(src: LocalUser, dst: User): Promise<unknown> {
-		// Make sure that the destination is a remote account.
-		if (this.userEntityService.isLocalUser(dst)) throw new Error('move destiantion is not remote');
+	public async moveFromLocal(src: LocalUser, dst: User): Promise<unknown> {
 		if (!dst.uri) throw new Error('destination uri is empty');
 
 		// add movedToUri to indicate that the user has moved
@@ -64,25 +80,8 @@ export class AccountMoveService {
 		const iObj = await this.userEntityService.pack<true, true>(src.id, src, { detail: true, includeSecrets: true });
 		this.globalEventService.publishMainStream(src.id, 'meUpdated', iObj);
 
-		// follow the new account and unfollow the old one
-		const followings = await this.followingsRepository.find({
-			relations: {
-				follower: true,
-			},
-			where: {
-				followeeId: src.id,
-				followerHost: IsNull(), // follower is local
-			},
-		});
-		for (const following of followings) {
-			if (!following.follower) continue;
-			try {
-				await this.userFollowingService.follow(following.follower, dst);
-				await this.userFollowingService.unfollow(following.follower, src);
-			} catch {
-				/* empty */
-			}
-		}
+		// Move!
+		await this.move(src, dst);
 
 		return iObj;
 	}
@@ -111,4 +110,74 @@ export class AccountMoveService {
 
 		return iObj;
 	}
+
+	@bindThis
+	public async move(src: User, dst: User): Promise<void> {
+		// Copy blockings:
+		// Followers shouldn't overlap with blockers, but the destination account, different from the blockee (i.e., old account), may have followed the local user before moving.
+		// So block the destination account here.
+		const blockings = await this.blockingsRepository.find({
+			relations: {
+				blocker: true
+			},
+			where: {
+				blockeeId: src.id
+			}
+		})
+		// reblock the destination account
+		const blockJobs: RelationshipJobData[] = [];
+		for (const blocking of blockings) {
+			if (!blocking.blocker) continue;
+			blockJobs.push({ from: blocking.blocker, to: dst });
+		}
+		// no need to unblock the old account because it may be still functional
+		this.queueService.createBlockJob(blockJobs);
+
+		// Copy mutings:
+		// Insert new mutings with the same values except mutee
+		const mutings = await this.mutingsRepository.findBy({ muteeId: src.id });
+		const newMuting: Partial<Muting>[] = [];
+		for (const muting of mutings) {
+			newMuting.push({
+				id: this.idService.genId(),
+				createdAt: new Date(),
+				expiresAt: muting.expiresAt,
+				muterId: muting.muterId,
+				muteeId: dst.id,
+			})
+		}
+		this.mutingsRepository.insert(mutings); // no need to wait
+		for (const mute of mutings) {
+			if (mute.muter) this.cacheService.userMutingsCache.refresh(mute.muter.id);
+		}
+		// no need to unmute the old account because it may be still functional
+
+		// follow the new account and unfollow the old one
+		const followings = await this.followingsRepository.find({
+			relations: {
+				follower: true,
+			},
+			where: {
+				followeeId: src.id,
+				followerHost: IsNull(), // follower is local
+			},
+		});
+		const followJobs: RelationshipJobData[] = [];
+		const unfollowJobs: RelationshipJobData[] = [];
+		for (const following of followings) {
+			if (!following.follower) continue;
+			followJobs.push({ from: following.follower, to: dst });
+			unfollowJobs.push({ from: following.follower, to: src });
+		}
+		// Should be queued because this can cause a number of follow/unfollow per one move.
+		// No need to care job orders as there should be no overlaps of follow/unfollow target.
+		this.queueService.createFollowJob(followJobs);
+		this.queueService.createUnfollowJob(unfollowJobs);
+	}
+
+	@bindThis
+	public getUserUri(user: User): string {
+			return this.userEntityService.isRemoteUser(user)
+				? user.uri : `${this.config.url}/users/${user.id}`;
+	}
 }
diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts
index 3fca0bb1fd..982487e5f5 100644
--- a/packages/backend/src/core/activitypub/ApInboxService.ts
+++ b/packages/backend/src/core/activitypub/ApInboxService.ts
@@ -1,5 +1,5 @@
 import { Inject, Injectable } from '@nestjs/common';
-import { In, IsNull } from 'typeorm';
+import { In } from 'typeorm';
 import { DI } from '@/di-symbols.js';
 import type { Config } from '@/config.js';
 import { UserFollowingService } from '@/core/UserFollowingService.js';
@@ -13,13 +13,15 @@ import { concat, toArray, toSingle, unique } from '@/misc/prelude/array.js';
 import { AppLockService } from '@/core/AppLockService.js';
 import type Logger from '@/logger.js';
 import { MetaService } from '@/core/MetaService.js';
+import { AccountMoveService } from '@/core/AccountMoveService.js';
 import { IdService } from '@/core/IdService.js';
 import { StatusError } from '@/misc/status-error.js';
 import { UtilityService } from '@/core/UtilityService.js';
+import { CacheService } from '@/core/CacheService.js';
 import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { QueueService } from '@/core/QueueService.js';
-import type { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserReportsRepository, FollowRequestsRepository } from '@/models/index.js';
+import type { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserReportsRepository, FollowRequestsRepository, } from '@/models/index.js';
 import { bindThis } from '@/decorators.js';
 import type { RemoteUser } from '@/models/entities/User.js';
 import { getApHrefNullable, getApId, getApIds, getApType, getOneApHrefNullable, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isMove, isPost, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js';
@@ -76,6 +78,8 @@ export class ApInboxService {
 		private apNoteService: ApNoteService,
 		private apPersonService: ApPersonService,
 		private apQuestionService: ApQuestionService,
+		private accountMoveService: AccountMoveService,
+		private cacheService: CacheService,
 		private queueService: QueueService,
 	) {
 		this.logger = this.apLoggerService.logger;
@@ -140,7 +144,7 @@ export class ApInboxService {
 		} else if (isFlag(activity)) {
 			await this.flag(actor, activity);
 		} else if (isMove(activity)) {
-			//await this.move(actor, activity);
+			await this.move(actor, activity);
 		} else {
 			this.logger.warn(`unrecognized activity type: ${activity.type}`);
 		}
@@ -158,6 +162,7 @@ export class ApInboxService {
 			return 'skip: フォローしようとしているユーザーはローカルユーザーではありません';
 		}
 
+		// don't queue because the sender may attempt again when timeout
 		await this.userFollowingService.follow(actor, followee, activity.id);
 		return 'ok';
 	}
@@ -596,6 +601,7 @@ export class ApInboxService {
 			throw e;
 		});
 
+		// don't queue because the sender may attempt again when timeout
 		if (isFollow(object)) return await this.undoFollow(actor, object);
 		if (isBlock(object)) return await this.undoBlock(actor, object);
 		if (isLike(object)) return await this.undoLike(actor, object);
@@ -736,52 +742,37 @@ export class ApInboxService {
 		// fetch the new and old accounts
 		const targetUri = getApHrefNullable(activity.target);
 		if (!targetUri) return 'skip: invalid activity target';
-		let new_acc = await this.apPersonService.resolvePerson(targetUri);
-		let old_acc = await this.apPersonService.resolvePerson(actor.uri);
+		let newAccount = await this.apPersonService.resolvePerson(targetUri);
+		let oldAccount = await this.apPersonService.resolvePerson(actor.uri);
 
 		// update them if they're remote
-		if (new_acc.uri) await this.apPersonService.updatePerson(new_acc.uri);
-		if (old_acc.uri) await this.apPersonService.updatePerson(old_acc.uri);
-
-		// retrieve updated users
-		new_acc = await this.apPersonService.resolvePerson(targetUri);
-		old_acc = await this.apPersonService.resolvePerson(actor.uri);
+		if (newAccount.uri) {
+ 			await this.apPersonService.updatePerson(newAccount.uri);
+			newAccount = await this.apPersonService.resolvePerson(newAccount.uri);
+		}
+		if (oldAccount.uri) {
+ 			await this.apPersonService.updatePerson(oldAccount.uri);
+			oldAccount = await this.apPersonService.resolvePerson(oldAccount.uri);
+		}
 
 		// check if alsoKnownAs of the new account is valid
 		let isValidMove = true;
-		if (old_acc.uri) {
-			if (!new_acc.alsoKnownAs?.includes(old_acc.uri)) {
+		if (oldAccount.uri) {
+			if (!newAccount.alsoKnownAs?.includes(oldAccount.uri)) {
 				isValidMove = false;
 			}
-		} else if (!new_acc.alsoKnownAs?.includes(old_acc.id)) {
+		} else if (!newAccount.alsoKnownAs?.includes(this.accountMoveService.getUserUri(oldAccount))) {
 			isValidMove = false;
 		}
 		if (!isValidMove) {
-			return 'skip: accounts invalid';
+			return 'skip: destination account invalid';
 		}
 
 		// add target uri to movedToUri in order to indicate that the user has moved
-		await this.usersRepository.update(old_acc.id, { movedToUri: targetUri });
+		this.usersRepository.update(oldAccount.id, { movedToUri: targetUri });
 
-		// follow the new account and unfollow the old one
-		const followings = await this.followingsRepository.find({
-			relations: {
-				follower: true,
-			},
-			where: {
-				followeeId: old_acc.id,
-				followerHost: IsNull(), // follower is local
-			},
-		});
-		for (const following of followings) {
-			if (!following.follower) continue;
-			try {
-				await this.userFollowingService.follow(following.follower, new_acc);
-				await this.userFollowingService.unfollow(following.follower, old_acc);
-			} catch {
-				/* empty */
-			}
-		}
+		// Move!
+		await this.accountMoveService.move(oldAccount, newAccount);
 
 		return 'ok';
 	}
diff --git a/packages/backend/src/server/api/endpoints/i/known-as.ts b/packages/backend/src/server/api/endpoints/i/known-as.ts
index 964704d82b..7aa401e9bb 100644
--- a/packages/backend/src/server/api/endpoints/i/known-as.ts
+++ b/packages/backend/src/server/api/endpoints/i/known-as.ts
@@ -27,11 +27,6 @@ export const meta = {
 			code: 'NO_SUCH_USER',
 			id: 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5',
 		},
-		notRemote: {
-			message: 'User is not remote. You can only migrate from other instances.',
-			code: 'NOT_REMOTE',
-			id: '4362f8dc-731f-4ad8-a694-be2a88922a24',
-		},
 		uriNull: {
 			message: 'User ActivityPup URI is null.',
 			code: 'URI_NULL',
@@ -69,19 +64,17 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				// Parse user's input into the old account
 				if (unfiltered.startsWith('acct:')) unfiltered = unfiltered.substring(5);
 				if (unfiltered.startsWith('@')) unfiltered = unfiltered.substring(1);
-				if (!unfiltered.includes('@')) throw new ApiError(meta.errors.notRemote);
+				if (!unfiltered.includes('@')) throw new ApiError(meta.errors.noSuchUser);
 
 				const userAddress = unfiltered.split('@');
 				// Retrieve the old account
 				const knownAs = await this.remoteUserResolveService.resolveUser(userAddress[0], userAddress[1]).catch((e) => {
-					this.apiLoggerService.logger.warn(`failed to resolve remote user: ${e}`);
+					this.apiLoggerService.logger.warn(`failed to resolve dstination user: ${e}`);
 					throw new ApiError(meta.errors.noSuchUser);
 				});
 
-				const toUrl: string | null = knownAs.uri;
+				const toUrl = this.accountMoveService.getUserUri(knownAs);
 				if (!toUrl) throw new ApiError(meta.errors.uriNull);
-				// Only allow moving from a remote account
-				if (this.userEntityService.isLocalUser(knownAs)) throw new ApiError(meta.errors.notRemote);
 
 				updates.alsoKnownAs = updates.alsoKnownAs?.concat([toUrl]) ?? [toUrl];
 			}
diff --git a/packages/backend/src/server/api/endpoints/i/move.ts b/packages/backend/src/server/api/endpoints/i/move.ts
index ac76e1f620..53195d9c63 100644
--- a/packages/backend/src/server/api/endpoints/i/move.ts
+++ b/packages/backend/src/server/api/endpoints/i/move.ts
@@ -30,17 +30,12 @@ export const meta = {
 			code: 'NO_SUCH_MOVE_TARGET',
 			id: 'b5c90186-4ab0-49c8-9bba-a1f76c202ba4',
 		},
-		remoteAccountForbids: {
+		destinationAccountForbids: {
 			message:
-				'Remote account doesn\'t have proper \'Known As\' alias. Did you remember to set it?',
+				'Destination account doesn\'t have proper \'Known As\' alias. Did you remember to set it?',
 			code: 'REMOTE_ACCOUNT_FORBIDS',
 			id: 'b5c90186-4ab0-49c8-9bba-a1f766282ba4',
 		},
-		notRemote: {
-			message: 'User is not remote. You can only migrate to other instances.',
-			code: 'NOT_REMOTE',
-			id: '4362f8dc-731f-4ad8-a694-be2a88922a24',
-		},
 		rootForbidden: {
 			message: 'The root can\'t migrate.',
 			code: 'NOT_ROOT_FORBIDDEN',
@@ -105,7 +100,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 			// parse user's input into the destination account
 			if (unfiltered.startsWith('acct:')) unfiltered = unfiltered.substring(5);
 			if (unfiltered.startsWith('@')) unfiltered = unfiltered.substring(1);
-			if (!unfiltered.includes('@')) throw new ApiError(meta.errors.notRemote);
+			if (!unfiltered.includes('@')) throw new ApiError(meta.errors.noSuchMoveTarget);
 
 			const userAddress = unfiltered.split('@');
 			// retrieve the destination account
@@ -113,28 +108,25 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				this.apiLoggerService.logger.warn(`failed to resolve remote user: ${e}`);
 				throw new ApiError(meta.errors.noSuchMoveTarget);
 			});
-			const remoteMoveTo = await this.getterService.getRemoteUser(moveTo.id);
-			if (!remoteMoveTo.uri) throw new ApiError(meta.errors.uriNull);
+			const destination = await this.getterService.getUser(moveTo.id);
+			moveTo.uri = this.accountMoveService.getUserUri(destination)
 
 			// update local db
-			await this.apPersonService.updatePerson(remoteMoveTo.uri);
+			await this.apPersonService.updatePerson(moveTo.uri);
 			// retrieve updated user
-			moveTo = await this.apPersonService.resolvePerson(remoteMoveTo.uri);
-			// only allow moving to a remote account
-			if (this.userEntityService.isLocalUser(moveTo)) throw new ApiError(meta.errors.notRemote);
+			moveTo = await this.apPersonService.resolvePerson(moveTo.uri);
 
-			let allowed = false;
-
-			const fromUrl = `${this.config.url}/users/${me.id}`;
 			// make sure that the user has indicated the old account as an alias
+			const fromUrl = `${this.config.url}/users/${me.id}`;
+			let allowed = false;
 			moveTo.alsoKnownAs?.forEach((elem) => {
 				if (fromUrl.includes(elem)) allowed = true;
 			});
 
 			// abort if unintended
-			if (!(allowed && moveTo.uri && fromUrl)) throw new ApiError(meta.errors.remoteAccountForbids);
+			if (!(allowed && moveTo.uri && fromUrl)) throw new ApiError(meta.errors.destinationAccountForbids);
 
-			return await this.accountMoveService.moveToRemote(me, moveTo);
+			return await this.accountMoveService.moveFromLocal(me, moveTo);
 		});
 	}
 }