diff --git a/morph/grid/local/grid.nix b/morph/grid/local/grid.nix
index ed3cda9e787ba567c5c5eed19557fc281a80ceb6..efdfa1f955d9864e56acb20bb0c5810f22c47643 100644
--- a/morph/grid/local/grid.nix
+++ b/morph/grid/local/grid.nix
@@ -116,9 +116,7 @@ let
     imports = [
       gridlib.monitoring
       (gridlib.customize-monitoring {
-        inherit hostsMap vpnClientIPs
-                nodeExporterTargets
-                paymentExporterTargets
+        inherit paymentExporterTargets
                 blackboxExporterHttpsTargets;
         inherit (grid-config) letsEncryptAdminEmail monitoringDomains;
         googleOAuthClientID = grid-config.monitoringGoogleOAuthClientID;
@@ -134,14 +132,6 @@ let
   };
 
   # TBD: derive these automatically:
-  hostsMap = {
-    "172.23.23.1"  = [ "monitoring" "monitoring.monitoringvpn" ];
-    "172.23.23.11" = [ "payments" "payments.monitoringvpn" ];
-    "172.23.23.12" = [ "storage1" "storage1.monitoringvpn" ];
-    "172.23.23.13" = [ "storage2" "storage2.monitoringvpn" ];
-  };
-  vpnClientIPs = [ "172.23.23.11" "172.23.23.12" "172.23.23.13" ];
-  nodeExporterTargets = [ "monitoring" "payments" "storage1" "storage2" ];
   paymentExporterTargets = [ "payments" ];
   blackboxExporterHttpsTargets = [
     # "https://private.storage/"
diff --git a/morph/grid/production/grid.nix b/morph/grid/production/grid.nix
index 12873a4bc010887c54f522a01115061e9875726f..33b9726ffe7dc90a6d6528fb3d0da7d2fa401430 100644
--- a/morph/grid/production/grid.nix
+++ b/morph/grid/production/grid.nix
@@ -49,9 +49,7 @@ let
       gridlib.monitoring
       gridlib.hardware-aws
       (gridlib.customize-monitoring {
-        inherit hostsMap vpnClientIPs
-                nodeExporterTargets
-                paymentExporterTargets
+        inherit paymentExporterTargets
                 blackboxExporterHttpsTargets;
         inherit (grid-config) letsEncryptAdminEmail monitoringDomains;
         googleOAuthClientID = grid-config.monitoringGoogleOAuthClientID;
@@ -117,33 +115,6 @@ let
     storage005 = { vpnIP = "172.23.23.25"; stateVersion = "19.03"; };
   };
 
-  # TBD: derive these automatically:
-  hostsMap = {
-    "172.23.23.1"  = [ "monitoring" "monitoring.monitoringvpn" ];
-    "172.23.23.11" = [   "payments"   "payments.monitoringvpn" ];
-    "172.23.23.21" = [ "storage001" "storage001.monitoringvpn" ];
-    "172.23.23.22" = [ "storage002" "storage002.monitoringvpn" ];
-    "172.23.23.23" = [ "storage003" "storage003.monitoringvpn" ];
-    "172.23.23.24" = [ "storage004" "storage004.monitoringvpn" ];
-    "172.23.23.25" = [ "storage005" "storage005.monitoringvpn" ];
-  };
-  vpnClientIPs = [
-    "172.23.23.11"
-    "172.23.23.21"
-    "172.23.23.22"
-    "172.23.23.23"
-    "172.23.23.24"
-    "172.23.23.25"
-  ];
-  nodeExporterTargets = [
-    "monitoring"
-    "payments"
-    "storage001"
-    "storage002"
-    "storage003"
-    "storage004"
-    "storage005"
-  ];
   paymentExporterTargets = [ "payments" ];
   blackboxExporterHttpsTargets = [
     "https://private.storage/"
diff --git a/morph/grid/testing/grid.nix b/morph/grid/testing/grid.nix
index 7f7aeccbaba1f4e8d2c9576ece080791d03fd065..8d3e5483f398a5db5c422d197a80ab6f41542d15 100644
--- a/morph/grid/testing/grid.nix
+++ b/morph/grid/testing/grid.nix
@@ -65,9 +65,7 @@ let
       gridlib.monitoring
       gridlib.hardware-aws
       (gridlib.customize-monitoring {
-        inherit hostsMap vpnClientIPs
-                nodeExporterTargets
-                paymentExporterTargets
+        inherit paymentExporterTargets
                 blackboxExporterHttpsTargets;
         inherit (grid-config) letsEncryptAdminEmail monitoringDomains;
         googleOAuthClientID = grid-config.monitoringGoogleOAuthClientID;
@@ -82,13 +80,6 @@ let
   };
 
   # TBD: derive these automatically:
-  hostsMap = {
-    "172.23.23.1"  = [ "monitoring" "monitoring.monitoringvpn" ];
-    "172.23.23.11" = [ "payments"   "payments.monitoringvpn"   ];
-    "172.23.23.12" = [ "storage001" "storage001.monitoringvpn" ];
-  };
-  vpnClientIPs = [ "172.23.23.11" "172.23.23.12" ];
-  nodeExporterTargets = [ "monitoring" "payments" "storage001" ];
   paymentExporterTargets = [ "payments" ];
   blackboxExporterHttpsTargets = [
     "https://privatestorage-staging.com/"
diff --git a/morph/lib/customize-monitoring.nix b/morph/lib/customize-monitoring.nix
index 67544de5bab466fab0927b71b493ff81935192e9..841a0d04b9bb462eb9d62742a514c9fa412f3206 100644
--- a/morph/lib/customize-monitoring.nix
+++ b/morph/lib/customize-monitoring.nix
@@ -1,29 +1,14 @@
 # Define a function which returns a value which fills in all the holes left by
 # ``monitoring.nix``.
 {
-  # A set mapping VPN IP addresses as strings to lists of hostnames as
-  # strings.  The system's ``/etc/hosts`` will be populated with this
-  # information.  Apart from helping with normal forward resolution, this
-  # *also* gives us reverse resolution from the VPN IPs to hostnames which
-  # allows Grafana to show us hostnames instead of VPN IP addresses.
-  hostsMap
-
   # A string giving an email address to use for Let's Encrypt registration and
   # certificate issuance.
-, letsEncryptAdminEmail
+  letsEncryptAdminEmail
 
   # A list of strings giving the domain names that point at this monitoring
   # system.  These will all be included in Let's Encrypt certificate.
 , monitoringDomains
 
-  # A list of VPN IP addresses as strings indicating which clients will be
-  # allowed onto the VPN.
-, vpnClientIPs
-
-  # A list of VPN clients (IP addresses or hostnames) as strings indicating
-  # which nodes to scrape "nodeExporter" metrics from.
-, nodeExporterTargets
-
   # A list of VPN clients (IP addresses or hostnames) as strings indicating
   # which nodes to scrape "nginxExporter" metrics from.
 , nginxExporterTargets ? []
@@ -99,17 +84,7 @@ in {
     in
       grafanaSSO // grafanaSlackUrl;
 
-  networking.hosts = hostsMap;
-
-  services.private-storage.monitoring.vpn.server = {
-    enable = true;
-    ip = monitoringvpnIPv4;
-    inherit vpnClientIPs;
-    pubKeysPath = "${publicKeyPath}/monitoringvpn";
-  };
-
   services.private-storage.monitoring.prometheus = {
-    inherit nodeExporterTargets;
     inherit nginxExporterTargets;
     inherit paymentExporterTargets;
     inherit blackboxExporterHttpsTargets;
diff --git a/morph/lib/monitoring.nix b/morph/lib/monitoring.nix
index e10dd31c4dee7404d436285ad80c9babc350c246..ca1db1a029775b37e749b4462545dd539c66fda2 100644
--- a/morph/lib/monitoring.nix
+++ b/morph/lib/monitoring.nix
@@ -1,30 +1,30 @@
 # Similar to ``issuer.nix`` but for a "monitoring"-type system.  Holes are
 # filled by ``customize-monitoring.nix``.
-{ config, ...}:
+{ lib, config, nodes, ...}:
 let
-  inherit (config.grid) privateKeyPath;
-in {
-  deployment = {
-    secrets = {
-      "monitoringvpn-private-key" = {
-        destination = "/run/keys/monitoringvpn/server.key";
-        source = "${privateKeyPath}/monitoringvpn/server.key";
-        owner.user = "root";
-        owner.group = "root";
-        permissions = "0400";
-        action = ["sudo" "systemctl" "restart" "wireguard-monitoringvpn.service"];
-      };
-      "monitoringvpn-preshared-key" = {
-        destination = "/run/keys/monitoringvpn/preshared.key";
-        source = "${privateKeyPath}/monitoringvpn/preshared.key";
-        owner.user = "root";
-        owner.group = "root";
-        permissions = "0400";
-        action = ["sudo" "systemctl" "restart" "wireguard-monitoringvpn.service"];
-      };
-    };
-  };
+  inherit (config.grid) publicKeyPath privateKeyPath monitoringvpnIPv4;
+
+  # This collects information about monitored hosts from their configuration for use below.
+  monitoringHosts = lib.mapAttrsToList (name: node: rec {
+    inherit name;
+    vpnIPv4 = node.config.grid.monitoringvpnIPv4;
+    vpnHostName = "${name}.monitoringvpn";
+    hostNames = [name vpnHostName];
+  }) nodes;
 
+  # A set mapping VPN IP addresses as strings to lists of hostnames as
+  # strings.  The system's ``/etc/hosts`` will be populated with this
+  # information.  Apart from helping with normal forward resolution, this
+  # *also* gives us reverse resolution from the VPN IPs to hostnames which
+  # allows Grafana to show us hostnames instead of VPN IP addresses.
+  hostsMap = lib.listToAttrs (map (node: lib.nameValuePair node.vpnIPv4 node.hostNames) monitoringHosts);
+  # A list of VPN IP addresses as strings indicating which clients will be
+  # allowed onto the VPN.
+  vpnClientIPs = lib.remove monitoringvpnIPv4 (map (node: node.vpnIPv4) monitoringHosts);
+  # A list of VPN clients (IP addresses or hostnames) as strings indicating
+  # which nodes to scrape "nodeExporter" metrics from.
+  nodeExporterTargets = map (node: node.name) monitoringHosts;
+in {
   imports = [
     ../../nixos/modules/monitoring/vpn/server.nix
     ../../nixos/modules/monitoring/server/grafana.nix
@@ -34,4 +34,40 @@ in {
     # Loki 0.3.0 from Nixpkgs 19.09 is too old and does not work:
     # ../../nixos/modules/monitoring/server/loki.nix
   ];
+
+  config = {
+    deployment = {
+      secrets = {
+        "monitoringvpn-private-key" = {
+          destination = "/run/keys/monitoringvpn/server.key";
+          source = "${privateKeyPath}/monitoringvpn/server.key";
+          owner.user = "root";
+          owner.group = "root";
+          permissions = "0400";
+          action = ["sudo" "systemctl" "restart" "wireguard-monitoringvpn.service"];
+        };
+        "monitoringvpn-preshared-key" = {
+          destination = "/run/keys/monitoringvpn/preshared.key";
+          source = "${privateKeyPath}/monitoringvpn/preshared.key";
+          owner.user = "root";
+          owner.group = "root";
+          permissions = "0400";
+          action = ["sudo" "systemctl" "restart" "wireguard-monitoringvpn.service"];
+        };
+      };
+    };
+
+    networking.hosts = hostsMap;
+
+    services.private-storage.monitoring.vpn.server = {
+      enable = true;
+      ip = monitoringvpnIPv4;
+      inherit vpnClientIPs;
+      pubKeysPath = "${publicKeyPath}/monitoringvpn";
+    };
+
+    services.private-storage.monitoring.prometheus = {
+      inherit nodeExporterTargets;
+    };
+  };
 }