diff --git a/backend/src/config/mock/note.config.mock.ts b/backend/src/config/mock/note.config.mock.ts
index 318e4287e..45d7e1b7d 100644
--- a/backend/src/config/mock/note.config.mock.ts
+++ b/backend/src/config/mock/note.config.mock.ts
@@ -21,6 +21,7 @@ export function createDefaultMockNoteConfig(): NoteConfig {
       },
     },
     guestAccess: GuestAccess.CREATE,
+    revisionRetentionDays: 0,
   };
 }
 
diff --git a/backend/src/config/note.config.spec.ts b/backend/src/config/note.config.spec.ts
index d4f615637..2f4930976 100644
--- a/backend/src/config/note.config.spec.ts
+++ b/backend/src/config/note.config.spec.ts
@@ -19,6 +19,7 @@ describe('noteConfig', () => {
   const invalidMaxDocumentLength = 'not-a-max-document-length';
   const guestAccess = GuestAccess.CREATE;
   const wrongDefaultPermission = 'wrong';
+  const retentionDays = 30;
 
   describe('correctly parses config', () => {
     it('when given correct and complete environment variables', () => {
@@ -30,6 +31,7 @@ describe('noteConfig', () => {
           HD_PERMISSION_DEFAULT_EVERYONE: DefaultAccessLevel.READ,
           HD_PERMISSION_DEFAULT_LOGGED_IN: DefaultAccessLevel.READ,
           HD_GUEST_ACCESS: guestAccess,
+          HD_REVISION_RETENTION_DAYS: retentionDays.toString(),
           /* eslint-enable @typescript-eslint/naming-convention */
         },
         {
@@ -47,6 +49,7 @@ describe('noteConfig', () => {
         DefaultAccessLevel.READ,
       );
       expect(config.guestAccess).toEqual(guestAccess);
+      expect(config.revisionRetentionDays).toEqual(retentionDays);
       restore();
     });
 
@@ -221,6 +224,36 @@ describe('noteConfig', () => {
       expect(config.guestAccess).toEqual(GuestAccess.WRITE);
       restore();
     });
+
+    it('when no HD_REVISION_RETENTION_DAYS is set', () => {
+      const restore = mockedEnv(
+        {
+          /* eslint-disable @typescript-eslint/naming-convention */
+          HD_FORBIDDEN_NOTE_IDS: forbiddenNoteIds.join(' , '),
+          HD_MAX_DOCUMENT_LENGTH: maxDocumentLength.toString(),
+          HD_PERMISSION_DEFAULT_EVERYONE: DefaultAccessLevel.READ,
+          HD_PERMISSION_DEFAULT_LOGGED_IN: DefaultAccessLevel.READ,
+          HD_GUEST_ACCESS: guestAccess,
+          /* eslint-enable @typescript-eslint/naming-convention */
+        },
+        {
+          clear: true,
+        },
+      );
+      const config = noteConfig();
+      expect(config.forbiddenNoteIds).toHaveLength(forbiddenNoteIds.length);
+      expect(config.forbiddenNoteIds).toEqual(forbiddenNoteIds);
+      expect(config.maxDocumentLength).toEqual(maxDocumentLength);
+      expect(config.permissions.default.everyone).toEqual(
+        DefaultAccessLevel.READ,
+      );
+      expect(config.permissions.default.loggedIn).toEqual(
+        DefaultAccessLevel.READ,
+      );
+      expect(config.guestAccess).toEqual(guestAccess);
+      expect(config.revisionRetentionDays).toEqual(0);
+      restore();
+    });
   });
 
   describe('throws error', () => {
@@ -454,5 +487,27 @@ describe('noteConfig', () => {
       );
       restore();
     });
+
+    it('when given a negative retention days', async () => {
+      const restore = mockedEnv(
+        {
+          /* eslint-disable @typescript-eslint/naming-convention */
+          HD_FORBIDDEN_NOTE_IDS: forbiddenNoteIds.join(' , '),
+          HD_MAX_DOCUMENT_LENGTH: maxDocumentLength.toString(),
+          HD_PERMISSION_DEFAULT_EVERYONE: DefaultAccessLevel.READ,
+          HD_PERMISSION_DEFAULT_LOGGED_IN: DefaultAccessLevel.READ,
+          HD_GUEST_ACCESS: guestAccess,
+          HD_REVISION_RETENTION_DAYS: (-1).toString(),
+          /* eslint-enable @typescript-eslint/naming-convention */
+        },
+        {
+          clear: true,
+        },
+      );
+      expect(() => noteConfig()).toThrow(
+        '"HD_REVISION_RETENTION_DAYS" must be greater than or equal to 0',
+      );
+      restore();
+    });
   });
 });
diff --git a/backend/src/config/note.config.ts b/backend/src/config/note.config.ts
index dc808112f..c7615e437 100644
--- a/backend/src/config/note.config.ts
+++ b/backend/src/config/note.config.ts
@@ -23,6 +23,7 @@ export interface NoteConfig {
       loggedIn: DefaultAccessLevel;
     };
   };
+  revisionRetentionDays: number;
 }
 
 const schema = Joi.object<NoteConfig>({
@@ -56,6 +57,12 @@ const schema = Joi.object<NoteConfig>({
         .label('HD_PERMISSION_DEFAULT_LOGGED_IN'),
     },
   },
+  revisionRetentionDays: Joi.number()
+    .integer()
+    .default(0)
+    .min(0)
+    .optional()
+    .label('HD_REVISION_RETENTION_DAYS'),
 });
 
 function checkEveryoneConfigIsConsistent(config: NoteConfig): void {
@@ -97,6 +104,9 @@ export default registerAs('noteConfig', () => {
           loggedIn: process.env.HD_PERMISSION_DEFAULT_LOGGED_IN,
         },
       },
+      revisionRetentionDays: parseOptionalNumber(
+        process.env.HD_REVISION_RETENTION_DAYS,
+      ),
     } as NoteConfig,
     {
       abortEarly: false,
diff --git a/backend/src/revisions/revisions.module.ts b/backend/src/revisions/revisions.module.ts
index 453339c6b..ccafea294 100644
--- a/backend/src/revisions/revisions.module.ts
+++ b/backend/src/revisions/revisions.module.ts
@@ -9,6 +9,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
 
 import { AuthorsModule } from '../authors/authors.module';
 import { LoggerModule } from '../logger/logger.module';
+import { Note } from '../notes/note.entity';
 import { Edit } from './edit.entity';
 import { EditService } from './edit.service';
 import { Revision } from './revision.entity';
@@ -16,7 +17,7 @@ import { RevisionsService } from './revisions.service';
 
 @Module({
   imports: [
-    TypeOrmModule.forFeature([Revision, Edit]),
+    TypeOrmModule.forFeature([Revision, Edit, Note]),
     LoggerModule,
     ConfigModule,
     AuthorsModule,
diff --git a/backend/src/revisions/revisions.service.spec.ts b/backend/src/revisions/revisions.service.spec.ts
index f8964165e..b6ad9398f 100644
--- a/backend/src/revisions/revisions.service.spec.ts
+++ b/backend/src/revisions/revisions.service.spec.ts
@@ -7,8 +7,9 @@ import { ConfigModule } from '@nestjs/config';
 import { EventEmitterModule } from '@nestjs/event-emitter';
 import { Test, TestingModule } from '@nestjs/testing';
 import { getRepositoryToken } from '@nestjs/typeorm';
+import { createPatch } from 'diff';
 import { Mock } from 'ts-mockery';
-import { Repository } from 'typeorm';
+import { DataSource, EntityManager, Repository } from 'typeorm';
 
 import { ApiToken } from '../api-token/api-token.entity';
 import { Author } from '../authors/author.entity';
@@ -16,6 +17,11 @@ import appConfigMock from '../config/mock/app.config.mock';
 import authConfigMock from '../config/mock/auth.config.mock';
 import databaseConfigMock from '../config/mock/database.config.mock';
 import noteConfigMock from '../config/mock/note.config.mock';
+import {
+  createDefaultMockNoteConfig,
+  registerNoteConfig,
+} from '../config/mock/note.config.mock';
+import { NoteConfig } from '../config/note.config';
 import { NotInDBError } from '../errors/errors';
 import { eventModuleConfig } from '../events';
 import { Group } from '../groups/group.entity';
@@ -37,8 +43,21 @@ import { RevisionsService } from './revisions.service';
 describe('RevisionsService', () => {
   let service: RevisionsService;
   let revisionRepo: Repository<Revision>;
+  let noteRepo: Repository<Note>;
+  const noteConfig: NoteConfig = createDefaultMockNoteConfig();
 
   beforeEach(async () => {
+    noteRepo = new Repository<Note>(
+      '',
+      new EntityManager(
+        new DataSource({
+          type: 'sqlite',
+          database: ':memory:',
+        }),
+      ),
+      undefined,
+    );
+
     const module: TestingModule = await Test.createTestingModule({
       providers: [
         RevisionsService,
@@ -47,6 +66,10 @@ describe('RevisionsService', () => {
           provide: getRepositoryToken(Revision),
           useClass: Repository,
         },
+        {
+          provide: getRepositoryToken(Note),
+          useClass: Repository,
+        },
       ],
       imports: [
         NotesModule,
@@ -58,6 +81,7 @@ describe('RevisionsService', () => {
             databaseConfigMock,
             authConfigMock,
             noteConfigMock,
+            registerNoteConfig(noteConfig),
           ],
         }),
         EventEmitterModule.forRoot(eventModuleConfig),
@@ -72,7 +96,7 @@ describe('RevisionsService', () => {
       .overrideProvider(getRepositoryToken(Identity))
       .useValue({})
       .overrideProvider(getRepositoryToken(Note))
-      .useValue({})
+      .useValue(noteRepo)
       .overrideProvider(getRepositoryToken(Revision))
       .useClass(Repository)
       .overrideProvider(getRepositoryToken(Tag))
@@ -95,6 +119,7 @@ describe('RevisionsService', () => {
     revisionRepo = module.get<Repository<Revision>>(
       getRepositoryToken(Revision),
     );
+    noteRepo = module.get<Repository<Note>>(getRepositoryToken(Note));
   });
 
   it('should be defined', () => {
@@ -423,4 +448,163 @@ describe('RevisionsService', () => {
       expect(repoSaveSpy).not.toHaveBeenCalled();
     });
   });
+
+  describe('auto remove old revisions', () => {
+    beforeEach(() => {
+      jest.spyOn(service, 'removeOldRevisions');
+    });
+
+    it('handleCron should call removeOldRevisions', async () => {
+      await service.handleCron();
+      expect(service.removeOldRevisions).toHaveBeenCalledTimes(1);
+    });
+
+    it('handleTimeout should call removeOldRevisions', async () => {
+      await service.handleTimeout();
+      expect(service.removeOldRevisions).toHaveBeenCalledTimes(1);
+    });
+  });
+
+  describe('removeOldRevisions', () => {
+    let note: Note;
+    let notes: Note[];
+    let revisions: Revision[];
+    let oldRevisions: Revision[];
+    const retentionDays = 30;
+
+    beforeEach(() => {
+      noteConfig.revisionRetentionDays = retentionDays;
+
+      note = Mock.of<Note>({ publicId: 'test-note', id: 1 });
+      notes = [note];
+    });
+
+    afterEach(() => {
+      jest.clearAllMocks();
+    });
+
+    it('remove all revisions except latest revision', async () => {
+      const date1 = new Date();
+      const date2 = new Date();
+      const date3 = new Date();
+      date1.setDate(date1.getDate() - retentionDays - 2);
+      date2.setDate(date2.getDate() - retentionDays - 1);
+
+      const revision1 = Mock.of<Revision>({
+        id: 1,
+        createdAt: date1,
+        note: Promise.resolve(note),
+      });
+      const revision2 = Mock.of<Revision>({
+        id: 2,
+        createdAt: date2,
+        note: Promise.resolve(note),
+        content: 'old content\n',
+      });
+      const revision3 = Mock.of<Revision>({
+        id: 3,
+        createdAt: date3,
+        note: Promise.resolve(note),
+        content:
+          '---\ntitle: new title\ndescription: new description\ntags: [ "tag1" ]\n---\nnew content\n',
+      });
+      revision3.patch = createPatch(
+        note.publicId,
+        revision2.content,
+        revision3.content,
+      );
+
+      revisions = [revision1, revision2, revision3];
+      oldRevisions = [revision1, revision2];
+
+      jest.spyOn(noteRepo, 'find').mockResolvedValueOnce(notes);
+      jest.spyOn(revisionRepo, 'find').mockResolvedValueOnce(revisions);
+      jest
+        .spyOn(revisionRepo, 'remove')
+        .mockImplementationOnce(async (entry, _) => {
+          expect(entry).toEqual(oldRevisions);
+          return entry;
+        });
+      jest.spyOn(revisionRepo, 'save').mockResolvedValue(revision3);
+
+      await service.removeOldRevisions();
+      expect(revision3.patch).toMatchSnapshot;
+    });
+
+    it('remove a part of old revisions', async () => {
+      const date1 = new Date();
+      const date2 = new Date();
+      const date3 = new Date();
+      date1.setDate(date1.getDate() - retentionDays);
+      date2.setDate(date2.getDate() - retentionDays + 1);
+
+      const revision1 = Mock.of<Revision>({
+        id: 1,
+        createdAt: date1,
+        note: Promise.resolve(note),
+        content: 'old content\n',
+      });
+      const revision2 = Mock.of<Revision>({
+        id: 2,
+        createdAt: date2,
+        note: Promise.resolve(note),
+        content:
+          '---\ntitle: new title\ndescription: new description\ntags: [ "tag1" ]\n---\nnew content\n',
+      });
+      const revision3 = Mock.of<Revision>({
+        id: 3,
+        createdAt: date3,
+        note: Promise.resolve(note),
+      });
+      revision2.patch = createPatch(
+        note.publicId,
+        revision1.content,
+        revision2.content,
+      );
+
+      revisions = [revision1, revision2, revision3];
+      oldRevisions = [revision1];
+
+      jest.spyOn(noteRepo, 'find').mockResolvedValueOnce(notes);
+      jest.spyOn(revisionRepo, 'find').mockResolvedValueOnce(revisions);
+      jest
+        .spyOn(revisionRepo, 'remove')
+        .mockImplementationOnce(async (entry, _) => {
+          expect(entry).toEqual(oldRevisions);
+          return entry;
+        });
+      jest.spyOn(revisionRepo, 'save').mockResolvedValue(revision2);
+
+      await service.removeOldRevisions();
+      expect(revision2.patch).toMatchSnapshot;
+    });
+
+    it('do nothing when only one revision', async () => {
+      const date = new Date();
+      date.setDate(date.getDate() - retentionDays * 2);
+
+      const revision1 = Mock.of<Revision>({
+        id: 1,
+        createdAt: date,
+        note: Promise.resolve(note),
+      });
+      revisions = [revision1];
+      oldRevisions = [];
+
+      jest.spyOn(noteRepo, 'find').mockResolvedValueOnce(notes);
+      jest.spyOn(revisionRepo, 'find').mockResolvedValueOnce(revisions);
+      const spyOnRemove = jest.spyOn(revisionRepo, 'remove');
+
+      await service.removeOldRevisions();
+      expect(spyOnRemove).toHaveBeenCalledTimes(0);
+    });
+
+    it('do nothing when retention days config is zero', async () => {
+      noteConfig.revisionRetentionDays = 0;
+      const spyOnRemove = jest.spyOn(revisionRepo, 'remove');
+
+      await service.removeOldRevisions();
+      expect(spyOnRemove).toHaveBeenCalledTimes(0);
+    });
+  });
 });
diff --git a/backend/src/revisions/revisions.service.ts b/backend/src/revisions/revisions.service.ts
index cb2406b66..fc0cac81d 100644
--- a/backend/src/revisions/revisions.service.ts
+++ b/backend/src/revisions/revisions.service.ts
@@ -3,11 +3,13 @@
  *
  * SPDX-License-Identifier: AGPL-3.0-only
  */
-import { Injectable } from '@nestjs/common';
+import { Inject, Injectable } from '@nestjs/common';
+import { Cron, Timeout } from '@nestjs/schedule';
 import { InjectRepository } from '@nestjs/typeorm';
 import { createPatch } from 'diff';
 import { Repository } from 'typeorm';
 
+import noteConfiguration, { NoteConfig } from '../config/note.config';
 import { NotInDBError } from '../errors/errors';
 import { ConsoleLoggerService } from '../logger/console-logger.service';
 import { Note } from '../notes/note.entity';
@@ -29,6 +31,9 @@ export class RevisionsService {
     private readonly logger: ConsoleLoggerService,
     @InjectRepository(Revision)
     private revisionRepository: Repository<Revision>,
+    @InjectRepository(Note)
+    private noteRepository: Repository<Note>,
+    @Inject(noteConfiguration.KEY) private noteConfig: NoteConfig,
     private editService: EditService,
   ) {
     this.logger.setContext(RevisionsService.name);
@@ -230,4 +235,80 @@ export class RevisionsService {
       await this.revisionRepository.save(revision);
     }
   }
+
+  // Delete all old revisions everyday on 0:00 AM
+  @Cron('0 0 * * *')
+  async handleRevisionCleanup(): Promise<void> {
+    return await this.removeOldRevisions();
+  }
+
+  // Delete all old revisions 5 sec after startup
+  @Timeout(5000)
+  async handleRevisionCleanupTimeout(): Promise<void> {
+    return await this.removeOldRevisions();
+  }
+
+  /**
+   * Delete old {@link Revision}s except the latest one.
+   *
+   * @async
+   */
+  async removeOldRevisions(): Promise<void> {
+    const currentTime = new Date().getTime();
+    const revisionRetentionDays: number = this.noteConfig.revisionRetentionDays;
+    if (revisionRetentionDays <= 0) {
+      return;
+    }
+    const revisionRetentionSeconds =
+      revisionRetentionDays * 24 * 60 * 60 * 1000;
+
+    const notes: Note[] = await this.noteRepository.find();
+    for (const note of notes) {
+      const revisions: Revision[] = await this.revisionRepository.find({
+        where: {
+          note: { id: note.id },
+        },
+        order: {
+          createdAt: 'ASC',
+        },
+      });
+
+      const oldRevisions = revisions
+        .slice(0, -1) // always keep the latest revision
+        .filter(
+          (revision) =>
+            new Date(revision.createdAt).getTime() <=
+            currentTime - revisionRetentionSeconds,
+        );
+      const remainedRevisions = revisions.filter(
+        (val) => !oldRevisions.includes(val),
+      );
+
+      if (!oldRevisions.length) {
+        continue;
+      } else if (oldRevisions.length === revisions.length - 1) {
+        const beUpdatedRevision = revisions.slice(-1)[0];
+        beUpdatedRevision.patch = createPatch(
+          note.publicId,
+          '', // there is no older revision
+          beUpdatedRevision.content,
+        );
+        await this.revisionRepository.save(beUpdatedRevision);
+      } else {
+        const beUpdatedRevision = remainedRevisions.slice(0)[0];
+        beUpdatedRevision.patch = createPatch(
+          note.publicId,
+          oldRevisions.slice(-1)[0].content,
+          beUpdatedRevision.content,
+        );
+        await this.revisionRepository.save(beUpdatedRevision);
+      }
+
+      await this.revisionRepository.remove(oldRevisions);
+      this.logger.log(
+        `${oldRevisions.length} old revisions of the note '${note.id}' were removed from the DB`,
+        'removeOldRevisions',
+      );
+    }
+  }
 }
diff --git a/docs/content/references/config/notes.md b/docs/content/references/config/notes.md
index 63d0cd043..e3f4b5579 100644
--- a/docs/content/references/config/notes.md
+++ b/docs/content/references/config/notes.md
@@ -8,3 +8,4 @@
 | `HD_PERMISSION_DEFAULT_LOGGED_IN` | `write` | `none`, `read`, `write`           | The default permission for the "logged-in" group that is set on new notes.                                                                                                           |
 | `HD_PERMISSION_DEFAULT_EVERYONE`  | `read`  | `none`, `read`, `write`           | The default permission for the "everyone" group (logged-in & guest users), that is set on new notes created by logged-in users. Notes created by guests always set this to "write".  |
 | `HD_PERSIST_INTERVAL`             | 10      | `0`, `5`, `10`, `20`              | The time interval in **minutes** for the periodic note revision creation during realtime editing. `0` deactivates the periodic note revision creation.                               |
+| `HD_REVISION_RETENTION_DAYS`      | 0       |                                   | The number of days a revision should be kept. If the config option is not set or set to 0, all revisions will be kept forever.                                                       |