diff --git a/api/funkwhale_api/federation/views.py b/api/funkwhale_api/federation/views.py
index 9b51a534df506169be39b66d75becca94bb5d90c..ef581408c2e187bf32f3922c651b903f3583884e 100644
--- a/api/funkwhale_api/federation/views.py
+++ b/api/funkwhale_api/federation/views.py
@@ -85,13 +85,31 @@ class InstanceActorViewSet(FederationMixin, viewsets.GenericViewSet):
         return response.Response({}, status=200)
 
 
-class WellKnownViewSet(FederationMixin, viewsets.GenericViewSet):
+class WellKnownViewSet(viewsets.GenericViewSet):
     authentication_classes = []
     permission_classes = []
     renderer_classes = [renderers.WebfingerRenderer]
 
+    @list_route(methods=['get'])
+    def nodeinfo(self, request, *args, **kwargs):
+        if not preferences.get('instance__nodeinfo_enabled'):
+            return HttpResponse(status=404)
+        data = {
+            'links': [
+                {
+                    'rel': 'http://nodeinfo.diaspora.software/ns/schema/2.0',
+                    'href': utils.full_url(
+                        reverse('api:v1:instance:nodeinfo-2.0')
+                    )
+                }
+            ]
+        }
+        return response.Response(data)
+
     @list_route(methods=['get'])
     def webfinger(self, request, *args, **kwargs):
+        if not preferences.get('federation__enabled'):
+            return HttpResponse(status=405)
         try:
             resource_type, resource = webfinger.clean_resource(
                 request.GET['resource'])
diff --git a/api/funkwhale_api/instance/dynamic_preferences_registry.py b/api/funkwhale_api/instance/dynamic_preferences_registry.py
index 1d11a2988ca1d76da7b61d8ef8657ac6cdee74df..03555b0be57bde18f4fe13ba1b62d852ffd93ae4 100644
--- a/api/funkwhale_api/instance/dynamic_preferences_registry.py
+++ b/api/funkwhale_api/instance/dynamic_preferences_registry.py
@@ -68,3 +68,31 @@ class RavenEnabled(types.BooleanPreference):
         'Wether error reporting to a Sentry instance using raven is enabled'
         ' for front-end errors'
     )
+
+
+@global_preferences_registry.register
+class InstanceNodeinfoEnabled(types.BooleanPreference):
+    show_in_api = False
+    section = instance
+    name = 'nodeinfo_enabled'
+    default = True
+    verbose_name = 'Enable nodeinfo endpoint'
+    help_text = (
+        'This endpoint is needed for your about page to work.'
+        'It\'s also helpful for the various monitoring '
+        'tools that map and analyzize the fediverse, '
+        'but you can disable it completely if needed.'
+    )
+
+
+@global_preferences_registry.register
+class InstanceNodeinfoStatsEnabled(types.BooleanPreference):
+    show_in_api = False
+    section = instance
+    name = 'nodeinfo_stats_enabled'
+    default = True
+    verbose_name = 'Enable usage and library stats in nodeinfo endpoint'
+    help_text = (
+        'Disable this f you don\'t want to share usage and library statistics'
+        'in the nodeinfo endpoint but don\'t want to disable it completely.'
+    )
diff --git a/api/funkwhale_api/instance/nodeinfo.py b/api/funkwhale_api/instance/nodeinfo.py
new file mode 100644
index 0000000000000000000000000000000000000000..e267f197d17ce7c317128c684f1db8f72a1a47ba
--- /dev/null
+++ b/api/funkwhale_api/instance/nodeinfo.py
@@ -0,0 +1,73 @@
+import memoize.djangocache
+
+import funkwhale_api
+from funkwhale_api.common import preferences
+
+from . import stats
+
+
+store = memoize.djangocache.Cache('default')
+memo = memoize.Memoizer(store, namespace='instance:stats')
+
+
+def get():
+    share_stats = preferences.get('instance__nodeinfo_stats_enabled')
+    data = {
+        'version': '2.0',
+        'software': {
+            'name': 'funkwhale',
+            'version': funkwhale_api.__version__
+        },
+        'protocols': ['activitypub'],
+        'services': {
+            'inbound': [],
+            'outbound': []
+        },
+        'openRegistrations': preferences.get('users__registration_enabled'),
+        'usage': {
+            'users': {
+                'total': 0,
+            }
+        },
+        'metadata': {
+            'shortDescription': preferences.get('instance__short_description'),
+            'longDescription': preferences.get('instance__long_description'),
+            'nodeName': preferences.get('instance__name'),
+            'library': {
+                'federationEnabled': preferences.get('federation__enabled'),
+                'federationNeedsApproval': preferences.get('federation__music_needs_approval'),
+                'anonymousCanListen': preferences.get('common__api_authentication_required'),
+            },
+        }
+    }
+    if share_stats:
+        getter = memo(
+            lambda: stats.get(),
+            max_age=600
+        )
+        statistics = getter()
+        data['usage']['users']['total'] = statistics['users']
+        data['metadata']['library']['tracks'] = {
+            'total': statistics['tracks'],
+        }
+        data['metadata']['library']['artists'] = {
+            'total': statistics['artists'],
+        }
+        data['metadata']['library']['albums'] = {
+            'total': statistics['albums'],
+        }
+        data['metadata']['library']['music'] = {
+            'hours': statistics['music_duration']
+        }
+
+        data['metadata']['usage'] = {
+            'favorites': {
+                'tracks': {
+                    'total': statistics['track_favorites'],
+                }
+            },
+            'listenings': {
+                'total': statistics['listenings']
+            }
+        }
+    return data
diff --git a/api/funkwhale_api/instance/urls.py b/api/funkwhale_api/instance/urls.py
index af23e7e08433b97c6c10f07e1b929892e5dbe32c..f506488fc4db7819da6aae5d5c79fe33e8a9af5c 100644
--- a/api/funkwhale_api/instance/urls.py
+++ b/api/funkwhale_api/instance/urls.py
@@ -1,11 +1,9 @@
 from django.conf.urls import url
