diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 57298f1f35..119fe791b4 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1383,6 +1383,7 @@ admin/views/federation.vue:
   latest-request-received-at: "直近のリクエスト受信"
   remove-all-following: "フォローを全解除"
   remove-all-following-info: "{host}からのフォローをすべて解除します。そのインスタンスがもう存在しなくなった場合などに実行してください。"
+  block: "ブロック"
   lookup: "照会"
   instances: "インスタンス"
   instance-not-registered: "そのインスタンスは登録されていません"
@@ -1398,6 +1399,11 @@ admin/views/federation.vue:
     followingDesc: "フォローが多い順"
     followersAsc: "フォロワーが少ない順"
     followersDesc: "フォロワーが多い順"
+  state: "状態"
+  states:
+    all: "すべて"
+    blocked: "ブロック"
+  result-is-truncated: "上位{n}件を表示しています。"
 
 desktop/views/pages/welcome.vue:
   about: "詳しく..."
diff --git a/src/client/app/admin/views/federation.vue b/src/client/app/admin/views/federation.vue
index 754a70f525..80b9e9541f 100644
--- a/src/client/app/admin/views/federation.vue
+++ b/src/client/app/admin/views/federation.vue
@@ -39,6 +39,7 @@
 				<ui-input :value="instance.latestRequestReceivedAt" type="text" readonly>
 					<span>{{ $t('latest-request-received-at') }}</span>
 				</ui-input>
+				<ui-switch v-model="instance.isBlocked" @change="updateInstance()">{{ $t('block') }}</ui-switch>
 				<section>
 					<ui-button @click="removeAllFollowing()"><fa :icon="faMinusCircle"/> {{ $t('remove-all-following') }}</ui-button>
 					<ui-info warn>{{ $t('remove-all-following-info', { host: instance.host }) }}</ui-info>
@@ -64,6 +65,11 @@
 					<option value="-followers">{{ $t('sorts.followersAsc') }}</option>
 					<option value="+followers">{{ $t('sorts.followersDesc') }}</option>
 				</ui-select>
+				<ui-select v-model="state">
+					<span slot="label">{{ $t('state') }}</span>
+					<option value="all">{{ $t('states.all') }}</option>
+					<option value="blocked">{{ $t('states.blocked') }}</option>
+				</ui-select>
 			</ui-horizon-group>
 
 			<div class="instances">
@@ -84,6 +90,8 @@
 					<span>{{ instance.latestStatus }}</span>
 				</div>
 			</div>
+
+			<ui-info v-if="instances.length == limit">{{ $t('result-is-truncated', { n: limit }) }}</ui-info>
 		</section>
 	</ui-card>
 </div>
@@ -102,6 +110,7 @@ export default Vue.extend({
 			instance: null,
 			target: null,
 			sort: '+caughtAt',
+			state: 'all',
 			limit: 50,
 			instances: [],
 			faGlobe, faTerminal, faSearch, faMinusCircle
@@ -110,7 +119,10 @@ export default Vue.extend({
 
 	watch: {
 		sort() {
-			this.instances = [];
+			this.fetchInstances();
+		},
+
+		state() {
 			this.fetchInstances();
 		},
 	},
@@ -137,9 +149,11 @@ export default Vue.extend({
 		},
 
 		fetchInstances() {
+			this.instances = [];
 			this.$root.api('federation/instances', {
+				state: this.state,
 				sort: this.sort,
-				limit: 50
+				limit: this.limit
 			}).then(instances => {
 				this.instances = instances;
 			});
@@ -154,7 +168,14 @@ export default Vue.extend({
 					splash: true
 				});
 			});
-		}
+		},
+
+		updateInstance() {
+			this.$root.api('admin/federation/update-instance', {
+				host: this.instance.host,
+				isBlocked: this.instance.isBlocked,
+			});
+		},
 	}
 });
 </script>
diff --git a/src/models/instance.ts b/src/models/instance.ts
index 904ed95dc0..242e80f300 100644
--- a/src/models/instance.ts
+++ b/src/models/instance.ts
@@ -57,4 +57,9 @@ export interface IInstance {
 	 * 直近のリクエスト受信日時
 	 */
 	latestRequestReceivedAt?: Date;
+
+	/**
+	 * このインスタンスをブロックしているか
+	 */
+	isBlocked: boolean;
 }
