diff --git a/api/config/settings/common.py b/api/config/settings/common.py
index a85a46f97c468900b489ce922aaecaba6c4b83b9..be2fa136cf52994710b82a5c2f30973fa76b5255 100644
--- a/api/config/settings/common.py
+++ b/api/config/settings/common.py
@@ -486,8 +486,15 @@ CELERY_BEAT_SCHEDULE = {
         "schedule": crontab(minute="0", hour="0"),
         "options": {"expires": 60 * 60 * 24},
     },
+    "federation.refresh_nodeinfo_known_nodes": {
+        "task": "federation.refresh_nodeinfo_known_nodes",
+        "schedule": crontab(minute="0", hour="*"),
+        "options": {"expires": 60 * 60},
+    },
 }
 
+NODEINFO_REFRESH_DELAY = env.int("NODEINFO_REFRESH_DELAY", default=3600 * 24)
+
 JWT_AUTH = {
     "JWT_ALLOW_REFRESH": True,
     "JWT_EXPIRATION_DELTA": datetime.timedelta(days=7),
diff --git a/api/funkwhale_api/federation/models.py b/api/funkwhale_api/federation/models.py
index caf8c7db670b4cfbe8c64a03364034145635a434..f465ea3ac086166ec6bfae0f06bf6c9d2fc5ef59 100644
--- a/api/funkwhale_api/federation/models.py
+++ b/api/funkwhale_api/federation/models.py
@@ -151,6 +151,10 @@ class Domain(models.Model):
         )
         return data
 
+    @property
+    def is_local(self):
+        return self.name == settings.FEDERATION_HOSTNAME
+
 
 class Actor(models.Model):
     ap_type = "Actor"
diff --git a/api/funkwhale_api/federation/serializers.py b/api/funkwhale_api/federation/serializers.py
index 666fde092fe31860449010b70ea63d770c600880..b32c09bdba9c4bdacc6b56d2f5739c2da4241fec 100644
--- a/api/funkwhale_api/federation/serializers.py
+++ b/api/funkwhale_api/federation/serializers.py
@@ -11,7 +11,7 @@ from funkwhale_api.music import licenses
 from funkwhale_api.music import models as music_models
 from funkwhale_api.music import tasks as music_tasks
 
-from . import activity, actors, contexts, jsonld, models, utils
+from . import activity, actors, contexts, jsonld, models, tasks, utils
 
 AP_CONTEXT = jsonld.get_default_context()
 
@@ -152,7 +152,12 @@ class ActorSerializer(jsonld.JsonLdSerializer):
         if maf is not None:
             kwargs["manually_approves_followers"] = maf
         domain = urllib.parse.urlparse(kwargs["fid"]).netloc
-        kwargs["domain"] = models.Domain.objects.get_or_create(pk=domain)[0]
+        domain, domain_created = models.Domain.objects.get_or_create(pk=domain)
+        if domain_created and not domain.is_local:
+            # first time we see the domain, we trigger nodeinfo fetching
+            tasks.update_domain_nodeinfo(domain_name=domain.name)
+
+        kwargs["domain"] = domain
         for endpoint, url in self.validated_data.get("endpoints", {}).items():
             if endpoint == "sharedInbox":
                 kwargs["shared_inbox_url"] = url
diff --git a/api/funkwhale_api/federation/tasks.py b/api/funkwhale_api/federation/tasks.py
index 8aebcb27a8f5840821ae0aa6dfed6cbc043deffa..38e8eb6776a218d99cc109065913bd76f4bdba82 100644
--- a/api/funkwhale_api/federation/tasks.py
+++ b/api/funkwhale_api/federation/tasks.py
@@ -212,6 +212,22 @@ def update_domain_nodeinfo(domain):
     domain.save(update_fields=["nodeinfo", "nodeinfo_fetch_date", "service_actor"])
 
 
+@celery.app.task(name="federation.refresh_nodeinfo_known_nodes")
+def refresh_nodeinfo_known_nodes():
+    """
+    Trigger a node info refresh on all nodes that weren't refreshed since
+    settings.NODEINFO_REFRESH_DELAY
+    """
+    limit = timezone.now() - datetime.timedelta(seconds=settings.NODEINFO_REFRESH_DELAY)
+    candidates = models.Domain.objects.external().exclude(
+        nodeinfo_fetch_date__gte=limit
+    )
+    names = candidates.values_list("name", flat=True)
+    logger.info("Launching periodic nodeinfo refresh on %s domains", len(names))
+    for domain_name in names:
+        update_domain_nodeinfo.delay(domain_name=domain_name)
+
+
 def delete_qs(qs):
     label = qs.model._meta.label
     result = qs.delete()
