diff --git a/aleksis/apps/matrix/apps.py b/aleksis/apps/matrix/apps.py
index 6128273ad9f4fe91815b5d708e629c95581d3a00..984ec7237a76e0aa9a0848b266fff48e0a5ee9c9 100644
--- a/aleksis/apps/matrix/apps.py
+++ b/aleksis/apps/matrix/apps.py
@@ -1,3 +1,5 @@
+from django.db.models.signals import m2m_changed, post_save
+
 from aleksis.core.util.apps import AppConfig
 
 
@@ -11,3 +13,16 @@ class DefaultConfig(AppConfig):
     }
     licence = "EUPL-1.2+"
     copyright_info = (([2021, 2022], "Jonathan Weth", "dev@jonathanweth.de"),)
+
+    def ready(self):
+        from aleksis.core.models import Group
+
+        from .models import MatrixProfile, MatrixRoom
+        from .signals import m2m_changed_matrix_signal, post_save_matrix_signal
+
+        post_save.connect(post_save_matrix_signal, sender=Group)
+        post_save.connect(post_save_matrix_signal, sender=MatrixProfile)
+        post_save.connect(post_save_matrix_signal, sender=MatrixRoom)
+
+        m2m_changed.connect(m2m_changed_matrix_signal, sender=Group.members.through)
+        m2m_changed.connect(m2m_changed_matrix_signal, sender=Group.owners.through)
diff --git a/aleksis/apps/matrix/models.py b/aleksis/apps/matrix/models.py
index a2c19c573d16746fa0b9c3e57dddc2642816cc30..7f2a7a331fda8d849817e69db19a3f9ec566cb3f 100644
--- a/aleksis/apps/matrix/models.py
+++ b/aleksis/apps/matrix/models.py
@@ -6,6 +6,8 @@ from django.db.models import Q, QuerySet
 from django.template.defaultfilters import slugify
 from django.utils.translation import gettext_lazy as _
 
+from model_utils import FieldTracker
+
 from aleksis.core.mixins import ExtensibleModel, ExtensiblePolymorphicModel
 from aleksis.core.models import Group, Person
 from aleksis.core.util.core_helpers import get_site_preferences
@@ -26,6 +28,8 @@ class MatrixProfile(ExtensibleModel):
         related_name="matrix_profile",
     )
 
+    change_tracker = FieldTracker()
+
     @classmethod
     def build_matrix_id(cls, username: str, homeserver: Optional[str] = None) -> str:
         """Build a Matrix ID from a username."""
@@ -65,6 +69,8 @@ class MatrixRoom(ExtensiblePolymorphicModel):
         related_name="matrix_rooms",
     )
 
+    change_tracker = FieldTracker(["group_id"])
+
     @classmethod
     def get_queryset(cls):
         """Get a queryset for only Matrix rooms."""
diff --git a/aleksis/apps/matrix/signals.py b/aleksis/apps/matrix/signals.py
new file mode 100644
index 0000000000000000000000000000000000000000..0b67a1fa331fdfc963ab0813a208275dcfad6b2c
--- /dev/null
+++ b/aleksis/apps/matrix/signals.py
@@ -0,0 +1,36 @@
+from django.db.models import Q
+
+from aleksis.apps.matrix.models import MatrixProfile, MatrixRoom
+from aleksis.core.models import Group
+
+from .tasks import sync_room
+
+
+def post_save_matrix_signal(sender, instance, created, **kwargs):
+    """Sync Matrix room after changing a group/Matrix room/Matrix profile."""
+    rooms = []
+    if isinstance(instance, Group):
+        rooms = MatrixRoom.objects.filter(group=instance)
+    elif isinstance(instance, MatrixRoom) and instance.change_tracker.has_changed("group_id"):
+        rooms = [instance]
+    elif isinstance(instance, MatrixProfile) and instance.change_tracker.changed():
+        rooms = MatrixRoom.objects.filter(
+            Q(group__members=instance.person) | Q(group__owners=instance.person)
+        ).distinct()
+
+    for room in rooms:
+        sync_room.delay(room.pk)
+
+
+def m2m_changed_matrix_signal(sender, instance, action, reverse, model, pk_set, **kwargs):
+    """Sync Matrix room after changing group member- and ownerships."""
+    if action not in ("post_add", "post_remove", "post_clear"):
+        return
+
+    if isinstance(instance, Group):
+        groups = [instance]
+    else:
+        groups = Group.objects.filter(Q(members=instance) | Q(owners=instance)).distinct()
+
+    for room in MatrixRoom.objects.filter(group__in=groups):
+        sync_room.delay(room.pk)
diff --git a/aleksis/apps/matrix/tasks.py b/aleksis/apps/matrix/tasks.py
index c7346d8c5eb632fd7255e678cd5ca6aeb4c18075..34ccd15eb6cab43b7d43d8959f7abb577b425149 100644
--- a/aleksis/apps/matrix/tasks.py
+++ b/aleksis/apps/matrix/tasks.py
@@ -1,3 +1,4 @@
+from datetime import timedelta
 from typing import Sequence
 
 from aleksis.apps.matrix.models import MatrixRoom
