diff --git a/locales/en-US.yml b/locales/en-US.yml
index 1fb581d8df..527beb4714 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -1212,6 +1212,10 @@ useGroupedNotifications: "Display grouped notifications"
 signupPendingError: "There was a problem verifying the email address. The link may have expired."
 cwNotationRequired: "If \"Hide content\" is enabled, a description must be provided."
 doReaction: "Add reaction"
+wellKnownWebsites: "Well-known websites"
+wellKnownWebsitesDescription: "Separate with spaces for AND, new lines for OR. Surround with slashes for regular expressions. Matching will allow redirection to external sites without a warning."
+warningRedirectingExternalWebsiteTitle: "You are leaving our site!"
+warningRedirectingExternalWebsiteDescription: "You are about to jump to another site.\nPlease make sure this link is reliable before proceeding.\n\n{url}"
 code: "Code"
 reloadRequiredToApplySettings: "Reloading is required to apply the settings."
 remainingN: "Remaining: {n}"
diff --git a/locales/index.d.ts b/locales/index.d.ts
index 44b24d83ce..bcca401dab 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -4860,6 +4860,25 @@ export interface Locale extends ILocale {
      * リアクションする
      */
     "doReaction": string;
+    /**
+     * よく知られたウェブサイト
+     */
+    "wellKnownWebsites": string;
+    /**
+     * スペースで区切るとAND指定になり、改行で区切るとOR指定になります。スラッシュで囲むと正規表現になります。一致した場合、外部サイトへのリダイレクトの警告を省略させることができます。
+     */
+    "wellKnownWebsitesDescription": string;
+    /**
+     * 外部サイトへ移動します
+     */
+    "warningRedirectingExternalWebsiteTitle": string;
+    /**
+     * 別のサイトにジャンプしようとしています。
+     * リンク先の安全性を十分に確認した上で進んでください。
+     *
+     * {url}
+     */
+    "warningRedirectingExternalWebsiteDescription": ParameterizedString<"url">;
     /**
      * サムネイルの表示を制限するURL
      */
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index fd84e0318d..d30ded3a52 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1211,6 +1211,10 @@ useGroupedNotifications: "通知をグルーピングして表示する"
 signupPendingError: "メールアドレスの確認中に問題が発生しました。リンクの有効期限が切れている可能性があります。"
 cwNotationRequired: "「内容を隠す」がオンの場合は注釈の記述が必要です。"
 doReaction: "リアクションする"
+wellKnownWebsites: "よく知られたウェブサイト"
+wellKnownWebsitesDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります。スラッシュで囲むと正規表現になります。一致した場合、外部サイトへのリダイレクトの警告を省略させることができます。"
+warningRedirectingExternalWebsiteTitle: "外部サイトへ移動します"
+warningRedirectingExternalWebsiteDescription: "別のサイトにジャンプしようとしています。\nリンク先の安全性を十分に確認した上で進んでください。\n\n{url}"
 urlPreviewDenyList: "サムネイルの表示を制限するURL"
 urlPreviewDenyListDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります。スラッシュで囲むと正規表現になります。一致した場合、サムネイルがぼかされて表示されます。"
 code: "コード"
diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml
index 5b3d5538a0..e105a0df1e 100644
--- a/locales/ko-KR.yml
+++ b/locales/ko-KR.yml
@@ -1209,6 +1209,10 @@ useGroupedNotifications: "알림을 그룹화하고 표시"
 signupPendingError: "메일 주소 확인중에 문제가 발생했습니다. 링크의 유효기간이 지났을 가능성이 있습니다."
 cwNotationRequired: "'내용을 숨기기'를 체크한 경우 주석을 써야 합니다."
 doReaction: "리액션 추가"
+wellKnownWebsites: "잘 알려진 웹사이트"
+wellKnownWebsitesDescription: "공백으로 구분하면 AND 지정이 되며, 개행으로 구분하면 OR 지정이 됩니다. 슬래시로 둘러싸면 정규 표현식이 됩니다. 일치하는 경우, 외부 사이트로의 리다이렉트 경고를 생략할 수 있습니다."
+warningRedirectingExternalWebsiteTitle: "외부 사이트로 이동합니다"
+warningRedirectingExternalWebsiteDescription: "다른 사이트로 이동하려고 합니다.\n링크가 안전한지 충분히 확인한 후 이동해주세요.\n\n{url}"
 code: "문자열"
 reloadRequiredToApplySettings: "설정을 적용하려면 새로고침을 해야 합니다."
 remainingN: "나머지: {n}"
diff --git a/packages/backend/migration/1711008460816-external-website-warn.js b/packages/backend/migration/1711008460816-external-website-warn.js
new file mode 100644
index 0000000000..6e6ae75ef6
--- /dev/null
+++ b/packages/backend/migration/1711008460816-external-website-warn.js
@@ -0,0 +1,11 @@
+export class ExternalWebsiteWarn1711008460816 {
+    name = 'ExternalWebsiteWarn1711008460816'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" ADD "wellKnownWebsites" character varying(3072) array NOT NULL DEFAULT '{}'`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "wellKnownWebsites"`);
+    }
+}
diff --git a/packages/backend/src/core/entities/MetaEntityService.ts b/packages/backend/src/core/entities/MetaEntityService.ts
index b21185183d..ad35a440d5 100644
--- a/packages/backend/src/core/entities/MetaEntityService.ts
+++ b/packages/backend/src/core/entities/MetaEntityService.ts
@@ -99,6 +99,7 @@ export class MetaEntityService {
 				imageUrl: ad.imageUrl,
 				dayOfWeek: ad.dayOfWeek,
 			})),
