diff --git a/morph/lib/storage.nix b/morph/lib/storage.nix
index 86e142286351237099337d38d03a9b54255b8246..15e2373737a7ff2f1efe8cf2c41b59de606f0a1a 100644
--- a/morph/lib/storage.nix
+++ b/morph/lib/storage.nix
@@ -43,8 +43,12 @@ in {
     ../../nixos/modules/monitoring/vpn/client.nix
     # Expose base system metrics over the monitoringvpn.
     ../../nixos/modules/monitoring/exporters/node.nix
+    # Collect Tahoe OpenMetrics statistics.
+    ../../nixos/modules/monitoring/exporters/tahoe.nix
   ];
 
+  services.private-storage.monitoring.tahoe.enable = true;
+
   # Turn on the Private Storage (Tahoe-LAFS) service.
   services.private-storage = {
     # Yep.  Turn it on.
diff --git a/nixos/modules/monitoring/exporters/megacli2prom.nix b/nixos/modules/monitoring/exporters/megacli2prom.nix
index b364088aa148a2faa58a6ac34980120f8d7bf9d0..a38f1ccc18b59073ff835e50babeb565f79a20b8 100644
--- a/nixos/modules/monitoring/exporters/megacli2prom.nix
+++ b/nixos/modules/monitoring/exporters/megacli2prom.nix
@@ -42,7 +42,13 @@ in {
         wantedBy = [ "multi-user.target" ];
         startAt = cfg.interval;
         path = [ pkgs.megacli ];
-        script = "${ourpkgs.megacli2prom}/bin/megacli2prom > ${cfg.outFile}";
+        # Save to a temp file and then move atomically so the
+        # textfile collector won't read a partial file.
+        # See https://github.com/prometheus/node_exporter#textfile-collector
+        script = ''
+          "${ourpkgs.megacli2prom}/bin/megacli2prom" > "${cfg.outFile}.tmp"
+          mv "${cfg.outFile}.tmp" "${cfg.outFile}"
+        '';
       };
   };
 }
diff --git a/nixos/modules/monitoring/exporters/tahoe.nix b/nixos/modules/monitoring/exporters/tahoe.nix
new file mode 100644
index 0000000000000000000000000000000000000000..17b6320e3a01e18fd9a08c848e4223c65902e158
--- /dev/null
+++ b/nixos/modules/monitoring/exporters/tahoe.nix
@@ -0,0 +1,59 @@
+# Tahoe Prometheus metrics collector
+#
+# Scope: Retrieves OpenMetrics from Tahoe and puts them
+#        where textfile collector can find them.
+#
+# Usage: Import this to every server running Tahoe.
+#
+# See https://nixos.org/manual/nixos/stable/#module-services-prometheus-exporters
+
+{ config, options, lib, pkgs, ... }:
+
+let
+  cfg = config.services.private-storage.monitoring.tahoe;
+
+in {
+  options.services.private-storage.monitoring.tahoe = {
+    enable = lib.mkEnableOption "Tahoe OpenMetrics collecting service";
+    scrapeEndpoint = lib.mkOption {
+      type = lib.types.str;
+      description = "Where to get our metrics from?";
+      default = "http://localhost:3456/statistics?t=openmetrics";
+    };
+    outFile = lib.mkOption {
+      type = lib.types.str;
+      description = "Where to store the temporary file for node exporter to scrape?";
+      default = "/run/prometheus-node-exporter/tahoe.prom";
+    };
+    interval = lib.mkOption {
+      type = lib.types.str;
+      description = ''
+        How often to do it?
+        See https://www.freedesktop.org/software/systemd/man/systemd.time.html#Calendar%20Events
+      '';
+      # Every five minutes.
+      default = "*:0/5";
+    };
+  };
+
+  config =
+    lib.mkIf cfg.enable {
+      environment.systemPackages = [ pkgs.curl ];
+      systemd.services.tahoe-metrics-collector = {
+        enable = true;
+        description = "Tahoe metrics gathering service";
+        wantedBy = [ "multi-user.target" ];
+        startAt = cfg.interval;
+        path = [ pkgs.curl ];
+
+        # Save to a temp file and then move atomically so the
+        # textfile collector won't read a partial file.
+        # See https://github.com/prometheus/node_exporter#textfile-collector
+        script = ''
+          curl --silent --show-error --fail-with-body --output "${cfg.outFile}.tmp" "${cfg.scrapeEndpoint}"
+          mv "${cfg.outFile}.tmp" "${cfg.outFile}"
+        '';
+      };
+  };
+}
+