@@ -25,3 +26,11 @@ def provision_group_in_matrix(pk: int):
     """Provision provided group in Matrix."""
     group = Group.objects.get(pk=pk)
     group._provision_in_matrix()
+
+
+@app.task(run_every=timedelta(days=1))
+def sync_rooms():
+    """Synchronise all Matrix rooms."""
+    rooms = MatrixRoom.objects.all()
+    for room in rooms:
+        sync_room.delay(room.pk)
diff --git a/aleksis/apps/matrix/tests/synapse/homeserver.yaml b/aleksis/apps/matrix/tests/synapse/homeserver.yaml
index 05da9c0f914e699c1cafdcae0a47f14210443d71..9221c000737b21d86de9e3ea7302e82e53baf7a9 100644
--- a/aleksis/apps/matrix/tests/synapse/homeserver.yaml
+++ b/aleksis/apps/matrix/tests/synapse/homeserver.yaml
@@ -30,4 +30,6 @@ form_secret: "eYJgrzzEXHsgblxAi3pBmPsNrXrga.OVTKkmb&u64A11V_8axr"
 signing_key_path: "%path%/synapse/matrix.aleksis.example.org.signing.key"
 
 trusted_key_servers:
-  - server_name: "matrix.org"
\ No newline at end of file
+  - server_name: "matrix.org"
+
+enable_registration_without_verification: true
diff --git a/aleksis/apps/matrix/tests/test_matrix.py b/aleksis/apps/matrix/tests/test_matrix.py
index 14fc9f78177f5842600af0884d24b89686fa9387..b1cc47de7e3c77681cce5e219d801818628be2e6 100644
--- a/aleksis/apps/matrix/tests/test_matrix.py
+++ b/aleksis/apps/matrix/tests/test_matrix.py
@@ -1,7 +1,9 @@
 import time
 from datetime import date
+from unittest.mock import call
 
 from django.contrib.auth.models import User
+from django.db.models import Q
 
 import pytest
 import requests
@@ -319,32 +321,6 @@ def test_use_room_sync(matrix_bot_user):
 from django.test import TransactionTestCase, override_settings
 
 
-@pytest.mark.usefixtures("celery_worker", "matrix_bot_user")
-@override_settings(CELERY_BROKER_URL="memory://localhost//")
-@override_settings(HAYSTACK_SIGNAL_PROCESSOR="")
-class MatrixCeleryTest(TransactionTestCase):
-    serialized_rollback = True
-
-    def test_use_room_async(self):
-        get_site_preferences()["matrix__homeserver_ids"] = "matrix.aleksis.example.org"
-
-        g = Group.objects.create(name="Test Room")
-        u1 = User.objects.create_user("test1", "test1@example.org", "test1")
-
-        p1 = Person.objects.create(first_name="Test", last_name="Person", user=u1)
-
-        g.members.add(p1)
-
-        r = g.provision_in_matrix(sync=False)
-        assert isinstance(r, AsyncResult)
-
-        time.sleep(3)
-
-        assert MatrixProfile.objects.all().count() == 1
-        assert p1.matrix_profile
-        assert p1.matrix_profile.matrix_id == "@test1:matrix.aleksis.example.org"
-
-
 def test_space_creation(matrix_bot_user):
     parent_group = Group.objects.create(name="Test Group")
     child_1 = Group.objects.create(name="Test Group 1")
