diff --git a/lib/modules/platform/github/index.ts b/lib/modules/platform/github/index.ts
index c2513c100010d6d9e7cb874b971c62bf486fff84..e1f37380ba49675cfd88f072f9794d1d41b92cd6 100644
--- a/lib/modules/platform/github/index.ts
+++ b/lib/modules/platform/github/index.ts
@@ -35,7 +35,10 @@ import type {
 import * as hostRules from '../../../util/host-rules';
 import * as githubHttp from '../../../util/http/github';
 import type { GithubHttpOptions } from '../../../util/http/github';
-import type { HttpResponse } from '../../../util/http/types';
+import type {
+  HttpResponse,
+  InternalHttpOptions,
+} from '../../../util/http/types';
 import { coerceObject } from '../../../util/object';
 import { regEx } from '../../../util/regex';
 import { sanitize } from '../../../util/sanitize';
@@ -316,11 +319,15 @@ export async function getRawFile(
   branchOrTag?: string,
 ): Promise<string | null> {
   const repo = repoName ?? config.repository;
+  const httpOptions: InternalHttpOptions = {
+    // Only cache response if it's from the same repo
+    repoCache: repo === config.repository,
+  };
   let url = `repos/${repo}/contents/${fileName}`;
   if (branchOrTag) {
     url += `?ref=` + branchOrTag;
   }
-  const res = await githubApi.getJson<{ content: string }>(url);
+  const res = await githubApi.getJson<{ content: string }>(url, httpOptions);
   const buf = res.body.content;
   const str = fromBase64(buf);
   return str;
@@ -1220,7 +1227,7 @@ export async function getIssue(
     const issueBody = (
       await githubApi.getJson<{ body: string }>(
         `repos/${config.parentRepo ?? config.repository}/issues/${number}`,
-        { memCache: useCache },
+        { memCache: useCache, repoCache: true },
       )
     ).body.body;
     return {
@@ -1306,6 +1313,7 @@ export async function ensureIssue({
           `repos/${config.parentRepo ?? config.repository}/issues/${
             issue.number
           }`,
+          { repoCache: true },
         )
       ).body.body;
       if (
diff --git a/lib/modules/platform/github/pr.ts b/lib/modules/platform/github/pr.ts
index 432abe996515c2df911236da6bbb34b087c62359..2cd6bb9359d88d9f3ef4654e6661baf4a94cbdc1 100644
--- a/lib/modules/platform/github/pr.ts
+++ b/lib/modules/platform/github/pr.ts
@@ -67,6 +67,7 @@ export async function getPrCache(
       if (pageIdx === 1 && isInitial) {
         // Speed up initial fetch
         opts.paginate = true;
+        opts.repoCache = true;
       }
 
       const perPage = isInitial ? 100 : 20;
diff --git a/lib/util/cache/repository/types.ts b/lib/util/cache/repository/types.ts
index eabbaba5f2ca5effaf36d386c0dda4f3392264ea..68d915d30da974a6df1883a449e0cc5030054a8c 100644
--- a/lib/util/cache/repository/types.ts
+++ b/lib/util/cache/repository/types.ts
@@ -8,6 +8,7 @@ import type { BitbucketPrCacheData } from '../../../modules/platform/bitbucket/t
 import type { GiteaPrCacheData } from '../../../modules/platform/gitea/types';
 import type { RepoInitConfig } from '../../../workers/repository/init/types';
 import type { PrBlockedBy } from '../../../workers/types';
+import type { HttpResponse } from '../../http/types';
 
 export interface BaseBranchCache {
   sha: string; // branch commit sha
@@ -124,8 +125,15 @@ export interface BranchCache {
   result?: string;
 }
 
+export interface HttpCache {
+  etag: string;
+  httpResponse: HttpResponse<unknown>;
+  timeStamp: string;
+}
+
 export interface RepoCacheData {
   configFileName?: string;
+  httpCache?: Record<string, HttpCache>;
   semanticCommits?: 'enabled' | 'disabled';
   branches?: BranchCache[];
   init?: RepoInitConfig;
diff --git a/lib/util/http/index.spec.ts b/lib/util/http/index.spec.ts
index eb9aadd63bac5b55371cc6e92e4cba7ee1e528d3..a48769c6aa58a5699d6fd0750b0c3f2112ea672d 100644
--- a/lib/util/http/index.spec.ts
+++ b/lib/util/http/index.spec.ts
@@ -76,13 +76,38 @@ describe('util/http/index', () => {
         },
       })
       .get('/')
-      .reply(200, '{ "test": true }');
-    expect(await http.getJson('http://renovate.com')).toEqual({
+      .reply(200, '{ "test": true }', { etag: 'abc123' });
+    expect(
+      await http.getJson('http://renovate.com', { repoCache: true }),
+    ).toEqual({
       authorization: false,
       body: {
         test: true,
       },
-      headers: {},
+      headers: {
+        etag: 'abc123',
+      },
+      statusCode: 200,
+    });
+
+    httpMock
+      .scope(baseUrl, {
+        reqheaders: {
+          accept: 'application/json',
+        },
+      })
+      .get('/')
+      .reply(304, '', { etag: 'abc123' });
+    expect(
+      await http.getJson('http://renovate.com', { repoCache: true }),
+    ).toEqual({
+      authorization: false,
+      body: {
+        test: true,
+      },
+      headers: {
+        etag: 'abc123',
+      },
       statusCode: 200,
     });
   });
diff --git a/lib/util/http/index.ts b/lib/util/http/index.ts
index c675e378f5692e1d15ce6e8eb4b3cd6fafb95548..40881e4309777bc1cd7651da2d570c4585f0d6fa 100644
--- a/lib/util/http/index.ts
+++ b/lib/util/http/index.ts
@@ -7,6 +7,7 @@ import { pkg } from '../../expose.cjs';
 import { logger } from '../../logger';
 import { ExternalHostError } from '../../types/errors/external-host-error';
 import * as memCache from '../cache/memory';
+import { getCache } from '../cache/repository';
 import { clone } from '../clone';
 import { hash } from '../hash';
 import { type AsyncResult, Result } from '../result';
@@ -163,6 +164,8 @@ export class Http<Opts extends HttpOptions = HttpOptions> {
       httpOptions,
     );
 
+    logger.trace(`HTTP request: ${options.method.toUpperCase()} ${url}`);
+
     const etagCache =
       httpOptions.etagCache && options.method === 'get'
         ? httpOptions.etagCache
@@ -210,6 +213,16 @@ export class Http<Opts extends HttpOptions = HttpOptions> {
 
     // istanbul ignore else: no cache tests
     if (!resPromise) {
+      if (httpOptions.repoCache) {
+        const cachedEtag = getCache().httpCache?.[url]?.etag;
+        if (cachedEtag) {
+          logger.debug(`Using cached etag for ${url}`);
+          options.headers = {
+            ...options.headers,
+            'If-None-Match': cachedEtag,
+          };
+        }
+      }
       const startTime = Date.now();
       const httpTask: GotTask<T> = () => {
         const queueDuration = Date.now() - startTime;
@@ -243,6 +256,31 @@ export class Http<Opts extends HttpOptions = HttpOptions> {
       const deepCopyNeeded = !!memCacheKey && res.statusCode !== 304;
       const resCopy = copyResponse(res, deepCopyNeeded);
       resCopy.authorization = !!options?.headers?.authorization;
+      if (httpOptions.repoCache) {
+        const cache = getCache();
+        cache.httpCache ??= {};
+        if (resCopy.statusCode === 200 && resCopy.headers?.etag) {
+          logger.debug(
+            `Saving response to cache: ${url} with etag ${resCopy.headers.etag}`,
+          );
+          cache.httpCache[url] = {
+            etag: resCopy.headers.etag,
+            httpResponse: copyResponse(res, deepCopyNeeded),
+            timeStamp: new Date().toISOString(),
+          };
+        }
+        if (resCopy.statusCode === 304 && cache.httpCache[url]?.httpResponse) {
+          logger.debug(
+            `Using cached response: ${url} with etag ${resCopy.headers.etag} from ${cache.httpCache[url].timeStamp}`,
+          );
+          const cacheCopy = copyResponse(
+            cache.httpCache[url].httpResponse,
+            deepCopyNeeded,
+          );
+          cacheCopy.authorization = !!options?.headers?.authorization;
+          return cacheCopy as HttpResponse<T>;
+        }
+      }
       return resCopy;
     } catch (err) {
       const { abortOnError, abortIgnoreStatusCodes } = options;
diff --git a/lib/util/http/types.ts b/lib/util/http/types.ts
index 5d3d2770a29d0f5b122276564253aeedabb9b16b..5969aeade45806611eed62a959c773ab546b5870 100644
--- a/lib/util/http/types.ts
+++ b/lib/util/http/types.ts
@@ -65,6 +65,7 @@ export interface HttpOptions {
 
   token?: string;
   memCache?: boolean;
+  repoCache?: boolean;
 }
 
 export interface EtagCache<T = any> {
@@ -81,6 +82,7 @@ export interface InternalHttpOptions extends HttpOptions {
   responseType?: 'json' | 'buffer';
   method?: 'get' | 'post' | 'put' | 'patch' | 'delete' | 'head';
   parseJson?: ParseJsonFunction;
+  repoCache?: boolean;
 }
 
 export interface HttpHeaders extends IncomingHttpHeaders {