diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 51d9e1c4b7ed376fd4e17a6651e0460e1cf2d1b1..4eab2dad571201d406bc83f0c8b974bf17699c24 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -13,6 +13,7 @@ Added
 ~~~~~
 
 * Provide an ``ExtensiblePolymorphicModel`` to support the features of extensible models for polymorphic models and vice-versa.
+* Implement optional Sentry integration for error and performance tracing.
 
 Changed
 ~~~~~~~
diff --git a/aleksis/core/settings.py b/aleksis/core/settings.py
index 7a733c13983ba1ca7602f9661ef458b64414db8e..9a0a8b5f2e7de5ff23ddd738db1b8974d32430d1 100644
--- a/aleksis/core/settings.py
+++ b/aleksis/core/settings.py
@@ -486,6 +486,7 @@ YARN_INSTALLED_APPS = [
     "paper-css",
     "jquery-sortablejs",
     "sortablejs",
+    "@sentry/tracing",
 ]
 
 merge_app_settings("YARN_INSTALLED_APPS", YARN_INSTALLED_APPS, True)
@@ -516,6 +517,7 @@ ANY_JS = {
     "Roboto500": {"css_url": JS_URL + "/@fontsource/roboto/500.css"},
     "Roboto700": {"css_url": JS_URL + "/@fontsource/roboto/700.css"},
     "Roboto900": {"css_url": JS_URL + "/@fontsource/roboto/900.css"},
+    "Sentry": {"js_url": JS_URL + "/@sentry/tracing/build/bundle.tracing.js"},
 }
 
 merge_app_settings("ANY_JS", ANY_JS, True)
@@ -859,5 +861,31 @@ else:
 
 SASS_PROCESSOR_STORAGE = DEFAULT_FILE_STORAGE
 
+SENTRY_ENABLED = _settings.get("health.sentry.enabled", False)
+if SENTRY_ENABLED:
+    import sentry_sdk
+    from sentry_sdk.integrations.celery import CeleryIntegration
+    from sentry_sdk.integrations.django import DjangoIntegration
+    from sentry_sdk.integrations.redis import RedisIntegration
+
+    from aleksis.core import __version__
+
+    SENTRY_SETTINGS = {
+        "dsn": _settings.get("health.sentry.dsn"),
+        "environment": _settings.get("health.sentry.environment"),
+        "traces_sample_rate": _settings.get("health.sentry.traces_sample_rate", 1.0),
+        "send_default_pii": _settings.get("health.sentry.send_default_pii", False),
+        "release": f"aleksis-core@{__version__}",
+        "in_app_include": "aleksis",
+    }
+    sentry_sdk.init(
+        integrations=[
+            DjangoIntegration(transaction_style="function_name"),
+            RedisIntegration(),
+            CeleryIntegration(),
+        ],
+        **SENTRY_SETTINGS,
+    )
+
 # Add django-cleanup after all apps to ensure that it gets all signals as last app
 INSTALLED_APPS.append("django_cleanup.apps.CleanupConfig")
diff --git a/aleksis/core/templates/core/base.html b/aleksis/core/templates/core/base.html
index 80e6ee8d7cae0837b1c6b91ce4b258bd2608f22b..0dd75f2c6f237dea1ecac47b010abfac9453000f 100644
--- a/aleksis/core/templates/core/base.html
+++ b/aleksis/core/templates/core/base.html
@@ -35,6 +35,24 @@
   <script src="{% url "calendarweek_i18n_js" %}?first_day=6&amp;locale={{ LANGUAGE_CODE }}"
           type="text/javascript"></script>
 
