diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md
index f780c472f7fef8691a5fc8a965b99c86cccdc69c..68b14902227d933af957f3a295528e19473c8e13 100644
--- a/docs/usage/configuration-options.md
+++ b/docs/usage/configuration-options.md
@@ -2375,7 +2375,9 @@ You can configure the `rollbackPrs` property globally, per-lanuage, or per-packa
 ## schedule
 
 The `schedule` option allows you to define times of week or month for Renovate updates.
-Running Renovate around the clock may seem too "noisy" for some projects and therefore `schedule` is a good way to reduce the noise by reducing the timeframe in which Renovate will operate on your repository.
+Running Renovate around the clock can be too "noisy" for some projects.
+To reduce the noise you can use the `schedule` config option to limit the time frame in which Renovate will perform actions on your repository.
+You can use the standard [Cron syntax](https://crontab.guru/crontab.5.html) and [Later syntax](https://github.com/breejs/later) to define your schedule.
 
 The default value for `schedule` is "at any time", which is functionally the same as declaring a `null` schedule.
 i.e. Renovate will run on the repository around the clock.
@@ -2392,8 +2394,11 @@ after 10pm and before 5:00am
 after 10pm and before 5am every weekday
 on friday and saturday
 every 3 months on the first day of the month
+* 0 2 * *
 ```
 
+Note: For Cron schedules, you _must_ use the `*` wildcard for the minutes value, as Renovate doesn't support minute granularity.
+
 One example might be that you don't want Renovate to run during your typical business hours, so that your build machines don't get clogged up testing `package.json` updates.
 You could then configure a schedule like this at the repository level:
 
@@ -2422,6 +2427,7 @@ To restrict `aws-sdk` to only monthly updates, you could add this package rule:
 
 Technical details: We mostly rely on the text parsing of the library [@breejs/later](https://github.com/breejs/later) but only its concepts of "days", "time_before", and "time_after".
 Read the parser documentation at [breejs.github.io/later/parsers.html#text](https://breejs.github.io/later/parsers.html#text).
+To parse Cron syntax, Renovate uses [@cheap-glitch/mi-cron](https://github.com/cheap-glitch/mi-cron).
 Renovate does not support scheduled minutes or "at an exact time" granularity.
 
 ## semanticCommitScope
diff --git a/lib/config/validation.spec.ts b/lib/config/validation.spec.ts
index ea0566e943ac39844ba6b3866947604ad2a9c19c..1f31f88bd5d5005468a3915d2ccd608c0dead2a5 100644
--- a/lib/config/validation.spec.ts
+++ b/lib/config/validation.spec.ts
@@ -699,5 +699,22 @@ describe('config/validation', () => {
         },
       ]);
     });
+
+    it('errors if schedule is cron and has no * minutes', async () => {
+      const config = {
+        schedule: ['30 5 * * *'],
+      };
+      const { warnings, errors } = await configValidation.validateConfig(
+        config
+      );
+      expect(warnings).toHaveLength(0);
+      expect(errors).toMatchObject([
+        {
+          message:
+            'Invalid schedule: `Invalid schedule: "30 5 * * *" has cron syntax, but doesn\'t have * as minutes`',
+          topic: 'Configuration Error',
+        },
+      ]);
+    });
   });
 });
diff --git a/lib/workers/branch/schedule.spec.ts b/lib/workers/branch/schedule.spec.ts
index bfe2a4f9c77e325c68601df837c690fe364524fb..605a1ccf6b4b6eb291672ea45b6bb3142f11118c 100644
--- a/lib/workers/branch/schedule.spec.ts
+++ b/lib/workers/branch/schedule.spec.ts
@@ -81,6 +81,11 @@ describe('workers/branch/schedule', () => {
         ])[0]
       ).toBeTrue();
     });
