diff --git a/packages/tests/src/client/embed-html.ts b/packages/tests/src/client/embed-html.ts
index 99121b8f2..c1aaf58a0 100644
--- a/packages/tests/src/client/embed-html.ts
+++ b/packages/tests/src/client/embed-html.ts
@@ -23,7 +23,8 @@ describe('Test embed HTML generation', function () {
   let unlistedPlaylistId: string
   let playlistName: string
   let playlistDescription: string
-  let instanceDescription: string
+
+  let instanceConfig: { shortDescription: string }
 
   before(async function () {
     this.timeout(120000);
@@ -44,7 +45,7 @@ describe('Test embed HTML generation', function () {
       playlist,
       unlistedPlaylistId,
       privatePlaylistId,
-      instanceDescription
+      instanceConfig
     } = await prepareClientTests())
   })
 
@@ -58,7 +59,7 @@ describe('Test embed HTML generation', function () {
     it('Should have the correct embed html instance tags', async function () {
       const res = await makeHTMLRequest(servers[0].url, '/videos/embed/toto')
 
-      checkIndexTags(res.text, `PeerTube`, instanceDescription, '', config)
+      checkIndexTags(res.text, `PeerTube`, instanceConfig.shortDescription, '', config)
 
       expect(res.text).to.not.contain(`"name":`)
     })
diff --git a/packages/tests/src/client/index-html.ts b/packages/tests/src/client/index-html.ts
index 5f33955dc..30198ad57 100644
--- a/packages/tests/src/client/index-html.ts
+++ b/packages/tests/src/client/index-html.ts
@@ -35,8 +35,7 @@ describe('Test index HTML generation', function () {
       passwordProtectedVideoId,
       unlistedVideoId,
       privatePlaylistId,
-      unlistedPlaylistId,
-      instanceDescription
+      unlistedPlaylistId
     } = await prepareClientTests())
   })
 
diff --git a/packages/tests/src/client/og-twitter-tags.ts b/packages/tests/src/client/og-twitter-tags.ts
index 0244dce0b..05b1ad9da 100644
--- a/packages/tests/src/client/og-twitter-tags.ts
+++ b/packages/tests/src/client/og-twitter-tags.ts
@@ -22,11 +22,18 @@ describe('Test Open Graph and Twitter cards HTML tags', function () {
 
   let playlistIds: (string | number)[] = []
 
+  let instanceConfig: {
+    name: string
+    shortDescription: string
+    avatar: string
+  }
+
   before(async function () {
     this.timeout(120000);
 
     ({
       servers,
+      instanceConfig,
       account,
       playlistIds,
       videoIds,
@@ -41,6 +48,20 @@ describe('Test Open Graph and Twitter cards HTML tags', function () {
 
   describe('Open Graph', function () {
 
+    async function indexPageTest (path: string) {
+      const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
+      const text = res.text
+
+      let url = servers[0].url
+      if (path !== '/') url += path
+
+      expect(text).to.contain(`<meta property="og:title" content="${instanceConfig.name}" />`)
+      expect(text).to.contain(`<meta property="og:description" content="${instanceConfig.shortDescription}" />`)
+      expect(text).to.contain('<meta property="og:type" content="website" />')
+      expect(text).to.contain(`<meta property="og:url" content="${url}`)
+      expect(text).to.contain(`<meta property="og:image" content="${servers[0].url}/`)
+    }
+
     async function accountPageTest (path: string) {
       const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
       const text = res.text
@@ -49,6 +70,7 @@ describe('Test Open Graph and Twitter cards HTML tags', function () {
       expect(text).to.contain(`<meta property="og:description" content="${account.description}" />`)
       expect(text).to.contain('<meta property="og:type" content="website" />')
       expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/a/${servers[0].store.user.username}/video-channels" />`)
+      expect(text).to.not.contain(`<meta property="og:image"`)
     }
 
     async function channelPageTest (path: string) {
@@ -59,6 +81,7 @@ describe('Test Open Graph and Twitter cards HTML tags', function () {
       expect(text).to.contain(`<meta property="og:description" content="${channelDescription}" />`)
       expect(text).to.contain('<meta property="og:type" content="website" />')
       expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/c/${servers[0].store.channel.name}/videos" />`)
+      expect(text).to.contain(`<meta property="og:image" content="${servers[0].url}/`)
     }
 
     async function watchVideoPageTest (path: string) {
@@ -69,6 +92,7 @@ describe('Test Open Graph and Twitter cards HTML tags', function () {
       expect(text).to.contain(`<meta property="og:description" content="${videoDescriptionPlainText}" />`)
       expect(text).to.contain('<meta property="og:type" content="video" />')
       expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/${servers[0].store.video.shortUUID}" />`)
+      expect(text).to.contain(`<meta property="og:image" content="${servers[0].url}/`)
     }
 
     async function watchPlaylistPageTest (path: string) {
@@ -79,8 +103,16 @@ describe('Test Open Graph and Twitter cards HTML tags', function () {
       expect(text).to.contain(`<meta property="og:description" content="${playlistDescription}" />`)
       expect(text).to.contain('<meta property="og:type" content="video" />')
       expect(text).to.contain(`<meta property="og:url" content="${servers[0].url}/w/p/${playlist.shortUUID}" />`)
+      expect(text).to.contain(`<meta property="og:image" content="${servers[0].url}/`)
     }
 
+    it('Should have valid Open Graph tags on the common page', async function () {
+      await indexPageTest('/about/peertube')
+      await indexPageTest('/videos')
+      await indexPageTest('/homepage')
+      await indexPageTest('/')
+    })
+
     it('Should have valid Open Graph tags on the account page', async function () {
       await accountPageTest('/accounts/' + servers[0].store.user.username)
       await accountPageTest('/a/' + servers[0].store.user.username)
@@ -135,6 +167,7 @@ describe('Test Open Graph and Twitter cards HTML tags', function () {
 
       expect(text).to.contain('<meta property="twitter:card" content="summary" />')
       expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
+      expect(text).to.not.contain(`<meta property="twitter:image"`)
     }
 
     async function channelPageTest (path: string) {
@@ -143,6 +176,7 @@ describe('Test Open Graph and Twitter cards HTML tags', function () {
 
       expect(text).to.contain('<meta property="twitter:card" content="summary" />')
       expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
+      expect(text).to.contain(`<meta property="twitter:image" content="${servers[0].url}`)
     }
 
     async function watchVideoPageTest (path: string) {
@@ -151,6 +185,7 @@ describe('Test Open Graph and Twitter cards HTML tags', function () {
 
       expect(text).to.contain('<meta property="twitter:card" content="player" />')
       expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
+      expect(text).to.contain(`<meta property="twitter:image" content="${servers[0].url}`)
     }
 
     async function watchPlaylistPageTest (path: string) {
@@ -159,6 +194,7 @@ describe('Test Open Graph and Twitter cards HTML tags', function () {
 
       expect(text).to.contain('<meta property="twitter:card" content="player" />')
       expect(text).to.contain('<meta property="twitter:site" content="@Kuja" />')
+      expect(text).to.contain(`<meta property="twitter:image" content="${servers[0].url}`)
     }
 
     it('Should have valid twitter card on the watch video page', async function () {
diff --git a/packages/tests/src/shared/client.ts b/packages/tests/src/shared/client.ts
index 5afb20aae..f9572806e 100644
--- a/packages/tests/src/shared/client.ts
+++ b/packages/tests/src/shared/client.ts
@@ -5,7 +5,8 @@ import {
   VideoPlaylistCreateResult,
   Account,
   HTMLServerConfig,
-  ServerConfig
+  ServerConfig,
+  ActorImageType
 } from '@peertube/peertube-models'
 import {
   createMultipleServers,
@@ -43,11 +44,22 @@ export async function prepareClientTests () {
   const servers = await createMultipleServers(2)
 
   await setAccessTokensToServers(servers)
-
   await doubleFollow(servers[0], servers[1])
-
   await setDefaultVideoChannel(servers)
 
+  const instanceConfig = {
+    name: 'super instance title',
+    shortDescription: 'super instance description',
+    avatar: 'avatar.png'
+  }
+
+  await servers[0].config.updateExistingConfig({
+    newConfig: {
+      instance: { name: instanceConfig.name, shortDescription: instanceConfig.shortDescription }
+    }
+  })
+  await servers[0].config.updateInstanceImage({ type: ActorImageType.AVATAR, fixture: instanceConfig.avatar })
+
   let account: Account
 
   let videoIds: (string | number)[] = []
@@ -60,8 +72,6 @@ export async function prepareClientTests () {
   let privatePlaylistId: string
   let unlistedPlaylistId: string
 
-  const instanceDescription = 'PeerTube, an ActivityPub-federated video streaming platform using P2P directly in your web browser.'
-
   const videoName = 'my super name for server 1'
   const videoDescription = 'my<br> super __description__ for *server* 1<p></p>'
   const videoDescriptionPlainText = 'my super description for server 1'
@@ -77,6 +87,8 @@ export async function prepareClientTests () {
     attributes: { description: channelDescription }
   })
 
+  await servers[0].channels.updateImage({ channelName: servers[0].store.channel.name, fixture: 'avatar.png', type: 'avatar' })
+
   // Public video
 
   {
@@ -154,7 +166,7 @@ export async function prepareClientTests () {
   return {
     servers,
 
-    instanceDescription,
+    instanceConfig,
 
     account,
 
diff --git a/server/core/lib/html/shared/actor-html.ts b/server/core/lib/html/shared/actor-html.ts
index 2c6990afa..76d2ebcb7 100644
--- a/server/core/lib/html/shared/actor-html.ts
+++ b/server/core/lib/html/shared/actor-html.ts
@@ -84,9 +84,7 @@ export class ActorHtml {
         updatedAt: entity.updatedAt
       },
 
-      indexationPolicy: entity.Actor.isOwned()
-        ? 'always'
-        : 'never'
+      forbidIndexation: !entity.Actor.isOwned()
     }, {})
 
     return customHTML
diff --git a/server/core/lib/html/shared/common-embed-html.ts b/server/core/lib/html/shared/common-embed-html.ts
index d17ea2efc..565c3ddec 100644
--- a/server/core/lib/html/shared/common-embed-html.ts
+++ b/server/core/lib/html/shared/common-embed-html.ts
@@ -14,6 +14,6 @@ export class CommonEmbedHtml {
     let htmlResult = TagsHtml.addTitleTag(html)
     htmlResult = TagsHtml.addDescriptionTag(htmlResult)
 
-    return TagsHtml.addTags(htmlResult, { indexationPolicy: 'never' }, { playlist, video })
+    return TagsHtml.addTags(htmlResult, { forbidIndexation: true }, { playlist, video })
   }
 }
diff --git a/server/core/lib/html/shared/page-html.ts b/server/core/lib/html/shared/page-html.ts
index a67dadbe4..6106d3317 100644
--- a/server/core/lib/html/shared/page-html.ts
+++ b/server/core/lib/html/shared/page-html.ts
@@ -1,15 +1,17 @@
-import { buildFileLocale, getDefaultLocale, is18nLocale, POSSIBLE_LOCALES } from '@peertube/peertube-core-utils'
+import { buildFileLocale, escapeHTML, getDefaultLocale, is18nLocale, POSSIBLE_LOCALES } from '@peertube/peertube-core-utils'
+import { ActorImageType, HTMLServerConfig } from '@peertube/peertube-models'
 import { isTestOrDevInstance, root, sha256 } from '@peertube/peertube-node-utils'
+import { CONFIG } from '@server/initializers/config.js'
+import { ActorImageModel } from '@server/models/actor/actor-image.js'
+import { getServerActor } from '@server/models/application/application.js'
 import express from 'express'
+import { pathExists } from 'fs-extra/esm'
 import { readFile } from 'fs/promises'
 import { join } from 'path'
 import { logger } from '../../../helpers/logger.js'
-import { CUSTOM_HTML_TAG_COMMENTS, FILES_CONTENT_HASH, PLUGIN_GLOBAL_CSS_PATH } from '../../../initializers/constants.js'
+import { CUSTOM_HTML_TAG_COMMENTS, FILES_CONTENT_HASH, PLUGIN_GLOBAL_CSS_PATH, WEBSERVER } from '../../../initializers/constants.js'
 import { ServerConfigManager } from '../../server-config-manager.js'
 import { TagsHtml } from './tags-html.js'
-import { pathExists } from 'fs-extra/esm'
-import { HTMLServerConfig } from '@peertube/peertube-models'
-import { CONFIG } from '@server/initializers/config.js'
 
 export class PageHtml {
 
@@ -22,13 +24,33 @@ export class PageHtml {
   }
 
   static async getDefaultHTML (req: express.Request, res: express.Response, paramLang?: string) {
-    const html = paramLang
-      ? await this.getIndexHTML(req, res, paramLang)
-      : await this.getIndexHTML(req, res)
+    const html = await this.getIndexHTML(req, res, paramLang)
+    const serverActor = await getServerActor()
+    const avatar = serverActor.getMaxQualityImage(ActorImageType.AVATAR)
 
     let customHTML = TagsHtml.addTitleTag(html)
     customHTML = TagsHtml.addDescriptionTag(customHTML)
 
+    const url = req.originalUrl === '/'
+      ? WEBSERVER.URL
+      : WEBSERVER.URL + req.originalUrl
+
+    customHTML = await TagsHtml.addTags(customHTML, {
+      url,
+
+      escapedSiteName: escapeHTML(CONFIG.INSTANCE.NAME),
+      escapedTitle: escapeHTML(CONFIG.INSTANCE.NAME),
+      escapedTruncatedDescription: escapeHTML(CONFIG.INSTANCE.SHORT_DESCRIPTION),
+
+      image: avatar
+        ? { url: ActorImageModel.getImageUrl(avatar), width: avatar.width, height: avatar.height }
+        : undefined,
+
+      ogType: 'website',
+      twitterCard: 'summary_large_image',
+      forbidIndexation: false
+    }, {})
+
     return customHTML
   }
 
diff --git a/server/core/lib/html/shared/playlist-html.ts b/server/core/lib/html/shared/playlist-html.ts
index 8e994cffa..f46aa097e 100644
--- a/server/core/lib/html/shared/playlist-html.ts
+++ b/server/core/lib/html/shared/playlist-html.ts
@@ -113,11 +113,11 @@ export class PlaylistHtml {
       escapedTitle: escapeHTML(playlist.name),
       escapedTruncatedDescription,
 
-      indexationPolicy: !playlist.isOwned() || playlist.privacy !== VideoPlaylistPrivacy.PUBLIC
-        ? 'never'
-        : 'always',
+      forbidIndexation: !playlist.isOwned() || playlist.privacy !== VideoPlaylistPrivacy.PUBLIC,
 
-      image: { url: playlist.getThumbnailUrl() },
+      image: playlist.Thumbnail
+        ? { url: playlist.getThumbnailUrl(), width: playlist.Thumbnail.width, height: playlist.Thumbnail.height }
+        : undefined,
 
       list,
 
diff --git a/server/core/lib/html/shared/tags-html.ts b/server/core/lib/html/shared/tags-html.ts
index 92349204d..e4568c0ef 100644
--- a/server/core/lib/html/shared/tags-html.ts
+++ b/server/core/lib/html/shared/tags-html.ts
@@ -7,7 +7,7 @@ import truncate from 'lodash-es/truncate.js'
 import { mdToOneLinePlainText } from '@server/helpers/markdown.js'
 
 type Tags = {
-  indexationPolicy: 'always' | 'never'
+  forbidIndexation: boolean
 
   url?: string
 
@@ -31,8 +31,8 @@ type Tags = {
 
   image?: {
     url: string
-    width?: number
-    height?: number
+    width: number
+    height: number
   }
 
   embed?: {
@@ -76,7 +76,7 @@ export class TagsHtml {
     const twitterCardMetaTags = this.generateTwitterCardMetaTagsOptions(tagsValues)
     const schemaTags = await this.generateSchemaTagsOptions(tagsValues, context)
 
-    const { url, escapedTitle, oembedUrl, indexationPolicy } = tagsValues
+    const { url, escapedTitle, oembedUrl, forbidIndexation } = tagsValues
 
     const oembedLinkTags: { type: string, href: string, escapedTitle: string }[] = []
 
@@ -126,11 +126,11 @@ export class TagsHtml {
     }
 
     // SEO, use origin URL
-    if (indexationPolicy !== 'never' && url) {
+    if (forbidIndexation === true && url) {
       tagsStr += `<link rel="canonical" href="${url}" />`
     }
 
-    if (indexationPolicy === 'never') {
+    if (forbidIndexation === true) {
       tagsStr += `<meta name="robots" content="noindex" />`
     }
 
diff --git a/server/core/lib/html/shared/video-html.ts b/server/core/lib/html/shared/video-html.ts
index e1b285a9c..d6b35a98c 100644
--- a/server/core/lib/html/shared/video-html.ts
+++ b/server/core/lib/html/shared/video-html.ts
@@ -7,7 +7,7 @@ import validator from 'validator'
 import { CONFIG } from '../../../initializers/config.js'
 import { MEMOIZE_TTL, WEBSERVER } from '../../../initializers/constants.js'
 import { VideoModel } from '../../../models/video/video.js'
-import { MVideo, MVideoThumbnailBlacklist } from '../../../types/models/index.js'
+import { MVideo, MVideoThumbnail, MVideoThumbnailBlacklist } from '../../../types/models/index.js'
 import { getActivityStreamDuration } from '../../activitypub/activity.js'
 import { isVideoInPrivateDirectory } from '../../video-privacy.js'
 import { CommonEmbedHtml } from './common-embed-html.js'
@@ -78,7 +78,7 @@ export class VideoHtml {
 
   private static buildVideoHTML (options: {
     html: string
-    video: MVideo
+    video: MVideoThumbnail
 
     addOG: boolean
     addTwitterCard: boolean
@@ -111,6 +111,8 @@ export class VideoHtml {
 
     const schemaType = 'VideoObject'
 
+    const preview = video.getPreview()
+
     return TagsHtml.addTags(customHTML, {
       url: WEBSERVER.URL + video.getWatchStaticPath(),
 
@@ -118,11 +120,11 @@ export class VideoHtml {
       escapedTitle: escapeHTML(video.name),
       escapedTruncatedDescription,
 
-      indexationPolicy: video.remote || video.privacy !== VideoPrivacy.PUBLIC
-        ? 'never'
-        : 'always',
+      forbidIndexation: video.remote || video.privacy !== VideoPrivacy.PUBLIC,
 
-      image: { url: WEBSERVER.URL + video.getPreviewStaticPath() },
+      image: preview
+        ? { url: WEBSERVER.URL + video.getPreviewStaticPath(), width: preview.width, height: preview.height }
+        : undefined,
 
       embed,
       oembedUrl: this.getOEmbedUrl(video, currentQuery),
diff --git a/server/core/lib/video-pre-import.ts b/server/core/lib/video-pre-import.ts
index 615429087..e17bf2797 100644
--- a/server/core/lib/video-pre-import.ts
+++ b/server/core/lib/video-pre-import.ts
@@ -79,8 +79,8 @@ async function insertFromImportIntoDB (parameters: {
   const videoImport = await sequelizeTypescript.transaction(async t => {
     const sequelizeOptions = { transaction: t }
 
-    // Save video object in database
-    const videoCreated = await video.save(sequelizeOptions) as (MVideoAccountDefault & MVideoWithBlacklistLight & MVideoTag)
+    // eslint-disable-next-line max-len
+    const videoCreated = await video.save(sequelizeOptions) as (MVideoAccountDefault & MVideoWithBlacklistLight & MVideoTag & MVideoThumbnail)
     videoCreated.VideoChannel = videoChannel
 
     if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
diff --git a/server/core/models/video/video.ts b/server/core/models/video/video.ts
index b6aa85486..d72e5f744 100644
--- a/server/core/models/video/video.ts
+++ b/server/core/models/video/video.ts
@@ -1838,21 +1838,21 @@ export class VideoModel extends SequelizeModel<VideoModel> {
 
   // ---------------------------------------------------------------------------
 
-  hasMiniature (this: MVideoThumbnail) {
+  hasMiniature (this: Pick<MVideoThumbnail, 'getMiniature' | 'Thumbnails'>) {
     return !!this.getMiniature()
   }
 
-  getMiniature (this: MVideoThumbnail) {
+  getMiniature (this: Pick<MVideoThumbnail, 'Thumbnails'>) {
     if (Array.isArray(this.Thumbnails) === false) return undefined
 
     return this.Thumbnails.find(t => t.type === ThumbnailType.MINIATURE)
   }
 
-  hasPreview (this: MVideoThumbnail) {
+  hasPreview (this: Pick<MVideoThumbnail, 'getPreview' | 'Thumbnails'>) {
     return !!this.getPreview()
   }
 
-  getPreview (this: MVideoThumbnail) {
+  getPreview (this: Pick<MVideoThumbnail, 'Thumbnails'>) {
     if (Array.isArray(this.Thumbnails) === false) return undefined
 
     return this.Thumbnails.find(t => t.type === ThumbnailType.PREVIEW)
@@ -1872,14 +1872,14 @@ export class VideoModel extends SequelizeModel<VideoModel> {
     return buildVideoEmbedPath(this)
   }
 
-  getMiniatureStaticPath () {
+  getMiniatureStaticPath (this: Pick<MVideoThumbnail, 'getMiniature' | 'Thumbnails'>) {
     const thumbnail = this.getMiniature()
     if (!thumbnail) return null
 
     return thumbnail.getLocalStaticPath()
   }
 
-  getPreviewStaticPath () {
+  getPreviewStaticPath (this: Pick<MVideoThumbnail, 'getPreview' | 'Thumbnails'>) {
     const preview = this.getPreview()
     if (!preview) return null
 
diff --git a/server/core/types/models/abuse/abuse.ts b/server/core/types/models/abuse/abuse.ts
index bf6680470..c0c8d3776 100644
--- a/server/core/types/models/abuse/abuse.ts
+++ b/server/core/types/models/abuse/abuse.ts
@@ -40,7 +40,7 @@ export type MVideoAbuseVideoFull =
 export type MVideoAbuseFormattable =
   MVideoAbuse &
   UseVideoAbuse<'Video', Pick<MVideoAccountLightBlacklistAllFiles,
-  'id' | 'uuid' | 'name' | 'nsfw' | 'getMiniatureStaticPath' | 'isBlacklisted' | 'VideoChannel'>>
+  'id' | 'uuid' | 'name' | 'nsfw' | 'getMiniature' | 'getMiniatureStaticPath' | 'isBlacklisted' | 'VideoChannel' | 'Thumbnails'>>
 
 // ############################################################################
 
diff --git a/server/core/types/models/video/video.ts b/server/core/types/models/video/video.ts
index 8db18f1fb..de462176b 100644
--- a/server/core/types/models/video/video.ts
+++ b/server/core/types/models/video/video.ts
@@ -217,7 +217,7 @@ export type MVideoForRedundancyAPI =
 // Format for API or AP object
 
 export type MVideoFormattable =
-  MVideo &
+  MVideoThumbnail &
   PickWithOpt<VideoModel, 'UserVideoHistories', MUserVideoHistoryTime[]> &
   Use<'VideoChannel', MChannelAccountSummaryFormattable> &
   PickWithOpt<VideoModel, 'ScheduleVideoUpdate', Pick<MScheduleVideoUpdate, 'updateAt' | 'privacy'>> &