+  {% if SENTRY_ENABLED %}
+    {% if SENTRY_TRACE_ID %}
+      <meta name="sentry-trace" content="{{ SENTRY_TRACE_ID }}" />
+    {% endif %}
+    {% include_js "Sentry" %}
+    {{ SENTRY_SETTINGS|json_script:"sentry_settings" }}
+    <script type="text/javascript">
+      const sentry_settings = JSON.parse(document.getElementById('sentry_settings').textContent);
+
+      Sentry.init({
+        dsn: sentry_settings.dsn,
+        environment: sentry_settings.environment,
+        tracesSampleRate: sentry_settings.traces_sample_rate,
+        integrations: [new Sentry.Integrations.BrowserTracing()]
+      });
+    </script>
+  {% endif %}
+
   {# Include jQuery early to provide $(document).ready #}
   {% include_js "jQuery" %}
 
diff --git a/aleksis/core/util/core_helpers.py b/aleksis/core/util/core_helpers.py
index 2c40caf51d41045685a74b67800cc9575d3cf2de..5871fd8dd7f568d463c8f672373fa60b708caddf 100644
--- a/aleksis/core/util/core_helpers.py
+++ b/aleksis/core/util/core_helpers.py
@@ -208,12 +208,24 @@ def custom_information_processor(request: HttpRequest) -> dict:
         regrouped_pwa_icons.setdefault(pwa_icon.rel, {})
         regrouped_pwa_icons[pwa_icon.rel][pwa_icon.size] = pwa_icon
 
-    return {
+    context = {
         "FOOTER_MENU": CustomMenu.get_default("footer"),
         "ADMINS": settings.ADMINS,
         "PWA_ICONS": regrouped_pwa_icons,
+        "SENTRY_ENABLED": settings.SENTRY_ENABLED,
     }
 
+    if settings.SENTRY_ENABLED:
+        context["SENTRY_SETTINGS"] = settings.SENTRY_SETTINGS
+
+        import sentry_sdk
+
+        span = sentry_sdk.Hub.current.scope.span
+        if span is not None:
+            context["SENTRY_TRACE_ID"] = span.to_traceparent()
+
+    return context
+
 
 def now_tomorrow() -> datetime:
     """Return current time tomorrow."""
diff --git a/poetry.lock b/poetry.lock
index c9d4b0e2db4557925a052ed17ac3f3958a389ee9..5b8035e59280dcc14adefa0cb88178c007ced081 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -2269,6 +2269,40 @@ python-versions = "*"
 [package.dependencies]
 urllib3 = "*"
 
+[[package]]
+name = "sentry-sdk"
+version = "1.4.3"
+description = "Python client for Sentry (https://sentry.io)"
+category = "main"
+optional = true
+python-versions = "*"
+
+[package.dependencies]
+certifi = "*"
+urllib3 = ">=1.10.0"
+
+[package.extras]
+aiohttp = ["aiohttp (>=3.5)"]
+beam = ["apache-beam (>=2.12)"]
+bottle = ["bottle (>=0.12.13)"]
+celery = ["celery (>=3)"]
+chalice = ["chalice (>=1.16.0)"]
+django = ["django (>=1.8)"]
+falcon = ["falcon (>=1.4)"]
+flask = ["flask (>=0.11)", "blinker (>=1.1)"]
+httpx = ["httpx (>=0.16.0)"]
+pure_eval = ["pure-eval", "executing", "asttokens"]
+pyspark = ["pyspark (>=2.4.4)"]
+rq = ["rq (>=0.6)"]
+sanic = ["sanic (>=0.8)"]
+sqlalchemy = ["sqlalchemy (>=1.2)"]
+tornado = ["tornado (>=5)"]
+
+[package.source]
+type = "legacy"
+url = "https://edugit.org/api/v4/projects/461/packages/pypi/simple"
+reference = "gitlab"
+
 [[package]]
 name = "six"
 version = "1.16.0"
@@ -2647,11 +2681,12 @@ pycryptodome = "*"
 [extras]
 ldap = ["django-auth-ldap"]
 s3 = ["boto3", "django-storages"]
+sentry = []
 
 [metadata]
 lock-version = "1.1"
 python-versions = "^3.9"
-content-hash = "9117742426e175d6402dac268ed74f7e72e084fe020e5ebf22e97915ff7acd0d"
+content-hash = "d15f5a57e0a3e887cfe411829b19459af4ebfc9aadc6e2a0468b11f4fc03e6b4"
 
 [metadata.files]
 alabaster = [
@@ -3770,6 +3805,10 @@ selenium = [
     {file = "selenium-3.141.0-py2.py3-none-any.whl", hash = "sha256:2d7131d7bc5a5b99a2d9b04aaf2612c411b03b8ca1b1ee8d3de5845a9be2cb3c"},
     {file = "selenium-3.141.0.tar.gz", hash = "sha256:deaf32b60ad91a4611b98d8002757f29e6f2c2d5fcaf202e1c9ad06d6772300d"},
 ]
+sentry-sdk = [
+    {file = "sentry-sdk-1.4.3.tar.gz", hash = "sha256:b9844751e40710e84a457c5bc29b21c383ccb2b63d76eeaad72f7f1c808c8828"},
+    {file = "sentry_sdk-1.4.3-py2.py3-none-any.whl", hash = "sha256:c091cc7115ff25fe3a0e410dbecd7a996f81a3f6137d2272daef32d6c3cfa6dc"},
+]
 six = [
     {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
     {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
diff --git a/pyproject.toml b/pyproject.toml
index 7de56924b6d76c659ab4da8762e25e6815b9f435..044f77bfbf8d6f1c1e45923525274aa81d2a0f0b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -113,10 +113,12 @@ Whoosh = "^2.7.4"
 django-titofisto = "^0.1.0"
 haystack-redis = "^0.0.1"
 python-gnupg = "^0.4.7"
+sentry-sdk = {version = "^1.4.3", optional = true}
 
 [tool.poetry.extras]
 ldap = ["django-auth-ldap"]
 s3 = ["boto3", "django-storages"]
+sentry = ["sentry"]
 
 [tool.poetry.dev-dependencies]
 aleksis-builddeps = "*"