diff --git a/README.md b/README.md
index 9843ee21..ed525cb5 100644
--- a/README.md
+++ b/README.md
@@ -126,6 +126,8 @@ The action defaults to search for the dependency file (`package-lock.json`, `npm
 
 **Note:** The action does not cache `node_modules`
 
+Use `cache-invalidate-after-days` to change the default fallback cache invalidation of every 120 days. Set to 0 to deactivate.
+
 See the examples of using cache for `yarn`/`pnpm` and `cache-dependency-path` input in the [Advanced usage](docs/advanced-usage.md#caching-packages-data) guide.
 
 **Caching npm dependencies:**
diff --git a/__tests__/cache-restore.test.ts b/__tests__/cache-restore.test.ts
index 90153a40..135c9f87 100644
--- a/__tests__/cache-restore.test.ts
+++ b/__tests__/cache-restore.test.ts
@@ -132,13 +132,13 @@ describe('cache-restore', () => {
           }
         });
 
-        await restoreCache(packageManager, '');
+        await restoreCache(packageManager, '', '0');
         expect(hashFilesSpy).toHaveBeenCalled();
         expect(infoSpy).toHaveBeenCalledWith(
-          `Cache restored from key: node-cache-${platform}-${packageManager}-${fileHash}`
+          `Cache restored from key: ${platform}-0-setup-node-${packageManager}-${fileHash}`
         );
         expect(infoSpy).not.toHaveBeenCalledWith(
-          `${packageManager} cache is not found`
+          `Cache not found for input keys: ${platform}-0-setup-node-${packageManager}-${fileHash}, ${platform}-0-setup-node-${packageManager}-`
         );
         expect(setOutputSpy).toHaveBeenCalledWith('cache-hit', true);
       }
@@ -163,10 +163,10 @@ describe('cache-restore', () => {
         });
 
         restoreCacheSpy.mockImplementationOnce(() => undefined);
-        await restoreCache(packageManager, '');
+        await restoreCache(packageManager, '', '0');
         expect(hashFilesSpy).toHaveBeenCalled();
         expect(infoSpy).toHaveBeenCalledWith(
-          `${packageManager} cache is not found`
+          `Cache not found for input keys: ${platform}-0-setup-node-${packageManager}-${fileHash}, ${platform}-0-setup-node-${packageManager}-`
         );
         expect(setOutputSpy).toHaveBeenCalledWith('cache-hit', false);
       }
diff --git a/action.yml b/action.yml
index 99db5869..eca0ab50 100644
--- a/action.yml
+++ b/action.yml
@@ -25,6 +25,8 @@ inputs:
     description: 'Used to specify a package manager for caching in the default directory. Supported values: npm, yarn, pnpm.'
   cache-dependency-path:
     description: 'Used to specify the path to a dependency file: package-lock.json, yarn.lock, etc. Supports wildcards or a list of file names for caching multiple dependencies.'
+  cache-invalidate-after-days:
+    description: 'Used to control how often the fallback cache is invalidated automatically.'
 # TODO: add input to control forcing to pull from cloud or dist.
 #       escape valve for someone having issues or needing the absolute latest which isn't cached yet
 outputs:
diff --git a/dist/setup/index.js b/dist/setup/index.js
index 832d1db3..217e46ac 100644
--- a/dist/setup/index.js
+++ b/dist/setup/index.js
@@ -93305,7 +93305,7 @@ const path_1 = __importDefault(__nccwpck_require__(1017));
 const fs_1 = __importDefault(__nccwpck_require__(7147));
 const constants_1 = __nccwpck_require__(9042);
 const cache_utils_1 = __nccwpck_require__(1678);
-const restoreCache = (packageManager, cacheDependencyPath) => __awaiter(void 0, void 0, void 0, function* () {
+const restoreCache = (packageManager, cacheDependencyPath, cacheInvalidateAfterDays) => __awaiter(void 0, void 0, void 0, function* () {
     const packageManagerInfo = yield (0, cache_utils_1.getPackageManagerInfo)(packageManager);
     if (!packageManagerInfo) {
         throw new Error(`Caching for '${packageManager}' is not supported`);
@@ -93320,22 +93320,30 @@ const restoreCache = (packageManager, cacheDependencyPath) => __awaiter(void 0,
     if (!fileHash) {
         throw new Error('Some specified paths were not resolved, unable to cache dependencies.');
     }
-    const keyPrefix = `node-cache-${platform}-${packageManager}`;
+    const numericCacheInvalidateAfterDays = cacheInvalidateAfterDays && cacheInvalidateAfterDays === '0'
+        ? 0
+        : (parseInt(cacheInvalidateAfterDays || '', 10) || 120);
+    const timedInvalidationPrefix = numericCacheInvalidateAfterDays
+        ? Math.floor(Date.now() / (1000 * 60 * 60 * 24 * numericCacheInvalidateAfterDays)) % 1000 // % 1000 to get a rolling prefix between 0 and 999 rather than a possibly infinitely large
+        : 0;
+    const keyPrefixBase = `node-cache-${platform}-${packageManager}`;
+    const keyPrefix = `${keyPrefixBase}-${timedInvalidationPrefix}`;
     const primaryKey = `${keyPrefix}-${fileHash}`;
+    const restoreKeys = [`${keyPrefix}-`];
     core.debug(`primary key is ${primaryKey}`);
     core.saveState(constants_1.State.CachePrimaryKey, primaryKey);
     const isManagedByYarnBerry = yield (0, cache_utils_1.repoHasYarnBerryManagedDependencies)(packageManagerInfo, cacheDependencyPath);
     let cacheKey;
     if (isManagedByYarnBerry) {
         core.info('All dependencies are managed locally by yarn3, the previous cache can be used');
-        cacheKey = yield cache.restoreCache(cachePaths, primaryKey, [keyPrefix]);
+        cacheKey = yield cache.restoreCache(cachePaths, primaryKey, [keyPrefixBase]);
     }
     else {
-        cacheKey = yield cache.restoreCache(cachePaths, primaryKey);
+        cacheKey = yield cache.restoreCache(cachePaths, primaryKey, restoreKeys);
     }
     core.setOutput('cache-hit', Boolean(cacheKey));
     if (!cacheKey) {
-        core.info(`${packageManager} cache is not found`);
+        core.info(`Cache not found for input keys: ${[primaryKey, ...restoreKeys].join(', ')}`);
         return;
     }
     core.saveState(constants_1.State.CacheMatchedKey, cacheKey);
@@ -94470,7 +94478,8 @@ function run() {
             if (cache && (0, cache_utils_1.isCacheFeatureAvailable)()) {
                 core.saveState(constants_1.State.CachePackageManager, cache);
                 const cacheDependencyPath = core.getInput('cache-dependency-path');
-                yield (0, cache_restore_1.restoreCache)(cache, cacheDependencyPath);
+                const cacheInvalidateAfterDays = core.getInput('cache-invalidate-after-days');
+                yield (0, cache_restore_1.restoreCache)(cache, cacheDependencyPath, cacheInvalidateAfterDays);
             }
             const matchersPath = path.join(__dirname, '../..', '.github');
             core.info(`##[add-matcher]${path.join(matchersPath, 'tsc.json')}`);
diff --git a/src/cache-restore.ts b/src/cache-restore.ts
index 3b230970..deb912e5 100644
--- a/src/cache-restore.ts
+++ b/src/cache-restore.ts
@@ -14,7 +14,8 @@ import {
 
 export const restoreCache = async (
   packageManager: string,
-  cacheDependencyPath: string
+  cacheDependencyPath: string,
+  cacheInvalidateAfterDays?: string
 ) => {
   const packageManagerInfo = await getPackageManagerInfo(packageManager);
   if (!packageManagerInfo) {
@@ -37,9 +38,17 @@ export const restoreCache = async (
       'Some specified paths were not resolved, unable to cache dependencies.'
     );
   }
+  const numericCacheInvalidateAfterDays = cacheInvalidateAfterDays && cacheInvalidateAfterDays === '0'
+    ? 0
+    : (parseInt(cacheInvalidateAfterDays || '', 10) || 120)
+  const timedInvalidationPrefix = numericCacheInvalidateAfterDays
+    ? Math.floor(Date.now() / (1000 * 60 * 60 * 24 * numericCacheInvalidateAfterDays)) % 1000 // % 1000 to get a rolling prefix between 0 and 999 rather than a possibly infinitely large
+    : 0;
 
-  const keyPrefix = `node-cache-${platform}-${packageManager}`;
+  const keyPrefixBase = `node-cache-${platform}-${packageManager}`;
+  const keyPrefix = `${keyPrefixBase}-${timedInvalidationPrefix}`;
   const primaryKey = `${keyPrefix}-${fileHash}`;
+  const restoreKeys = [`${keyPrefix}-`];
   core.debug(`primary key is ${primaryKey}`);
 
   core.saveState(State.CachePrimaryKey, primaryKey);
@@ -53,15 +62,15 @@ export const restoreCache = async (
     core.info(
       'All dependencies are managed locally by yarn3, the previous cache can be used'
     );
-    cacheKey = await cache.restoreCache(cachePaths, primaryKey, [keyPrefix]);
+    cacheKey = await cache.restoreCache(cachePaths, primaryKey, [keyPrefixBase]);
   } else {
-    cacheKey = await cache.restoreCache(cachePaths, primaryKey);
+    cacheKey = await cache.restoreCache(cachePaths, primaryKey, restoreKeys);
   }
 
   core.setOutput('cache-hit', Boolean(cacheKey));
 
   if (!cacheKey) {
-    core.info(`${packageManager} cache is not found`);
+    core.info(`Cache not found for input keys: ${[primaryKey, ...restoreKeys].join(', ')}`);
     return;
   }
 
diff --git a/src/main.ts b/src/main.ts
index c55c3b00..f3adfc06 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -62,7 +62,8 @@ export async function run() {
     if (cache && isCacheFeatureAvailable()) {
       core.saveState(State.CachePackageManager, cache);
       const cacheDependencyPath = core.getInput('cache-dependency-path');
-      await restoreCache(cache, cacheDependencyPath);
+      const cacheInvalidateAfterDays = core.getInput('cache-invalidate-after-days');
+      await restoreCache(cache, cacheDependencyPath, cacheInvalidateAfterDays);
     }
 
     const matchersPath = path.join(__dirname, '../..', '.github');