diff --git a/home-server.nix b/home-server.nix
index ca4cae9..618786b 100644
--- a/home-server.nix
+++ b/home-server.nix
@@ -24,6 +24,7 @@ in with builtins; {
     ./services/genie.nix
     ./services/website.nix
     ./services/home-assistant
+    ./services/matrix
     ./services/watchtower.nix
     ./services/immich.nix
     ./services/miniflux.nix
@@ -84,6 +85,8 @@ in with builtins; {
       "felschr.com"
       "home.felschr.com"
       "esphome.felschr.com"
+      "matrix.felschr.com"
+      "element.felschr.com"
       "cloud.felschr.com"
       "office.felschr.com"
       "media.felschr.com"
diff --git a/secrets/dendrite/.env.age b/secrets/dendrite/.env.age
new file mode 100644
index 0000000..83dc107
--- /dev/null
+++ b/secrets/dendrite/.env.age
@@ -0,0 +1,9 @@
+age-encryption.org/v1
+-> ssh-ed25519 OAZQhA fgZF7gVrFY+mwM8BEfgkzXS7sCgarHTS2Y8UK7FB3hg
+5Q45bu0Z1SbTsx0R2He4u7SErfe/DisHliYuxqisHyc
+-> ssh-ed25519 72ij7w DnbIuHAmxkTqn7pgxlq91O9h0b0wsj2VighRh1WqQ0U
+C1JAbl23+sZf4OClbqsc7GEGWauW9GfjCHfCmaYLaX0
+-> Oz`pkbz-grease 5w #,[ \
+JgtzmyNqvGxlyND1WSJLtlnmWYur
+--- UlLvWMaswO3IWQLj/C8qhZaM2ffdsht54E6UVKRBHD8
+�K�8�kQ#N$��M�8\��"ؖϳ�S��d���Q��|32����9���DC�Pϻ6�+s(۫i|^5!�{��0�̻`������I/��U���'VY
\ No newline at end of file
diff --git a/secrets/dendrite/privateKey.age b/secrets/dendrite/privateKey.age
new file mode 100644
index 0000000..eddc912
Binary files /dev/null and b/secrets/dendrite/privateKey.age differ
diff --git a/secrets/secrets.nix b/secrets/secrets.nix
index cd35cf6..0330a3a 100644
--- a/secrets/secrets.nix
+++ b/secrets/secrets.nix
@@ -33,4 +33,6 @@ in {
   "esphome/password.age".publicKeys = [ felschr home-server ];
   "focalboard/.env.age".publicKeys = [ felschr home-server ];
   "focalboard/db-password.age".publicKeys = [ felschr home-server ];
+  "dendrite/.env.age".publicKeys = [ felschr home-server ];
+  "dendrite/privateKey.age".publicKeys = [ felschr home-server ];
 }
diff --git a/services/matrix/default.nix b/services/matrix/default.nix
new file mode 100644
index 0000000..ff3a114
--- /dev/null
+++ b/services/matrix/default.nix
@@ -0,0 +1,5 @@
+{ ... }:
+
+{
+  imports = [ ./dendrite.nix ./element.nix ];
+}
diff --git a/services/matrix/dendrite.nix b/services/matrix/dendrite.nix
new file mode 100644
index 0000000..db0c477
--- /dev/null
+++ b/services/matrix/dendrite.nix
@@ -0,0 +1,124 @@
+{ config, pkgs, ... }:
+
+let
+  inherit (config.services) dendrite;
+  server_name = "felschr.com";
+  domain = "matrix.${server_name}";
+  connectionString = "postgresql:///dendrite?host=/run/postgresql";
+in {
+  age.secrets.dendrite-private-key = {
+    file = ../../secrets/dendrite/privateKey.age;
+    mode = "755";
+  };
+  age.secrets.dendrite-env = {
+    file = ../../secrets/dendrite/.env.age;
+    mode = "755";
+  };
+
+  services.dendrite = {
+    enable = true;
+    environmentFile = config.age.secrets.dendrite-env.path;
+    settings = {
+      app_service_api.database.connection_string = connectionString;
+      federation_api.database.connection_string = connectionString;
+      key_server.database.connection_string = connectionString;
+      media_api.database.connection_string = connectionString;
+      mscs.database.connection_string = connectionString;
+      room_server.database.connection_string = connectionString;
+      sync_api.database.connection_string = connectionString;
+      user_api.account_database.connection_string = connectionString;
+      user_api.device_database.connection_string = connectionString;
+
+      client_api.registration_shared_secret = "$REGISTRATION_SHARED_SECRET";
+
+      # 2 megabytes in bytes
+      media_api.max_file_size_bytes = 2097152;
+
+      mscs.mscs = [
+        # threading
+        "msc2946"
+        # spaces
+        "msc2836"
+      ];
+
+      federation_api.key_perspectives = [{
+        server_name = "matrix.org";
+        keys = [
+          {
+            key_id = "ed25519:auto";
+            public_key = "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw";
+          }
+          {
+            key_id = "ed25519:a_RXGa";
+            public_key = "l8Hft5qXKn1vfHrg3p4+W8gELQVo8N13JkluMfmn2sQ";
+          }
+        ];
+      }];
+
+      global = {
+        inherit server_name;
+        private_key = config.age.secrets.dendrite-private-key.path;
+        jetstream.storage_path = "/var/lib/dendrite/jetstream";
+        dns_cache = {
+          enabled = true;
+          cache_size = 4096;
+          cache_lifetime = "600s";
+        };
+      };
+    };
+  };
+
+  services.postgresql = {
+    ensureUsers = [{
+      name = "dendrite";
+      ensurePermissions = { "DATABASE dendrite" = "ALL PRIVILEGES"; };
+    }];
+    ensureDatabases = [ "dendrite" ];
+  };
+
+  systemd.services.dendrite.after = [ "postgresql.service" ];
+
+  services.nginx.virtualHosts = {
+    ${server_name} = {
+      enableACME = true;
+      forceSSL = true;
+      locations = let
+        server = { "m.server" = "${domain}:443"; };
+        client = {
+          "m.homeserver"."base_url" = "https://${domain}";
+          "m.identity_server"."base_url" = "https://vector.im";
+        };
+      in {
+        "= /.well-known/matrix/server".extraConfig = ''
+          add_header Content-Type application/json;
+          return 200 '${builtins.toJSON server}';
+        '';
+        "= /.well-known/matrix/client".extraConfig = ''
+          add_header Content-Type application/json;
+          add_header Access-Control-Allow-Origin *;
+          return 200 '${builtins.toJSON client}';
+        '';
+      };
+    };
+    "${domain}" = {
+      enableACME = true;
+      forceSSL = true;
+      locations = {
+        "/".extraConfig = ''
+          return 404;
+        '';
+        "/_matrix".proxyPass =
+          "http://127.0.0.1:${toString config.services.dendrite.httpPort}";
+      };
+    };
+  };
+
+  environment.systemPackages = [
+    # run like: dendrite-create-account --username --admin
+    (pkgs.writeShellScriptBin "dendrite-create-account" ''
+      ${pkgs.dendrite}/bin/create-account \
+        --config /run/dendrite/dendrite.yaml \
+        "$@"
+    '')
+  ];
+}
diff --git a/services/matrix/element.nix b/services/matrix/element.nix
new file mode 100644
index 0000000..7f38907
--- /dev/null
+++ b/services/matrix/element.nix
@@ -0,0 +1,18 @@
+{ config, pkgs, ... }:
+
+let inherit (config.services.dendrite.settings.global) server_name;
+in {
+  services.nginx.virtualHosts."element.felschr.com" = {
+    forceSSL = true;
+    enableACME = true;
+    root = pkgs.element-web.override {
+      conf = {
+        default_server_config."m.homeserver" = {
+          "base_url" = "https://matrix.${server_name}";
+          "server_name" = "${server_name}";
+        };
+        show_labs_settings = true;
+      };
+    };
+  };
+}