diff --git a/lib/modules/versioning/composer/index.spec.ts b/lib/modules/versioning/composer/index.spec.ts
index e1bcbd602e90daf4dbf0e0f4c04b04ff4d127fb3..ba67d2f3c3a888d65dff3e413f8ec881d6523297 100644
--- a/lib/modules/versioning/composer/index.spec.ts
+++ b/lib/modules/versioning/composer/index.spec.ts
@@ -15,15 +15,22 @@ describe('modules/versioning/composer/index', () => {
     ${'1.0@alpha3'} | ${'1.0.0-alpha.3'} | ${true}
     ${'1.0@beta'}   | ${'1.0.0-beta'}    | ${true}
     ${'1.0@rc2'}    | ${'1.0.0-rc.2'}    | ${true}
+    ${'1.0.0'}      | ${'1.0.0-p1'}      | ${false}
   `('equals("$a", "$b") === $expected', ({ a, b, expected }) => {
     expect(semver.equals(a, b)).toBe(expected);
   });
 
   it.each`
-    a           | b         | expected
-    ${'1.2.0'}  | ${'v1.2'} | ${false}
-    ${'v1.0.1'} | ${'1'}    | ${true}
-    ${'1'}      | ${'1.1'}  | ${false}
+    a             | b             | expected
+    ${'1.2.0'}    | ${'v1.2'}     | ${false}
+    ${'v1.0.1'}   | ${'1'}        | ${true}
+    ${'1'}        | ${'1.1'}      | ${false}
+    ${'1.0.0'}    | ${'1.0.0-p1'} | ${false}
+    ${'1.0.0-p1'} | ${'1.0.0'}    | ${true}
+    ${'1.0.0-p1'} | ${'1.0.0-p2'} | ${false}
+    ${'1.0.0-p2'} | ${'1.0.0-p1'} | ${true}
+    ${'1'}        | ${'1.0-p1'}   | ${false}
+    ${'1.0-p1'}   | ${'1'}        | ${true}
   `('isGreaterThan("$a", "$b") === $expected', ({ a, b, expected }) => {
     expect(semver.isGreaterThan(a, b)).toBe(expected);
   });
@@ -37,8 +44,12 @@ describe('modules/versioning/composer/index', () => {
   });
 
   it.each`
-    version   | expected
-    ${'v1.2'} | ${true}
+    version           | expected
+    ${'v1.2'}         | ${true}
+    ${'v1.2.4-p2'}    | ${true}
+    ${'v1.2.4-p12'}   | ${true}
+    ${'v1.2.4-beta5'} | ${false}
+    ${null}           | ${false}
   `('isStable("$version") === $expected', ({ version, expected }) => {
     const res = !!semver.isStable(version);
     expect(res).toBe(expected);
@@ -71,6 +82,7 @@ describe('modules/versioning/composer/index', () => {
     ${'~1.0 || ~2.0'} | ${true}
     ${'<8.0-DEV'}     | ${true}
     ${'<8-DEV'}       | ${true}
+    ${'1.2.3-p1'}     | ${true}
   `('isValid("$version") === $expected', ({ version, expected }) => {
     const res = !!semver.isValid(version);
     expect(res).toBe(expected);
@@ -91,6 +103,7 @@ describe('modules/versioning/composer/index', () => {
     ${['v0.4.0', 'v0.5.0', 'v4.0.0', 'v4.2.0', 'v5.0.0']}                                      | ${'~4'}      | ${'v4.2.0'}
     ${['0.4.0', '0.5.0', '4.0.0', '4.2.0', '5.0.0']}                                           | ${'~0.4'}    | ${'0.5.0'}
     ${['0.4.0', '0.5.0', '4.0.0-beta1', '4.0.0-beta2', '4.2.0-beta1', '4.2.0-beta2', '5.0.0']} | ${'~4@beta'} | ${'4.0.0-beta2'}
+    ${['4.0.0', '4.2.0', '5.0.0', '4.2.0-p2', '4.2.0-p12']}                                    | ${'~4'}      | ${'4.2.0-p12'}
   `(
     'getSatisfyingVersion($versions, "$range") === $expected',
     ({ versions, range, expected }) => {
@@ -105,6 +118,8 @@ describe('modules/versioning/composer/index', () => {
     ${['v0.4.0', 'v0.5.0', 'v4.0.0', 'v4.2.0', 'v5.0.0']}                                | ${'~4'}      | ${'v4.0.0'}
     ${['0.4.0', '0.5.0', '4.0.0', '4.2.0', '5.0.0']}                                     | ${'~0.4'}    | ${'0.4.0'}
     ${['0.4.0', '0.5.0', '4.0.0-beta1', '4.0.0', '4.2.0-beta1', '4.2.0-beta2', '5.0.0']} | ${'~4@beta'} | ${'4.0.0-beta1'}
+    ${['0.4.0', '0.5.0', '4.0.0-p1', '4.0.0', '4.2.0-p1', '4.2.0-p2', '5.0.0']}          | ${'~4'}      | ${'4.0.0'}
+    ${['0.4.0', '0.5.0', '4.0.0-p1', '4.2.0-p1', '4.2.0-p2', '5.0.0']}                   | ${'~4'}      | ${'4.0.0-p1'}
   `(
     'minSatisfyingVersion($versions, "$range") === $expected',
     ({ versions, range, expected }) => {
@@ -203,13 +218,17 @@ describe('modules/versioning/composer/index', () => {
   it.each`
     versions                                                                      | expected
     ${['1.2.3-beta', '1.0.0-alpha24', '2.0.1', '1.3.4', '1.0.0-alpha9', '1.2.3']} | ${['1.0.0-alpha9', '1.0.0-alpha24', '1.2.3-beta', '1.2.3', '1.3.4', '2.0.1']}
+    ${['1.2.3-p1', '1.2.3-p2', '1.2.3']}                                          | ${['1.2.3', '1.2.3-p1', '1.2.3-p2']}
+    ${['1.2.3-p1', '1.2.2']}                                                      | ${['1.2.2', '1.2.3-p1']}
+    ${['1.0-p1', '1']}                                                            | ${['1', '1.0-p1']}
   `('$versions -> sortVersions -> $expected ', ({ versions, expected }) => {
     expect(versions.sort(semver.sortVersions)).toEqual(expected);
   });
 
   it.each`
-    version    | expected
-    ${'1.2.0'} | ${true}
+    version       | expected
+    ${'1.2.0'}    | ${true}
+    ${'1.2.0-p1'} | ${true}
   `('isCompatible("$version") === $expected', ({ version, expected }) => {
     expect(semver.isCompatible(version)).toBe(expected);
   });
diff --git a/lib/modules/versioning/composer/index.ts b/lib/modules/versioning/composer/index.ts
index f3de82dcd19a442c96b784b44e64c45bdda389a6..a176856e35bcd46e7c9b39cd6789a2aefb14f072 100644
--- a/lib/modules/versioning/composer/index.ts
+++ b/lib/modules/versioning/composer/index.ts
@@ -67,6 +67,57 @@ function normalizeVersion(input: string): string {
   return convertStabilityModifier(output);
 }
 
+/**
+ * @param versions Version list in any format, it recognizes the specific patch format x.x.x-pXX
+ * @param range Range in composer format
+ * @param minMode If true, it will calculate minSatisfyingVersion, if false, it calculates the maxSatisfyingVersion
+ * @returns min or max satisfyingVersion from the input
+ */
+function calculateSatisfyingVersionIntenal(
+  versions: string[],
+  range: string,
+  minMode: boolean
+): string | null {
+  // Because composer -p versions are considered stable, we have to remove the suffix for the npm.XXX functions.
+  const versionsMapped = versions.map((x) => {
+    return {
+      origianl: x,
+      cleaned: removeComposerSpecificPatchPart(x),
+      npmVariant: composer2npm(removeComposerSpecificPatchPart(x)[0]),
+    };
+  });
+
+  const npmVersions = versionsMapped.map((x) => x.npmVariant);
+  const npmVersion = minMode
+    ? npm.minSatisfyingVersion(npmVersions, composer2npm(range))
+    : npm.getSatisfyingVersion(npmVersions, composer2npm(range));
+
+  if (!npmVersion) {
+    return null;
+  }
+
+  // After we find the npm versions, we select from them back in the mapping the possible patches.
+  const candidates = versionsMapped
+    .filter((x) => x.npmVariant === npmVersion)
+    .sort((a, b) => (minMode ? 1 : -1) * sortVersions(a.origianl, b.origianl));
+
+  return candidates[0].origianl;
+}
+
+/**
+ * @param intput Version in any format, it recognizes the specific patch format x.x.x-pXX
+ * @returns If input contains the specific patch, it returns the input with removed the patch and true, otherwise it retunrs the same string and false.
+ */
+function removeComposerSpecificPatchPart(input: string): [string, boolean] {
+  // the regex is based on the original from composer implementation https://github.com/composer/semver/blob/fa1ec24f0ab1efe642671ec15c51a3ab879f59bf/src/VersionParser.php#L137
+  const pattern = /^v?\d+(\.\d+(\.\d+(\.\d+)?)?)?(?<suffix>-p[1-9]\d*)$/gi;
+  const match = pattern.exec(input);
+
+  return match
+    ? [input.replace(match.groups!.suffix, ''), true]
+    : [input, false];
+}
+
 function composer2npm(input: string): string {
   return input
     .split(regEx(/\s*\|\|?\s*/g))
@@ -119,11 +170,14 @@ function getMinor(version: string): number | null {
 
 function getPatch(version: string): number | null {
   const semverVersion = semver.coerce(composer2npm(version));
+
+  // This returns only the numbers without the optional `-pXX` patch version supported by composer. Fixing that would require a bigger
+  // refactoring, because the API supports only numbers.
   return semverVersion ? npm.getPatch(semverVersion) : null;
 }
 
 function isGreaterThan(a: string, b: string): boolean {
-  return npm.isGreaterThan(composer2npm(a), composer2npm(b));
+  return sortVersions(a, b) === 1;
 }
 
 function isLessThanRange(version: string, range: string): boolean {
@@ -135,7 +189,14 @@ function isSingleVersion(input: string): boolean {
 }
 
 function isStable(version: string): boolean {
-  return !!(version && npm.isStable(composer2npm(version)));
+  if (version) {
+    // Composer considers patches `-pXX` as stable: https://github.com/composer/semver/blob/fa1ec24f0ab1efe642671ec15c51a3ab879f59bf/src/VersionParser.php#L568 but npm not.
+    // In order to be able to use the standard npm.isStable function, we remove the potential patch version for the check.
+    const [withoutPatch] = removeComposerSpecificPatchPart(version);
+    return npm.isStable(composer2npm(withoutPatch));
+  }
+
+  return false;
 }
 
 export function isValid(input: string): boolean {
@@ -154,26 +215,14 @@ function getSatisfyingVersion(
   versions: string[],
   range: string
 ): string | null {
-  const npmVersions = versions.map(composer2npm);
-  const npmVersion = npm.getSatisfyingVersion(npmVersions, composer2npm(range));
-  if (!npmVersion) {
-    return null;
-  }
-  // get index of npmVersion in npmVersions
-  return versions[npmVersions.indexOf(npmVersion)] ?? npmVersion;
+  return calculateSatisfyingVersionIntenal(versions, range, false);
 }
 
 function minSatisfyingVersion(
   versions: string[],
   range: string
 ): string | null {
-  const npmVersions = versions.map(composer2npm);
-  const npmVersion = npm.minSatisfyingVersion(npmVersions, composer2npm(range));
-  if (!npmVersion) {
-    return null;
-  }
-  // get index of npmVersion in npmVersions
-  return versions[npmVersions.indexOf(npmVersion)] ?? npmVersion;
+  return calculateSatisfyingVersionIntenal(versions, range, true);
 }
 
 function subset(subRange: string, superRange: string): boolean | undefined {
@@ -305,7 +354,21 @@ function getNewValue({
 }
 
 function sortVersions(a: string, b: string): number {
-  return npm.sortVersions(composer2npm(a), composer2npm(b));
+  const [aWithoutPatch, aContainsPatch] = removeComposerSpecificPatchPart(a);
+  const [bWithoutPatch, bContainsPatch] = removeComposerSpecificPatchPart(b);
+
+  if (aContainsPatch === bContainsPatch) {
+    // If both [a and b] contain patch version or both [a and b] do not contain patch version, then npm comparison deliveres correct results
+    return npm.sortVersions(composer2npm(a), composer2npm(b));
+  } else if (
+    npm.equals(composer2npm(aWithoutPatch), composer2npm(bWithoutPatch))
+  ) {
+    // If only one [a or b] contains patch version and the parts without patch versions are equal, then the version with patch is greater (this is the case where npm comparison fails)
+    return aContainsPatch ? 1 : -1;
+  } else {
+    // All other cases can be compared correctly by npm
+    return npm.sortVersions(composer2npm(a), composer2npm(b));
+  }
 }
 
 function isCompatible(version: string): boolean {
diff --git a/lib/modules/versioning/composer/readme.md b/lib/modules/versioning/composer/readme.md
index dc8fd52f9b570b92f458ec73ec6502979d882017..88e43441d52cd04f2c88c1b798ef0e745fa9763b 100644
--- a/lib/modules/versioning/composer/readme.md
+++ b/lib/modules/versioning/composer/readme.md
@@ -7,3 +7,6 @@ Tilde ranges with "short" versions are different to npm. e.g.
 `~4` is equivalent to `^4` in npm
 `~4.1` is equivalent to `^4.1` in npm
 `~0.4` is equivalent to `>=0.4 <1` in npm
+
+Composer supports patches in the version numbers, which are considered stable. E.g., `1.2.3-p1` is recognized by npm as unstable, Renovate
+implements additional logic to support correct sorting and stability checks on composer patch versions syntax.