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/locale/de_DE/LC_MESSAGES/django.po b/aleksis/apps/matrix/locale/de_DE/LC_MESSAGES/django.po
index aff79b37efd858e32496ff7b240fd31c58e69f8b..b5e7a2ead3d9eb1ed21dc0fab0c0eaa3693d9653 100644
--- a/aleksis/apps/matrix/locale/de_DE/LC_MESSAGES/django.po
+++ b/aleksis/apps/matrix/locale/de_DE/LC_MESSAGES/django.po
@@ -3,173 +3,185 @@
 # This file is distributed under the same license as the PACKAGE package.
 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
 #
-#, fuzzy
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-06-25 11:24+0200\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: LANGUAGE <LL@li.org>\n"
-"Language: \n"
+"POT-Creation-Date: 2022-06-04 11:13+0000\n"
+"PO-Revision-Date: 2022-06-23 09:03+0000\n"
+"Last-Translator: Jonathan Weth <teckids@jonathanweth.de>\n"
+"Language-Team: German <https://translate.edugit.org/projects/aleksis/"
+"aleksis-app-matrix/de/>\n"
+"Language: de_DE\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 4.12.1\n"
 
 #: aleksis/apps/matrix/forms.py:12
 msgid "Provision in Matrix"
-msgstr ""
+msgstr "In Matrix bereitstellen"
 
 #: aleksis/apps/matrix/menus.py:6 aleksis/apps/matrix/preferences.py:8
 msgid "Matrix"
-msgstr ""
+msgstr "Matrix"
 
 #: aleksis/apps/matrix/menus.py:18
 msgid "Groups and Rooms"
-msgstr ""
+msgstr "Gruppen und Räume"
 
 #: aleksis/apps/matrix/model_extensions.py:43
 msgid "Can view matrix room of a group"
-msgstr ""
+msgstr "Kann Matrix-Raum einer Gruppe sehen"
 
 #: aleksis/apps/matrix/models.py:19
 msgid "Matrix ID"
-msgstr ""
+msgstr "Matrix-ID"
 
 #: aleksis/apps/matrix/models.py:23
 msgid "Person"
-msgstr ""
+msgstr "Person"
 
 #: aleksis/apps/matrix/models.py:52
 msgid "Matrix profile"
-msgstr ""
+msgstr "Matrix-Profil"
 
 #: aleksis/apps/matrix/models.py:53
 msgid "Matrix profiles"
-msgstr ""
+msgstr "Matrix-Profile"
 
 #: aleksis/apps/matrix/models.py:59
 msgid "Room ID"
-msgstr ""
+msgstr "Raum-ID"
 
 #: aleksis/apps/matrix/models.py:60
 msgid "Alias"
-msgstr ""
+msgstr "Alias"
 
 #: aleksis/apps/matrix/models.py:64
 msgid "Group"
-msgstr ""
+msgstr "Gruppe"
 
 #: aleksis/apps/matrix/models.py:266
 msgid "Matrix room"
-msgstr ""
+msgstr "Matrix-Raum"
 
 #: aleksis/apps/matrix/models.py:267
 msgid "Matrix rooms"
-msgstr ""
+msgstr "Matrix-Räume"
 
 #: aleksis/apps/matrix/models.py:273
 msgid "Child rooms/spaces"
-msgstr ""
+msgstr "Kind-Räume/-Spaces"
 
 #: aleksis/apps/matrix/models.py:355
 msgid "Matrix space"
-msgstr ""
+msgstr "Matrix-Space"
 
 #: aleksis/apps/matrix/models.py:356
 msgid "Matrix spaces"
-msgstr ""
+msgstr "Matrix-Spaces"
 
 #: aleksis/apps/matrix/preferences.py:15
 msgid "URL of Matrix homeserver"
-msgstr ""
+msgstr "URL des Matrix-Homeservers"
 
 #: aleksis/apps/matrix/preferences.py:18
 msgid "URL of the Matrix homeserver on which groups and spaces should be created (e. g. https://matrix.org)"
 msgstr ""
+"URL des Matrix-Homeservers, auf dem Gruppen und Spaces erstellt werden "
+"sollen (z. B. https://matrix.org)"
 
 #: aleksis/apps/matrix/preferences.py:27
 msgid "Name of Matrix homeserver used for auto-generating Matrix IDs"
 msgstr ""
