diff --git a/internal/models/consent_cleanup.go b/internal/models/consent_cleanup.go
new file mode 100644
index 0000000000000000000000000000000000000000..c7fe21a721cfd6c4c4aa7ae66cfa565603d43653
--- /dev/null
+++ b/internal/models/consent_cleanup.go
@@ -0,0 +1,35 @@
+package models
+
+import (
+	"log"
+	"time"
+)
+
+// deleteOutdatedConsents hard deletes outdated consents where end_date is more than 5 years old
+func deleteOutdatedConsents[T GrdfConsent | SgeConsent](dh *DataHandler, model *T, consentType string) {
+	log.Printf("Running %v outdated consents cleanup", consentType)
+	cutoffDate := time.Now().AddDate(-5, 0, 0)
+
+	result := dh.sqlClient.Unscoped().
+		Where("end_date < ?", cutoffDate).
+		Delete(model)
+
+	log.Printf("nb of rows %v", result.RowsAffected)
+
+	if result.Error != nil {
+		log.Printf("Error deleting outdated %s consents: %v\n", consentType, result.Error)
+		return
+	}
+
+	if result.RowsAffected > 0 {
+		log.Printf("Successfully deleted %d outdated %s consent(s) created before %v\n",
+			result.RowsAffected,
+			consentType,
+			cutoffDate.Format("2006-01-02"))
+	}
+}
+
+func DeleteOutdatedConsents(dh *DataHandler) {
+	deleteOutdatedConsents(dh, &GrdfConsent{}, "GRDF")
+	deleteOutdatedConsents(dh, &SgeConsent{}, "SGE")
+}
diff --git a/main.go b/main.go
index 587f06079e0d680ff4b0a02308e98d9a1b4c89f4..2ce10eea288fe1aee16b90bb229cbb4135982849 100644
--- a/main.go
+++ b/main.go
@@ -59,6 +59,21 @@ func main() {
 		}
 	}()
 
+	// Deletes outdated consents every 24h
+	dh := models.NewDataHandler()
+	dailyTicker := time.NewTicker(time.Hour * 24)
+	go func() {
+		for {
+			select {
+			case <-dailyTicker.C:
+				models.DeleteOutdatedConsents(dh)
+			case <-quit:
+				dailyTicker.Stop()
+				return
+			}
+		}
+	}()
+
 	// Serve locally with https
 	log.Fatal(http.ListenAndServeTLS(":"+strconv.Itoa(httpsPort), "./dev_certificates/localhost.crt", "./dev_certificates/localhost.key", rootMux.Router))
 	// log.Fatal(http.ListenAndServe(":"+strconv.Itoa(httpsPort), rootMux.Router))