diff --git a/api/funkwhale_api/manage/views.py b/api/funkwhale_api/manage/views.py
index c4a624e5c849d2b692ff066a5bcaf6ac3f0b8f74..588e66c589ba2adafd229e894fdf64536960c70f 100644
--- a/api/funkwhale_api/manage/views.py
+++ b/api/funkwhale_api/manage/views.py
@@ -121,6 +121,10 @@ class ManageDomainViewSet(
         "instance_policy",
     ]
 
+    def perform_create(self, serializer):
+        domain = serializer.save()
+        federation_tasks.update_domain_nodeinfo(domain_name=domain.name)
+
     @rest_decorators.action(methods=["get"], detail=True)
     def nodeinfo(self, request, *args, **kwargs):
         domain = self.get_object()
diff --git a/api/tests/federation/test_actors.py b/api/tests/federation/test_actors.py
index 97ecf31ad0690ce128f2616086b8fce146727a1d..6e5cf93225ad7de0bd55ccb11395c4d9f910065f 100644
--- a/api/tests/federation/test_actors.py
+++ b/api/tests/federation/test_actors.py
@@ -14,7 +14,10 @@ def test_actor_fetching(r_mock):
     assert r == payload
 
 
-def test_get_actor(factories, r_mock):
+def test_get_actor(factories, r_mock, mocker):
+    update_domain_nodeinfo = mocker.patch(
+        "funkwhale_api.federation.tasks.update_domain_nodeinfo"
+    )
     actor = factories["federation.Actor"].build()
     payload = serializers.ActorSerializer(actor).data
     r_mock.get(actor.fid, json=payload)
@@ -22,6 +25,7 @@ def test_get_actor(factories, r_mock):
 
     assert new_actor.pk is not None
     assert serializers.ActorSerializer(new_actor).data == payload
+    update_domain_nodeinfo.assert_called_once_with(domain_name=new_actor.domain_id)
 
 
 def test_get_actor_use_existing(factories, preferences, mocker):
diff --git a/api/tests/federation/test_tasks.py b/api/tests/federation/test_tasks.py
index 5c699cd7216dd038cc3ce091cb37504ddd4a6ce3..4428484b985b14c44a867e444d19d4940892f04a 100644
--- a/api/tests/federation/test_tasks.py
+++ b/api/tests/federation/test_tasks.py
@@ -216,6 +216,31 @@ def test_update_domain_nodeinfo_error(factories, r_mock, now):
     }
 
 
+def test_refresh_nodeinfo_known_nodes(settings, factories, mocker, now):
+    settings.NODEINFO_REFRESH_DELAY = 666
+
+    refreshed = [
+        factories["federation.Domain"](nodeinfo_fetch_date=None),
+        factories["federation.Domain"](
+            nodeinfo_fetch_date=now
+            - datetime.timedelta(seconds=settings.NODEINFO_REFRESH_DELAY + 1)
+        ),
+    ]
+    factories["federation.Domain"](
+        nodeinfo_fetch_date=now
+        - datetime.timedelta(seconds=settings.NODEINFO_REFRESH_DELAY - 1)
+    )
+
+    update_domain_nodeinfo = mocker.patch.object(tasks.update_domain_nodeinfo, "delay")
+
+    tasks.refresh_nodeinfo_known_nodes()
+
+    assert update_domain_nodeinfo.call_count == len(refreshed)
+
+    for d in refreshed:
+        update_domain_nodeinfo.assert_any_call(domain_name=d.name)
+
+
 def test_handle_purge_actors(factories, mocker):
     to_purge = factories["federation.Actor"]()
     keeped = [
diff --git a/api/tests/manage/test_views.py b/api/tests/manage/test_views.py
index 10db666256d9c781effb1bc38cae35c96c5d1f2a..673c39cbc9458df3aaca10e1dea391ab3780524a 100644
--- a/api/tests/manage/test_views.py
+++ b/api/tests/manage/test_views.py
@@ -77,12 +77,16 @@ def test_domain_detail(factories, superuser_api_client):
     assert response.data["name"] == d.pk
 
 
-def test_domain_create(superuser_api_client):
+def test_domain_create(superuser_api_client, mocker):
+    update_domain_nodeinfo = mocker.patch(
+        "funkwhale_api.federation.tasks.update_domain_nodeinfo"
+    )
     url = reverse("api:v1:manage:federation:domains-list")
     response = superuser_api_client.post(url, {"name": "test.federation"})
 
     assert response.status_code == 201
     assert federation_models.Domain.objects.filter(pk="test.federation").exists()
+    update_domain_nodeinfo.assert_called_once_with(domain_name="test.federation")
 
 
 def test_domain_nodeinfo(factories, superuser_api_client, mocker):