-from django.views.decorators.cache import cache_page
 
 from . import views
 
 
 urlpatterns = [
+    url(r'^nodeinfo/2.0/$', views.NodeInfo.as_view(), name='nodeinfo-2.0'),
     url(r'^settings/$', views.InstanceSettings.as_view(), name='settings'),
-    url(r'^stats/$',
-        cache_page(60 * 5)(views.InstanceStats.as_view()), name='stats'),
 ]
diff --git a/api/funkwhale_api/instance/views.py b/api/funkwhale_api/instance/views.py
index 7f8f393c964e24bfc7a5ee29bf6be2e30f337188..5953ca555a3081d5e58a1d60da1d3dec58279e3b 100644
--- a/api/funkwhale_api/instance/views.py
+++ b/api/funkwhale_api/instance/views.py
@@ -4,9 +4,17 @@ from rest_framework.response import Response
 from dynamic_preferences.api import serializers
 from dynamic_preferences.registries import global_preferences_registry
 
+from funkwhale_api.common import preferences
+
+from . import nodeinfo
 from . import stats
 
 
+NODEINFO_2_CONTENT_TYPE = (
+    'application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.0#; charset=utf-8'  # noqa
+)
+
+
 class InstanceSettings(views.APIView):
     permission_classes = []
     authentication_classes = []
@@ -27,10 +35,13 @@ class InstanceSettings(views.APIView):
         return Response(data, status=200)
 
 
-class InstanceStats(views.APIView):
+class NodeInfo(views.APIView):
     permission_classes = []
     authentication_classes = []
 
     def get(self, request, *args, **kwargs):