@@ -497,3 +473,202 @@ def test_too_much_invites(matrix_bot_user):
     room = MatrixRoom.from_group(g)
 
     room.sync_profiles()
+
+
+def test_signal_group_changed(matrix_bot_user, mocker):
+    g = Group.objects.create(name="Test Room")
+    room = MatrixRoom.from_group(g)
+
+    sync_mock = mocker.patch("aleksis.apps.matrix.tasks.sync_room.delay")
+
+    g.name = "Test Room 2"
+    g.save()
+
+    sync_mock.assert_called_once_with(room.pk)
+
+
+def test_signal_room_group_changed(matrix_bot_user, mocker):
+    g = Group.objects.create(name="Test Room")
+    g2 = Group.objects.create(name="Test Room 2")
+    room = MatrixRoom.from_group(g)
+
+    sync_mock = mocker.patch("aleksis.apps.matrix.tasks.sync_room.delay")
+
+    room.group = g2
+    room.save()
+
+    sync_mock.assert_called_once_with(room.pk)
+
+
+def test_signal_profile_person_changed(matrix_bot_user, mocker):
+    get_site_preferences()["matrix__homeserver_ids"] = "matrix.aleksis.example.org"
+
+    p = Person.objects.create(
+        first_name="Test", last_name="Person", user=User.objects.create(username="test")
+    )
+    p2 = Person.objects.create(
+        first_name="Test 2", last_name="Person 2", user=User.objects.create(username="test2")
+    )
+    p3 = Person.objects.create(
+        first_name="Test 3", last_name="Person 3", user=User.objects.create(username="test3")
+    )
+    p4 = Person.objects.create(
+        first_name="Test 4", last_name="Person 4", user=User.objects.create(username="test4")
+    )
+
+    g = Group.objects.create(name="Test Room")
+    g.members.set([p])
+    g.owners.set([p3])
+
+    g2 = Group.objects.create(name="Test Room 2")
+    g2.members.set([p2])
+    g2.owners.set([p4])
+
+    room = MatrixRoom.from_group(g)
+    room2 = MatrixRoom.from_group(g2)
+
+    sync_mock = mocker.patch("aleksis.apps.matrix.tasks.sync_room.delay")
+
+    profile = MatrixProfile.from_person(p)
+    p2.matrix_profile.delete()
+    profile.person = p2
+    profile.save()
+
+    sync_mock.assert_called_with(room2.pk)
+
+    profile2 = MatrixProfile.from_person(p3)
+    p4.matrix_profile.delete()
+    profile2.person = p4
+    profile2.save()
+
+    sync_mock.assert_called_with(room2.pk)
+
+
+def test_signal_room_members_changed(matrix_bot_user, mocker):
+    get_site_preferences()["matrix__homeserver_ids"] = "matrix.aleksis.example.org"
+
+    p = Person.objects.create(
+        first_name="Test", last_name="Person", user=User.objects.create(username="test")
+    )
+    p2 = Person.objects.create(
+        first_name="Test 2", last_name="Person 2", user=User.objects.create(username="test2")
+    )
+    p3 = Person.objects.create(
+        first_name="Test 3", last_name="Person 3", user=User.objects.create(username="test3")
+    )
+    p4 = Person.objects.create(
+        first_name="Test 4", last_name="Person 4", user=User.objects.create(username="test4")
+    )
+
+    g = Group.objects.create(name="Test Room")
+    g.members.set([p])
+
+    room = MatrixRoom.from_group(g)
+
+    sync_mock = mocker.patch("aleksis.apps.matrix.tasks.sync_room.delay")
+
+    g.members.set([p2, p3])
+
+    sync_mock.assert_called_with(room.pk)
+    sync_mock.reset_mock()
+
+    g.members.add(p4)
+
+    sync_mock.assert_called_with(room.pk)
+    sync_mock.reset_mock()
+
+    g.members.remove(p2)
+
+    sync_mock.assert_called_with(room.pk)
+    sync_mock.reset_mock()
+
+    g.members.clear()
+
+    sync_mock.assert_called_with(room.pk)
+    sync_mock.reset_mock()
+
+
+def test_signal_room_owners_changed(matrix_bot_user, mocker):
+    get_site_preferences()["matrix__homeserver_ids"] = "matrix.aleksis.example.org"
+
+    p = Person.objects.create(
+        first_name="Test", last_name="Person", user=User.objects.create(username="test")
+    )
+    p2 = Person.objects.create(
+        first_name="Test 2", last_name="Person 2", user=User.objects.create(username="test2")
+    )
+    p3 = Person.objects.create(
+        first_name="Test 3", last_name="Person 3", user=User.objects.create(username="test3")
+    )
+    p4 = Person.objects.create(
+        first_name="Test 4", last_name="Person 4", user=User.objects.create(username="test4")
+    )
+
+    g = Group.objects.create(name="Test Room")
+    g.owners.set([p])
+
+    room = MatrixRoom.from_group(g)
+
+    sync_mock = mocker.patch("aleksis.apps.matrix.tasks.sync_room.delay")
+
+    g.owners.set([p2, p3])
+
+    sync_mock.assert_called_with(room.pk)
+    sync_mock.reset_mock()
+
+    g.owners.add(p4)
+
+    sync_mock.assert_called_with(room.pk)
+    sync_mock.reset_mock()
+
+    g.owners.remove(p2)
+
+    sync_mock.assert_called_with(room.pk)
+    sync_mock.reset_mock()
+
+    g.owners.clear()
+
+    sync_mock.assert_called_with(room.pk)
+    sync_mock.reset_mock()
+
+
+def test_signal_room_members_changed_reverse(matrix_bot_user, mocker):
+    get_site_preferences()["matrix__homeserver_ids"] = "matrix.aleksis.example.org"
+
+    p = Person.objects.create(
+        first_name="Test", last_name="Person", user=User.objects.create(username="test")
+    )
+
+    g = Group.objects.create(name="Test Room")
+    g2 = Group.objects.create(name="Test Room 2")
+
+    room = MatrixRoom.from_group(g)
+    room2 = MatrixRoom.from_group(g2)
+
+    sync_mock = mocker.patch("aleksis.apps.matrix.tasks.sync_room.delay")
+
+    p.member_of.set([g, g2])
+
+    sync_mock.assert_has_calls([call(room.pk), call(room2.pk)])
+    sync_mock.reset_mock()
+
+
+def test_signal_room_owners_changed_reverse(matrix_bot_user, mocker):
+    get_site_preferences()["matrix__homeserver_ids"] = "matrix.aleksis.example.org"
+
+    p = Person.objects.create(
+        first_name="Test", last_name="Person", user=User.objects.create(username="test")
+    )
+
+    g = Group.objects.create(name="Test Room")
+    g2 = Group.objects.create(name="Test Room 2")
+
+    room = MatrixRoom.from_group(g)
+    room2 = MatrixRoom.from_group(g2)
+
+    sync_mock = mocker.patch("aleksis.apps.matrix.tasks.sync_room.delay")
+
+    p.owner_of.set([g, g2])
+
+    sync_mock.assert_has_calls([call(room.pk), call(room2.pk)])
+    sync_mock.reset_mock()
diff --git a/pyproject.toml b/pyproject.toml
index ee8788aea2c26328bea39c38990112df25769885..4daf6e42edf922cd0aab8441971305e4bf735678 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -38,6 +38,7 @@ aleksis-core = "^2.7"
 aleksis-builddeps = "*"
 matrix-synapse = "^1.49.2"
 pytest-xprocess = "^0.19.0"
+pytest-mock = "^3.7.0"
 
 [tool.poetry.plugins."aleksis.app"]
 matrix = "aleksis.apps.matrix.apps:DefaultConfig"