diff --git a/src/queue/processors/http/process-inbox.ts b/src/queue/processors/http/process-inbox.ts
index d88f00a098..583e255136 100644
--- a/src/queue/processors/http/process-inbox.ts
+++ b/src/queue/processors/http/process-inbox.ts
@@ -45,6 +45,15 @@ export default async (job: bq.Job, done: any): Promise<void> => {
 			return;
 		}
 
+		// ブロックしてたら中断
+		// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
+		const instance = await Instance.findOne({ host: host.toLowerCase() });
+		if (instance && instance.isBlocked) {
+			logger.warn(`Blocked request: ${host}`);
+			done();
+			return;
+		}
+
 		user = await User.findOne({ usernameLower: username, host: host.toLowerCase() }) as IRemoteUser;
 	} else {
 		// アクティビティ内のホストの検証
@@ -57,6 +66,15 @@ export default async (job: bq.Job, done: any): Promise<void> => {
 			return;
 		}
 
+		// ブロックしてたら中断
+		// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
+		const instance = await Instance.findOne({ host: host.toLowerCase() });
+		if (instance && instance.isBlocked) {
+			logger.warn(`Blocked request: ${host}`);
+			done();
+			return;
+		}
+
 		user = await User.findOne({
 			host: { $ne: null },
 			'publicKey.id': signature.keyId
diff --git a/src/remote/activitypub/request.ts b/src/remote/activitypub/request.ts
index 7264bef24c..df8eced13d 100644
--- a/src/remote/activitypub/request.ts
+++ b/src/remote/activitypub/request.ts
@@ -4,11 +4,13 @@ import { URL } from 'url';
 import * as crypto from 'crypto';
 import { lookup, IRunOptions } from 'lookup-dns-cache';
 import * as promiseAny from 'promise-any';
+import { toUnicode } from 'punycode';
 
 import config from '../../config';
 import { ILocalUser } from '../../models/user';
 import { publishApLogStream } from '../../services/stream';
 import { apLogger } from './logger';
+import Instance from '../../models/instance';
 
 export const logger = apLogger.createSubLogger('deliver');
 
@@ -19,6 +21,11 @@ export default (user: ILocalUser, url: string, object: any) => new Promise(async
 
 	const { protocol, host, hostname, port, pathname, search } = new URL(url);
 
+	// ブロックしてたら中断
+	// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
+	const instance = await Instance.findOne({ host: toUnicode(host) });
+	if (instance && instance.isBlocked) return;
+
 	const data = JSON.stringify(object);
 
 	const sha256 = crypto.createHash('sha256');
diff --git a/src/server/api/endpoints/admin/federation/update-instance.ts b/src/server/api/endpoints/admin/federation/update-instance.ts
new file mode 100644
index 0000000000..de40480a49
--- /dev/null
+++ b/src/server/api/endpoints/admin/federation/update-instance.ts
@@ -0,0 +1,34 @@
+import $ from 'cafy';
+import define from '../../../define';
+import Instance from '../../../../../models/instance';
+
+export const meta = {
+	requireCredential: true,
+	requireModerator: true,
+
+	params: {
+		host: {
+			validator: $.str
+		},
+
+		isBlocked: {
+			validator: $.bool
+		},
+	}
+};
+
+export default define(meta, (ps, me) => new Promise(async (res, rej) => {
+	const instance = await Instance.findOne({ host: ps.host });
+
+	if (instance == null) {
+		return rej('instance not found');
+	}
+
+	Instance.update({ host: ps.host }, {
+		$set: {
+			isBlocked: ps.isBlocked
+		}
+	});
+
+	res();
+}));
diff --git a/src/server/api/endpoints/federation/instances.ts b/src/server/api/endpoints/federation/instances.ts
index 723cbe8fd5..ce0d10af28 100644
--- a/src/server/api/endpoints/federation/instances.ts
+++ b/src/server/api/endpoints/federation/instances.ts
@@ -6,6 +6,10 @@ export const meta = {
 	requireCredential: false,
 
 	params: {
+		state: {
+			validator: $.str.optional,
+		},
+
 		limit: {
 			validator: $.num.optional.range(1, 100),
 			default: 30
@@ -73,8 +77,14 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
 		};
 	}
 
+	const q = {} as any;
+
+	if (ps.state === 'blocked') {
+		q.isBlocked = true;
+	}
+
 	const instances = await Instance
-		.find({}, {
+		.find(q, {
 			limit: ps.limit,
 			sort: sort,
 			skip: ps.offset