diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 99ed48344cd7bdea19fb27982552b07431eb12cc..234cdb8de9e7b9b8519a217c1a3f19118f152856 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -9,6 +9,11 @@ and this project adheres to `Semantic Versioning`_.
 Unreleased
 ----------
 
+Added
+~~~~~
+
+* Periodic tasks can now have a default schedule, which is automatically created
+
 Fixed
 ~~~~~
 
diff --git a/aleksis/core/apps.py b/aleksis/core/apps.py
index ab264c53a91edac303e8cb574e7d7a56fdca00ac..77e4b2a6327d9b88254b160375b1ce85be73ed68 100644
--- a/aleksis/core/apps.py
+++ b/aleksis/core/apps.py
@@ -17,7 +17,12 @@ from .registries import (
     site_preferences_registry,
 )
 from .util.apps import AppConfig
-from .util.core_helpers import get_or_create_favicon, get_site_preferences, has_person
+from .util.core_helpers import (
+    create_default_celery_schedule,
+    get_or_create_favicon,
+    get_site_preferences,
+    has_person,
+)
 from .util.sass_helpers import clean_scss
 
 
@@ -140,6 +145,9 @@ class CoreConfig(AppConfig):
         for name, default in settings.DEFAULT_FAVICON_PATHS.items():
             get_or_create_favicon(name, default, is_favicon=name == "favicon")
 
+        # Create default periodic tasks
+        create_default_celery_schedule()
+
     def user_logged_in(
         self, sender: type, request: Optional[HttpRequest], user: "User", **kwargs
     ) -> None:
diff --git a/aleksis/core/tasks.py b/aleksis/core/tasks.py
index 731c4356c20bba915bd5ad0811307c3877810631..13eb76444b77e84ca7509958f02517c98b678296 100644
--- a/aleksis/core/tasks.py
+++ b/aleksis/core/tasks.py
@@ -1,3 +1,5 @@
+from datetime import timedelta
+
 from django.conf import settings
 from django.core import management
 
@@ -15,7 +17,7 @@ def send_notification(notification: int, resend: bool = False) -> None:
     _send_notification(notification, resend)
 
 
-@app.task
+@app.task(run_every=timedelta(days=1))
 def backup_data() -> None:
     """Backup database and media using django-dbbackup."""
     # Assemble command-line options for dbbackup management command
diff --git a/aleksis/core/util/core_helpers.py b/aleksis/core/util/core_helpers.py
index 3b3c934a85341e75092b6ef1e8df77bdc838687e..148d33594d2cc0a2619a1c1a64c3b8fa69dad79c 100644
--- a/aleksis/core/util/core_helpers.py
+++ b/aleksis/core/util/core_helpers.py
@@ -332,3 +332,60 @@ def process_custom_context_processors(context_processors: list) -> Dict[str, Any
     for processor in processors:
         context.update(processor(None))
     return context
+
+
+def create_default_celery_schedule():
+    """Create default periodic tasks in database for tasks that have a schedule defined."""
+    from celery import current_app
+    from celery.schedules import BaseSchedule, crontab, schedule, solar
+    from django_celery_beat.clockedschedule import clocked
+    from django_celery_beat.models import (
+        ClockedSchedule,
+        CrontabSchedule,
+        IntervalSchedule,
+        PeriodicTask,
+        SolarSchedule,
+    )
+
+    defined_periodic_tasks = PeriodicTask.objects.values_list("task", flat=True).all()
+
+    for name, task in current_app.tasks.items():
+        if name in defined_periodic_tasks:
+            # Task is already known in database, skip
+            continue
+
+        run_every = getattr(task, "run_every", None)
+        if not run_every:
+            # Task has no default schedule, skip
+            continue
+
+        if isinstance(run_every, (float, int, timedelta)):
+            # Schedule is defined as a raw seconds value or timedelta, convert to schedule class
+            run_every = schedule(run_every)
+        elif not isinstance(run_every, BaseSchedule):
+            raise ValueError(f"Task {name} has an invalid schedule defined.")
+
+        # Find matching django-celery-beat schedule model
+        if isinstance(run_every, clocked):
+            Schedule = ClockedSchedule
+            attr = "clocked"
+        elif isinstance(run_every, crontab):
+            Schedule = CrontabSchedule
+            attr = "crontab"
+        elif isinstance(run_every, schedule):
+            Schedule = IntervalSchedule
+            attr = "interval"
+        elif isinstance(run_every, solar):
+            Schedule = SolarSchedule
+            attr = "solar"
+        else:
+            raise ValueError(f"Task {name} has an unknown schedule class defined.")
+
+        # Get or create schedule in database
+        db_schedule = Schedule.from_schedule(run_every)
+        db_schedule.save()
+
+        # Create periodic task
+        PeriodicTask.objects.create(
+            name=f"{name} (default schedule)", task=name, **{attr: db_schedule}
+        )