+			wellKnownWebsites: instance.wellKnownWebsites,
 			notesPerOneAd: instance.notesPerOneAd,
 			enableEmail: instance.enableEmail,
 			enableServiceWorker: instance.enableServiceWorker,
diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts
index 3255117d88..08451cff3a 100644
--- a/packages/backend/src/models/Meta.ts
+++ b/packages/backend/src/models/Meta.ts
@@ -594,6 +594,11 @@ export class MiMeta {
 	})
 	public notesPerOneAd: number;
 
+	@Column('varchar', {
+		length: 3072, array: true, default: '{}',
+	})
+	public wellKnownWebsites: string[];
+
 	@Column('varchar', {
 		length: 3072, array: true, default: '{}',
 	})
diff --git a/packages/backend/src/models/json-schema/meta.ts b/packages/backend/src/models/json-schema/meta.ts
index 150b94a18f..dd440b4b18 100644
--- a/packages/backend/src/models/json-schema/meta.ts
+++ b/packages/backend/src/models/json-schema/meta.ts
@@ -183,6 +183,14 @@ export const packedMetaLiteSchema = {
 				},
 			},
 		},
+		wellKnownWebsites: {
+			type: 'array',
+			optional: false, nullable: false,
+			items: {
+				type: 'string',
+				optional: false, nullable: false,
+			},
+		},
 		notesPerOneAd: {
 			type: 'number',
 			optional: false, nullable: false,
diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts
index 107608d791..2d533b2557 100644
--- a/packages/backend/src/server/api/endpoints/admin/meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/meta.ts
@@ -381,9 +381,17 @@ export const meta = {
 				type: 'number',
 				optional: false, nullable: false,
 			},
+			wellKnownWebsites: {
+				type: 'array',
+				optional: false, nullable: false,
+				items: {
+					type: 'string',
+					optional: false, nullable: false,
+				},
+			},
 			urlPreviewDenyList: {
 				type: 'array',
-				optional: true, nullable: false,
+				optional: false, nullable: false,
 				items: {
 					type: 'string',
 					optional: false, nullable: false,
@@ -602,6 +610,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				perRemoteUserUserTimelineCacheMax: instance.perRemoteUserUserTimelineCacheMax,
 				perUserHomeTimelineCacheMax: instance.perUserHomeTimelineCacheMax,
 				perUserListTimelineCacheMax: instance.perUserListTimelineCacheMax,
+				wellKnownWebsites: instance.wellKnownWebsites,
 				notesPerOneAd: instance.notesPerOneAd,
 				urlPreviewDenyList: instance.urlPreviewDenyList,
 				featuredGameChannels: instance.featuredGameChannels,
diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
index 439116a1a7..baad5eff7a 100644
--- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
@@ -155,6 +155,11 @@ export const paramDef = {
 				type: 'string',
 			},
 		},
+		wellKnownWebsites: {
+			type: 'array', nullable: true, items: {
+				type: 'string',
+			},
+		},
 		urlPreviewDenyList: {
 			type: 'array', nullable: true, items: {
 				type: 'string',
@@ -220,6 +225,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				}).map(x => x.toLowerCase());
 			}
 
+			if (Array.isArray(ps.wellKnownWebsites)) {
+				set.wellKnownWebsites = ps.wellKnownWebsites.filter(Boolean);
+			}
+
 			if (Array.isArray(ps.urlPreviewDenyList)) {
 				set.urlPreviewDenyList = ps.urlPreviewDenyList.filter(Boolean);
 			}
diff --git a/packages/frontend/src/components/MkLink.vue b/packages/frontend/src/components/MkLink.vue
index 3f7aba2fe4..3ff5fcc752 100644
--- a/packages/frontend/src/components/MkLink.vue
+++ b/packages/frontend/src/components/MkLink.vue
@@ -5,8 +5,15 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <component
-	:is="self ? 'MkA' : 'a'" ref="el" style="word-break: break-all;" class="_link" :[attr]="self ? url.substring(local.length) : url" :rel="rel ?? 'nofollow noopener'" :target="target"
+	:is="self ? 'MkA' : 'a'"
+	ref="el"
+	style="word-break: break-all;"
+	class="_link"
+	:[attr]="self ? url.substring(local.length) : url"
+	:rel="rel ?? 'nofollow noopener'"
+	:target="target"
 	:title="url"
+	@click="(ev: MouseEvent) => warningExternalWebsite(ev, url)"
 >
 	<slot></slot>
 	<i v-if="target === '_blank'" class="ti ti-external-link" :class="$style.icon"></i>
@@ -17,6 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { defineAsyncComponent, ref } from 'vue';
 import { url as local } from '@/config.js';
 import { useTooltip } from '@/scripts/use-tooltip.js';
+import { warningExternalWebsite } from '@/scripts/warning-external-website.js';
 import * as os from '@/os.js';
 
 const props = withDefaults(defineProps<{
diff --git a/packages/frontend/src/components/global/MkUrl.vue b/packages/frontend/src/components/global/MkUrl.vue
index 8d29a4da8c..c400be3da2 100644
--- a/packages/frontend/src/components/global/MkUrl.vue
+++ b/packages/frontend/src/components/global/MkUrl.vue
@@ -5,7 +5,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <component
-	:is="self ? 'MkA' : 'a'" ref="el" :class="$style.root" class="_link" :[attr]="self ? props.url.substring(local.length) : props.url" :rel="rel ?? 'nofollow noopener'" :target="target"
+	:is="self ? 'MkA' : 'a'"
+	ref="el"
+	:class="$style.root"
+	class="_link"
+	:[attr]="self ? props.url.substring(local.length) : props.url"
+	:rel="rel ?? 'nofollow noopener'"
+	:target="target"
+	@click="(ev: MouseEvent) => warningExternalWebsite(ev, props.url)"
 	@contextmenu.stop="() => {}"
 >
 	<template v-if="!self">
@@ -30,6 +37,7 @@ import { url as local } from '@/config.js';
 import * as os from '@/os.js';
 import { useTooltip } from '@/scripts/use-tooltip.js';
 import { safeURIDecode } from '@/scripts/safe-uri-decode.js';
+import { warningExternalWebsite } from '@/scripts/warning-external-website.js';
 
 const props = withDefaults(defineProps<{
 	url: string;
diff --git a/packages/frontend/src/pages/admin/moderation.vue b/packages/frontend/src/pages/admin/moderation.vue
index 2a2929dacd..e156d12539 100644
--- a/packages/frontend/src/pages/admin/moderation.vue
+++ b/packages/frontend/src/pages/admin/moderation.vue
@@ -45,6 +45,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 						<template #caption>{{ i18n.ts.prohibitedWordsDescription }}<br>{{ i18n.ts.prohibitedWordsDescription2 }}</template>
 					</MkTextarea>
 
+					<MkTextarea v-model="wellKnownWebsites">
+						<template #label>{{ i18n.ts.wellKnownWebsites }}</template>
+						<template #caption>{{ i18n.ts.wellKnownWebsitesDescription }}</template>
+					</MkTextarea>
+
 					<MkTextarea v-model="urlPreviewDenyList">
 						<template #label>{{ i18n.ts.urlPreviewDenyList }}</template>
 						<template #caption>{{ i18n.ts.urlPreviewDenyListDescription }}</template>
@@ -91,7 +96,8 @@ const hiddenTags = ref<string>('');
 const preservedUsernames = ref<string>('');
 const tosUrl = ref<string | null>(null);
 const privacyPolicyUrl = ref<string | null>(null);
-const urlPreviewDenyList = ref<string | undefined>('');
+const wellKnownWebsites = ref<string>('');
+const urlPreviewDenyList = ref<string>('');
 
 async function init() {
 	const meta = await misskeyApi('admin/meta');
@@ -103,7 +109,8 @@ async function init() {
 	preservedUsernames.value = meta.preservedUsernames.join('\n');
 	tosUrl.value = meta.tosUrl;
 	privacyPolicyUrl.value = meta.privacyPolicyUrl;
-	urlPreviewDenyList.value = meta.urlPreviewDenyList?.join('\n');
+	wellKnownWebsites.value = meta.wellKnownWebsites.join('\n');
+	urlPreviewDenyList.value = meta.urlPreviewDenyList.join('\n');
 }
 
 function save() {
@@ -116,7 +123,8 @@ function save() {
 		prohibitedWords: prohibitedWords.value.split('\n'),
 		hiddenTags: hiddenTags.value.split('\n'),
 		preservedUsernames: preservedUsernames.value.split('\n'),
-		urlPreviewDenyList: urlPreviewDenyList.value?.split('\n'),
+		wellKnownWebsites: wellKnownWebsites.value.split('\n'),
+		urlPreviewDenyList: urlPreviewDenyList.value.split('\n'),
 	}).then(() => {
 		fetchInstance(true);
 	});
diff --git a/packages/frontend/src/scripts/warning-external-website.ts b/packages/frontend/src/scripts/warning-external-website.ts
new file mode 100644
index 0000000000..04109af8a5
--- /dev/null
+++ b/packages/frontend/src/scripts/warning-external-website.ts
@@ -0,0 +1,33 @@
+import { url as local } from '@/config.js';
+import { instance } from '@/instance.js';
+import { i18n } from '@/i18n.js';
+import * as os from '@/os.js';
+
+const isRegExp = /^\/(.+)\/(.*)$/;
+
+export async function warningExternalWebsite(ev: MouseEvent, url: string) {
+	const self = url.startsWith(local);
+	const isWellKnownWebsite = self || instance.wellKnownWebsites.some(expression => {
+		const r = isRegExp.exec(expression);
+		if (r) {
+			return new RegExp(r[1], r[2]).test(url);
+		} else return expression.split(' ').every(keyword => url.includes(keyword));
+	});
+
+	if (!self && !isWellKnownWebsite) {
+		ev.preventDefault();
+		ev.stopPropagation();
+
+		const confirm = await os.confirm({
+			type: 'warning',
+			title: i18n.ts.warningRedirectingExternalWebsiteTitle,
+			text: i18n.tsx.warningRedirectingExternalWebsiteDescription({ url }),
+		});
+
+		if (confirm.canceled) return false;
+
+		window.open(url, '_blank', 'noopener');
+	}
+
+	return true;
+}
diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts
index 17adb4b418..f18d293ab9 100644
--- a/packages/misskey-js/src/autogen/types.ts
+++ b/packages/misskey-js/src/autogen/types.ts
@@ -4991,6 +4991,7 @@ export type components = {
           imageUrl: string;
           dayOfWeek: number;
         })[];
+      wellKnownWebsites: string[];
       /** @default 0 */
       notesPerOneAd: number;
       enableEmail: boolean;
@@ -5181,7 +5182,8 @@ export type operations = {
             perUserHomeTimelineCacheMax: number;
             perUserListTimelineCacheMax: number;
             notesPerOneAd: number;
-            urlPreviewDenyList?: string[];
+            wellKnownWebsites: string[];
+            urlPreviewDenyList: string[];
             featuredGameChannels: string[];
             backgroundImageUrl: string | null;
             deeplAuthKey: string | null;
@@ -9657,6 +9659,7 @@ export type operations = {
           notesPerOneAd?: number;
           silencedHosts?: string[] | null;
           sensitiveMediaHosts?: string[] | null;
+          wellKnownWebsites?: string[] | null;
           urlPreviewDenyList?: string[] | null;
           featuredGameChannels?: string[] | null;
         };