-        data = stats.get()
-        return Response(data, status=200)
+        if not preferences.get('instance__nodeinfo_enabled'):
+            return Response(status=404)
+        data = nodeinfo.get()
+        return Response(
+            data, status=200, content_type=NODEINFO_2_CONTENT_TYPE)
diff --git a/api/tests/federation/test_views.py b/api/tests/federation/test_views.py
index 09ecfc8ff7f6d192808890b78eb9d3226ad8cc7e..cc81f0657754960d1389bfe36bb6c02787f4c5a3 100644
--- a/api/tests/federation/test_views.py
+++ b/api/tests/federation/test_views.py
@@ -70,6 +70,32 @@ def test_wellknown_webfinger_system(
     assert response.data == serializer.data
 
 
+def test_wellknown_nodeinfo(db, preferences, api_client, settings):
+    expected = {
+        'links': [
+            {
+                'rel': 'http://nodeinfo.diaspora.software/ns/schema/2.0',
+                'href': '{}{}'.format(
+                    settings.FUNKWHALE_URL,
+                    reverse('api:v1:instance:nodeinfo-2.0')
+                )
+            }
+        ]
+    }
+    url = reverse('federation:well-known-nodeinfo')
+    response = api_client.get(url)
+    assert response.status_code == 200
+    assert response['Content-Type'] == 'application/jrd+json'
+    assert response.data == expected
+
+
+def test_wellknown_nodeinfo_disabled(db, preferences, api_client):
+    preferences['instance__nodeinfo_enabled'] = False
+    url = reverse('federation:well-known-nodeinfo')
+    response = api_client.get(url)
+    assert response.status_code == 404
+
+
 def test_audio_file_list_requires_authenticated_actor(
         db, preferences, api_client):
     preferences['federation__music_needs_approval'] = True
diff --git a/api/tests/instance/test_nodeinfo.py b/api/tests/instance/test_nodeinfo.py
new file mode 100644
index 0000000000000000000000000000000000000000..4ca1c43a5835f94a6db20018a76e456c5e1b7e28
--- /dev/null
+++ b/api/tests/instance/test_nodeinfo.py
@@ -0,0 +1,105 @@
+from django.urls import reverse
+
+import funkwhale_api
+
+from funkwhale_api.instance import nodeinfo
+
+
+def test_nodeinfo_dump(preferences, mocker):
+    preferences['instance__nodeinfo_stats_enabled'] = True
+    stats = {
+        'users': 1,
+        'tracks': 2,
+        'albums': 3,
+        'artists': 4,
+        'track_favorites': 5,
+        'music_duration': 6,
+        'listenings': 7,
+    }
+    mocker.patch('funkwhale_api.instance.stats.get', return_value=stats)
+
+    expected = {
+        'version': '2.0',
+        'software': {
+            'name': 'funkwhale',
+            'version': funkwhale_api.__version__
+        },
+        'protocols': ['activitypub'],
+        'services': {
+            'inbound': [],
+            'outbound': []
+        },
+        'openRegistrations': preferences['users__registration_enabled'],
+        'usage': {
+            'users': {
+                'total': stats['users'],
+            }
+        },
+        'metadata': {
+            'shortDescription': preferences['instance__short_description'],
+            'longDescription': preferences['instance__long_description'],
+            'nodeName': preferences['instance__name'],
+            'library': {
+                'federationEnabled': preferences['federation__enabled'],
+                'federationNeedsApproval': preferences['federation__music_needs_approval'],
+                'anonymousCanListen': preferences['common__api_authentication_required'],
+                'tracks': {
+                    'total': stats['tracks'],
+                },
+                'artists': {
+                    'total': stats['artists'],
+                },
+                'albums': {
+                    'total': stats['albums'],
+                },
+                'music': {
+                    'hours': stats['music_duration']
+                },
+            },
+            'usage': {
+                'favorites': {
+                    'tracks': {
+                        'total': stats['track_favorites'],
+                    }
+                },
+                'listenings': {
+                    'total': stats['listenings']
+                }
+            }
+        }
+    }
+    assert nodeinfo.get() == expected
+
+
+def test_nodeinfo_dump_stats_disabled(preferences, mocker):
+    preferences['instance__nodeinfo_stats_enabled'] = False
+
+    expected = {
+        'version': '2.0',
+        'software': {
+            'name': 'funkwhale',
+            'version': funkwhale_api.__version__
+        },
+        'protocols': ['activitypub'],
+        'services': {
+            'inbound': [],
+            'outbound': []
+        },
+        'openRegistrations': preferences['users__registration_enabled'],
+        'usage': {
+            'users': {
+                'total': 0,
+            }
+        },
+        'metadata': {
+            'shortDescription': preferences['instance__short_description'],
+            'longDescription': preferences['instance__long_description'],
+            'nodeName': preferences['instance__name'],
+            'library': {
+                'federationEnabled': preferences['federation__enabled'],
+                'federationNeedsApproval': preferences['federation__music_needs_approval'],
+                'anonymousCanListen': preferences['common__api_authentication_required'],
+            },
+        }
+    }
+    assert nodeinfo.get() == expected
diff --git a/api/tests/instance/test_stats.py b/api/tests/instance/test_stats.py
index 6eaad76f7f9d8292211965a462f473c8bb41745a..6063e9300512819a9cc3e6d6bebcc477af9a59d7 100644
--- a/api/tests/instance/test_stats.py
+++ b/api/tests/instance/test_stats.py
@@ -3,16 +3,6 @@ from django.urls import reverse
 from funkwhale_api.instance import stats
 
 
-def test_can_get_stats_via_api(db, api_client, mocker):
-    stats = {
-        'foo': 'bar'
-    }
-    mocker.patch('funkwhale_api.instance.stats.get', return_value=stats)
-    url = reverse('api:v1:instance:stats')
-    response = api_client.get(url)
-    assert response.data == stats
-
-
 def test_get_users(mocker):
     mocker.patch(
         'funkwhale_api.users.models.User.objects.count', return_value=42)
diff --git a/api/tests/instance/test_views.py b/api/tests/instance/test_views.py
new file mode 100644
index 0000000000000000000000000000000000000000..468c0ddae9de440b3edce7fd65fdc57c6ead8fff
--- /dev/null
+++ b/api/tests/instance/test_views.py
@@ -0,0 +1,23 @@
+from django.urls import reverse
+
+
+def test_nodeinfo_endpoint(db, api_client, mocker):
+    payload = {
+        'test': 'test'
+    }
+    mocked_nodeinfo = mocker.patch(
+        'funkwhale_api.instance.nodeinfo.get', return_value=payload)
+    url = reverse('api:v1:instance:nodeinfo-2.0')
+    response = api_client.get(url)
+    ct = 'application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.0#; charset=utf-8'  # noqa
+    assert response.status_code == 200
+    assert response['Content-Type'] == ct
+    assert response.data == payload
+
+
+def test_nodeinfo_endpoint_disabled(db, api_client, preferences):
+    preferences['instance__nodeinfo_enabled'] = False
+    url = reverse('api:v1:instance:nodeinfo-2.0')
+    response = api_client.get(url)
+
+    assert response.status_code == 404
diff --git a/changes/changelog.d/192.feature b/changes/changelog.d/192.feature
new file mode 100644
index 0000000000000000000000000000000000000000..caa8e60c15121f81ca594fcd9beeab55ab30c1bb
--- /dev/null
+++ b/changes/changelog.d/192.feature
@@ -0,0 +1,76 @@
+Use nodeinfo standard for publishing instance information (#192)
+
+Nodeinfo standard for instance information and stats
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. warning::
+
+    The ``/api/v1/instance/stats/`` endpoint which was used to display
+    instance data in the about page is removed in favor of the new
+    ``/api/v1/instance/nodeinfo/2.0/`` endpoint.
+
+In earlier version, we where using a custom endpoint and format for
+our instance information and statistics. While this was working,
+this was not compatible with anything else on the fediverse.
+
+We now offer a nodeinfo 2.0 endpoint which provides, in a single place,
+all the instance information such as library and user activity statistics,
+public instance settings (description, registration and federation status, etc.).
+
+We offer two settings to manage nodeinfo in your Funkwhale instance:
+
+1. One setting to completely disable nodeinfo, but this is not recommended
+   as the exposed data may be needed to make some parts of the front-end
+   work (especially the about page).
+2. One setting to disable only usage and library statistics in the nodeinfo
+   endpoint. This is useful if you want the nodeinfo endpoint to work,
+   but don't feel comfortable sharing aggregated statistics about your library
+   and user activity.
+ 
+To make your instance fully compatible with the nodeinfo protocol, you need to
+to edit your nginx configuration file:
+
+.. code-block::
+
+    # before
+    ...
+    location /.well-known/webfinger {
+        include /etc/nginx/funkwhale_proxy.conf;
+        proxy_pass   http://funkwhale-api/.well-known/webfinger;
+    }
+    ...
+
+    # after
+    ...
+    location /.well-known/ {
+        include /etc/nginx/funkwhale_proxy.conf;
+        proxy_pass   http://funkwhale-api/.well-known/;
+    }
+    ...
+
+You can do the same if you use apache:
+
+.. code-block::
+
+    # before
+    ...
+    <Location "/.well-known/webfinger">
+      ProxyPass ${funkwhale-api}/.well-known/webfinger
+      ProxyPassReverse ${funkwhale-api}/.well-known/webfinger
+    </Location>
+    ...
+
+    # after
+    ...
+    <Location "/.well-known/">
+      ProxyPass ${funkwhale-api}/.well-known/
+      ProxyPassReverse ${funkwhale-api}/.well-known/
+    </Location>
+    ...
+
+This will ensure all well-known endpoints are proxied to funkwhale, and
+not just webfinger one.
+
+Links:
+
+- About nodeinfo: https://github.com/jhass/nodeinfo
diff --git a/deploy/apache.conf b/deploy/apache.conf
index 8d5a5e1f7ee45c7a02f4f8654309744f841751c7..5bfcbce04587e96fe949f21e6c50b4e05c77360e 100644
--- a/deploy/apache.conf
+++ b/deploy/apache.conf
@@ -84,9 +84,9 @@ Define MUSIC_DIRECTORY_PATH /srv/funkwhale/data/music
       ProxyPassReverse ${funkwhale-api}/federation
    </Location>
 
-   <Location "/.well-known/webfinger">
-      ProxyPass ${funkwhale-api}/.well-known/webfinger
-      ProxyPassReverse ${funkwhale-api}/.well-known/webfinger
+   <Location "/.well-known/">
+      ProxyPass ${funkwhale-api}/.well-known/
+      ProxyPassReverse ${funkwhale-api}/.well-known/
    </Location>
 
    Alias /media /srv/funkwhale/data/media
diff --git a/deploy/nginx.conf b/deploy/nginx.conf
index b3a4c6aaf762830b533774689cb26266ca6b65e8..7d344408b67ad8c46e2d7a161180ba928ebb31aa 100644
--- a/deploy/nginx.conf
+++ b/deploy/nginx.conf
@@ -67,9 +67,9 @@ server {
         proxy_pass   http://funkwhale-api/federation/;
     }
 
-    location /.well-known/webfinger {
+    location /.well-known/ {
         include /etc/nginx/funkwhale_proxy.conf;
-        proxy_pass   http://funkwhale-api/.well-known/webfinger;
+        proxy_pass   http://funkwhale-api/.well-known/;
     }
 
     location /media/ {
diff --git a/front/src/components/instance/Stats.vue b/front/src/components/instance/Stats.vue
index 7da9fc6ede056c4174f5dc6004f5c6233664008b..ac144ceb3a910e50e59ea9f9efd0575d2c037a0a 100644
--- a/front/src/components/instance/Stats.vue
+++ b/front/src/components/instance/Stats.vue
@@ -3,7 +3,7 @@
     <div v-if="stats" class="ui stackable two column grid">
       <div class="column">
         <h3 class="ui left aligned header"><i18next path="User activity"/></h3>
-        <div class="ui mini horizontal statistics">
+        <div v-if="stats" class="ui mini horizontal statistics">
           <div class="statistic">
             <div class="value">
               <i class="green user icon"></i>
@@ -19,7 +19,7 @@
           </div>
           <div class="statistic">
             <div class="value">
-              <i class="pink heart icon"></i> {{ stats.track_favorites }}
+              <i class="pink heart icon"></i> {{ stats.trackFavorites }}
             </div>
             <i18next tag="div" class="label" path="Tracks favorited"/>
           </div>
@@ -30,7 +30,7 @@
         <div class="ui mini horizontal statistics">
           <div class="statistic">
             <div class="value">
-              {{ parseInt(stats.music_duration) }}
+              {{ parseInt(stats.musicDuration) }}
             </div>
             <i18next tag="div" class="label" path="hours of music"/>
           </div>
@@ -59,6 +59,7 @@
 </template>
 
 <script>
+import _ from 'lodash'
 import axios from 'axios'
 import logger from '@/logging'
 
@@ -76,8 +77,16 @@ export default {
       var self = this
       this.isLoading = true
       logger.default.debug('Fetching instance stats...')
-      axios.get('instance/stats/').then((response) => {
-        self.stats = response.data
+      axios.get('instance/nodeinfo/2.0/').then((response) => {
+        let d = response.data
+        self.stats = {}
+        self.stats.users = _.get(d, 'usage.users.total')
+        self.stats.listenings = _.get(d, 'metadata.usage.listenings.total')
+        self.stats.trackFavorites = _.get(d, 'metadata.usage.favorites.tracks.total')
+        self.stats.musicDuration = _.get(d, 'metadata.library.music.hours')
+        self.stats.artists = _.get(d, 'metadata.library.artists.total')
+        self.stats.albums = _.get(d, 'metadata.library.albums.total')
+        self.stats.tracks = _.get(d, 'metadata.library.tracks.total')
         self.isLoading = false
       })
     }