+"Name des Matrix-Homeservers, der für das automatische Generieren von Matrix-"
+"IDs genutzt werden soll"
 
 #: aleksis/apps/matrix/preferences.py:28
 msgid "Leave empty to not create Matrix IDs automatically"
-msgstr ""
+msgstr "Freilassen, um Matrix-IDs nicht automatisch zu erstellen"
 
 #: aleksis/apps/matrix/preferences.py:36
 msgid "Access token to access homeserver"
-msgstr ""
+msgstr "Access-Token, um auf den Homeserver zuzugreifen"
 
 #: aleksis/apps/matrix/preferences.py:39
 msgid "This has to be the access token of a suitable bot user. It is used for all actions."
 msgstr ""
+"Dies muss das Access-Token für einen passenden Bot-Benutzer sein. Es wird "
+"für alle Aktionen verwendet."
 
 #: aleksis/apps/matrix/preferences.py:47
 msgid "Disambiguate room aliases"
-msgstr ""
+msgstr "Raum-Aliase eindeutig machen"
 
 #: aleksis/apps/matrix/preferences.py:49
 msgid "Suffix room aliases with ascending numbers to avoid name clashes"
 msgstr ""
+"Raum-Aliase mit aufsteigenden Nummern ergänzen, um Namenskonflikte zu "
+"vermeiden"
 
 #: aleksis/apps/matrix/preferences.py:56
 msgid "Use Matrix spaces"
-msgstr ""
+msgstr "Matrix-Spaces nutzen"
 
 #: aleksis/apps/matrix/preferences.py:58
 msgid "This activates the creation and management of Matrix spaces."
-msgstr ""
+msgstr "Dies aktiviert die Erstellung und Verwaltung von Matrix-Spaces."
 
 #: aleksis/apps/matrix/preferences.py:65
 msgid "Reduce existing power levels"
-msgstr ""
+msgstr "Existierende Power-Levels reduzieren"
 
 #: aleksis/apps/matrix/preferences.py:67
 msgid "Reduce power levels of existing members to the level suggested by AlekSIS."
 msgstr ""
+"Power-Levels von existierenden Mitgliedern auf das von AlekSIS "
+"vorgeschlagene Level reduzieren."
 
 #: aleksis/apps/matrix/preferences.py:74
 msgid "Power level for owners"
-msgstr ""
+msgstr "Power-Level für Besitzer"
 
 #: aleksis/apps/matrix/preferences.py:77
 msgid "This power level will be set for all owners of a group."
-msgstr ""
+msgstr "Dieses Power-Level wird für alle Gruppenbesitzer gesetzt."
 
 #: aleksis/apps/matrix/preferences.py:84
 msgid "Power level for members"
-msgstr ""
+msgstr "Power-Level für Mitglieder"
 
 #: aleksis/apps/matrix/preferences.py:87
 msgid "This power level will be set for all members of a group."
-msgstr ""
+msgstr "Dieses Power-Level wird für alle Gruppenmitglieder gesetzt."
 
 #: aleksis/apps/matrix/templates/matrix/room/list.html:8
 #: aleksis/apps/matrix/templates/matrix/room/list.html:9
 msgid "Groups and Matrix Rooms"
-msgstr ""
+msgstr "Gruppen und Matrix-Räume"
 
 #: aleksis/apps/matrix/templates/matrix/room/list.html:14
 msgid "Create group"
-msgstr ""
+msgstr "Gruppe erstellen"
 
 #: aleksis/apps/matrix/templates/matrix/room/list.html:21
 msgid "Filter groups"
-msgstr ""
+msgstr "Gruppen filtern"
 
 #: aleksis/apps/matrix/templates/matrix/room/list.html:25
 msgid "Filter"
-msgstr ""
+msgstr "Filtern"
 
 #: aleksis/apps/matrix/templates/matrix/room/list.html:28
 msgid "Clear"
-msgstr ""
+msgstr "Zurücksetzen"
 
 #: aleksis/apps/matrix/templates/matrix/room/list.html:43
 msgid "Selected groups"
-msgstr ""
+msgstr "Ausgewählte Gruppen"
 
 #: aleksis/apps/matrix/templates/matrix/room/list.html:53
 msgid "Execute"
-msgstr ""
+msgstr "Ausführen"
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"