+
+    it('returns true if schedule uses cron syntax', () => {
+      expect(schedule.hasValidSchedule(['* 5 * * *'])[0]).toBeTrue();
+    });
+
     it('massages schedules', () => {
       expect(
         schedule.hasValidSchedule([
@@ -150,11 +155,53 @@ describe('workers/branch/schedule', () => {
       const res = schedule.isScheduledNow(config);
       expect(res).toBeFalse();
     });
+
     it('supports outside hours', () => {
       config.schedule = ['after 4:00pm'];
       const res = schedule.isScheduledNow(config);
       expect(res).toBeFalse();
     });
+
+    it('supports cron syntax with hours', () => {
+      config.schedule = ['* 10 * * *'];
+      let res = schedule.isScheduledNow(config);
+      expect(res).toBeTrue();
+
+      config.schedule = ['* 11 * * *'];
+      res = schedule.isScheduledNow(config);
+      expect(res).toBeFalse();
+    });
+
+    it('supports cron syntax with days', () => {
+      config.schedule = ['* * 30 * *'];
+      let res = schedule.isScheduledNow(config);
+      expect(res).toBeTrue();
+
+      config.schedule = ['* * 1 * *'];
+      res = schedule.isScheduledNow(config);
+      expect(res).toBeFalse();
+    });
+
+    it('supports cron syntax with months', () => {
+      config.schedule = ['* * * 6 *'];
+      let res = schedule.isScheduledNow(config);
+      expect(res).toBeTrue();
+
+      config.schedule = ['* * * 7 *'];
+      res = schedule.isScheduledNow(config);
+      expect(res).toBeFalse();
+    });
+
+    it('supports cron syntax with weekdays', () => {
+      config.schedule = ['* * * * 5'];
+      let res = schedule.isScheduledNow(config);
+      expect(res).toBeTrue();
+
+      config.schedule = ['* * * * 6'];
+      res = schedule.isScheduledNow(config);
+      expect(res).toBeFalse();
+    });
+
     describe('supports timezone', () => {
       const cases: [string, string, string, boolean][] = [
         ['after 4pm', 'Asia/Singapore', '2017-06-30T15:59:00.000+0800', false],
diff --git a/lib/workers/branch/schedule.ts b/lib/workers/branch/schedule.ts
index a84d7ed839ae551d1b618f9d55a3532bb2e3ee8b..8dd9efc6abdc2d888a36ae2eb3872007de6a32fb 100644
--- a/lib/workers/branch/schedule.ts
+++ b/lib/workers/branch/schedule.ts
@@ -1,10 +1,13 @@
 import later from '@breejs/later';
+import { parseCron } from '@cheap-glitch/mi-cron';
 import is from '@sindresorhus/is';
 import { DateTime } from 'luxon';
 import { fixShortHours } from '../../config/migration';
 import type { RenovateConfig } from '../../config/types';
 import { logger } from '../../logger';
 
+const minutesChar = '*';
+
 const scheduleMappings: Record<string, string> = {
   'every month': 'before 3am on the first day of the month',
   monthly: 'before 3am on the first day of the month',
@@ -32,9 +35,24 @@ export function hasValidSchedule(
   }
   // check if any of the schedules fail to parse
   const hasFailedSchedules = schedule.some((scheduleText) => {
+    const parsedCron = parseCron(scheduleText);
+    if (parsedCron !== undefined) {
+      if (
+        parsedCron.minutes.length !== 60 ||
+        scheduleText.indexOf(minutesChar) !== 0
+      ) {
+        message = `Invalid schedule: "${scheduleText}" has cron syntax, but doesn't have * as minutes`;
+        return true;
+      }
+
+      // It was valid cron syntax and * as minutes
+      return false;
+    }
+
     const massagedText = fixShortHours(
       scheduleMappings[scheduleText] || scheduleText
     );
+
     const parsedSchedule = later.parse.text(massagedText);
     if (parsedSchedule.error !== -1) {
       message = `Invalid schedule: Failed to parse "${scheduleText}"`;
@@ -63,6 +81,33 @@ export function hasValidSchedule(
   return [true, ''];
 }
 
+function cronMatches(cron: string, now: DateTime): boolean {
+  const parsedCron = parseCron(cron);
+
+  if (parsedCron.hours.indexOf(now.hour) === -1) {
+    // Hours mismatch
+    return false;
+  }
+
+  if (parsedCron.days.indexOf(now.day) === -1) {
+    // Days mismatch
+    return false;
+  }
+
+  if (parsedCron.weekDays.indexOf(now.weekday) === -1) {
+    // Weekdays mismatch
+    return false;
+  }
+
+  if (parsedCron.months.indexOf(now.month) === -1) {
+    // Months mismatch
+    return false;
+  }
+
+  // Match
+  return true;
+}
+
 export function isScheduledNow(config: RenovateConfig): boolean {
   let configSchedule = config.schedule;
   logger.debug(
@@ -119,13 +164,23 @@ export function isScheduledNow(config: RenovateConfig): boolean {
 
   // We run if any schedule matches
   const isWithinSchedule = configSchedule.some((scheduleText) => {
-    const massagedText = scheduleMappings[scheduleText] || scheduleText;
-    const parsedSchedule = later.parse.text(fixShortHours(massagedText));
-    logger.debug({ parsedSchedule }, `Checking schedule "${scheduleText}"`);
+    const cronSchedule = parseCron(scheduleText);
+    if (cronSchedule) {
+      // We have Cron syntax
+      if (cronMatches(scheduleText, now)) {
+        logger.debug(`Matches schedule ${scheduleText}`);
+        return true;
+      }
+    } else {
+      // We have Later syntax
+      const massagedText = scheduleMappings[scheduleText] || scheduleText;
+      const parsedSchedule = later.parse.text(fixShortHours(massagedText));
+      logger.debug({ parsedSchedule }, `Checking schedule "${scheduleText}"`);
 
-    if (later.schedule(parsedSchedule).isValid(jsNow)) {
-      logger.debug(`Matches schedule ${scheduleText}`);
-      return true;
+      if (later.schedule(parsedSchedule).isValid(jsNow)) {
+        logger.debug(`Matches schedule ${scheduleText}`);
+        return true;
+      }
     }
 
     return false;
diff --git a/package.json b/package.json
index 995624821cc0273390c2f3df559aa9b996f176ff..48662da32156d5538a4d0e0afee24dda4824bb64 100644
--- a/package.json
+++ b/package.json
@@ -214,6 +214,7 @@
     "@jest/globals": "27.4.6",
     "@jest/reporters": "27.4.6",
     "@jest/test-result": "27.4.6",
+    "@cheap-glitch/mi-cron": "1.0.1",
     "@ls-lint/ls-lint": "1.10.0",
     "@renovate/eslint-plugin": "https://github.com/renovatebot/eslint-plugin#v0.0.4",
     "@semantic-release/exec": "6.0.3",
diff --git a/yarn.lock b/yarn.lock
index ceffb3be8a3d6a613e5c0124d68f017509999cb3..b258a8da1c79a79a403a3d272938576beb958de5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1241,6 +1241,11 @@
   resolved "https://registry.yarnpkg.com/@breejs/later/-/later-4.1.0.tgz#9246907f46cc9e9c9af37d791ab468d98921bcc1"
   integrity sha512-QgGnZ9b7o4k0Ai1ZbTJWwZpZcFK9d+Gb+DyNt4UT9x6IEIs5HVu0iIlmgzGqN+t9MoJSpSPo9S/Mm51UtHr3JA==
 
+"@cheap-glitch/mi-cron@1.0.1":
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/@cheap-glitch/mi-cron/-/mi-cron-1.0.1.tgz#111f4ce746c269aedf74533ac806881763a68f99"
+  integrity sha512-kxl7vhg+SUgyHRn22qVbR9MfSm5CzdlYZDJTbGemqFFi/Jmno/hdoQIvBIPoqFY9dcPyxzOUNRRFn6x88UQMpw==
+
 "@chevrotain/types@^9.1.0":
   version "9.1.0"
   resolved "https://registry.yarnpkg.com/@chevrotain/types/-/types-9.1.0.tgz#689f2952be5ad9459dae3c8e9209c0f4ec3c5ec4"