diff --git a/global/fs/zfs/default.nix b/global/fs/zfs/default.nix
index 095f39d8..70520af2 100644
--- a/global/fs/zfs/default.nix
+++ b/global/fs/zfs/default.nix
@@ -6,6 +6,7 @@
 in {
   imports = [
     ./split.nix
+    ./replication.nix
   ];
 
   # -o ashift=12
@@ -42,6 +43,24 @@ in {
         description = "UUID of store filesystem";
       };
     };
+
+    replication = {
+      enable = mkEnableOption "zfs replication to remote";
+      remote = mkOption {
+        type = with types; str;
+        description = "remote host as replication destination";
+      };
+      datasets = mkOption {
+        type = with types; listOf str;
+        default = [ "persist" "service" "storage" ];
+        description = "list of filesystems to perform replication for";
+      };
+      sendOptions = mkOption {
+        type = with types; str;
+        default = "w";
+        description = "send options for all datasets";
+      };
+    };
   };
 
   config = mkIf (cfg.type == "zfs") {
diff --git a/global/fs/zfs/replication.nix b/global/fs/zfs/replication.nix
new file mode 100644
index 00000000..8f04cd63
--- /dev/null
+++ b/global/fs/zfs/replication.nix
@@ -0,0 +1,29 @@
+{ pkgs
+, lib
+, config
+, ... }: with lib; let
+  cfg = config.global.fs.zfs.replication;
+in mkIf cfg.enable {
+  services.syncoid = {
+    enable = mkDefault true;
+    interval = mkDefault "daily";
+    sshKey = mkDefault "/var/lib/syncoid/.ssh/id_ed25519";
+    commonArgs = [
+      "--recursive"
+      "--compress=lz4"
+      "--mbuffer-size=128M"
+    ];
+    localSourceAllow = options.services.syncoid.localSourceAllow.default ++ [ "mount" ];
+
+    commands = (lists.foldr (name: commands: commands // {
+      "${config.global.fs.store}/${name}" = {
+        inherit (cfg) sendOptions;
+        target = "${cfg.remote}/${name}";
+      };
+    }) { }) cfg.datasets;
+  };
+
+  users.users.syncoid.uid = 82;
+  users.groups.syncoid.gid = 82;
+  environment.persistence."/nix/persist/fhs".directories = [ "/var/lib/syncoid" ];
+}