diff --git a/morph/grid/local/README.rst b/morph/grid/local/README.rst
index 345547244635734278aa76cb5cd59946f2afd37f..f899c4975bab131e969acc79e8b39dee139b1554 100644
--- a/morph/grid/local/README.rst
+++ b/morph/grid/local/README.rst
@@ -33,13 +33,6 @@ Use the local development environment
 
     install -d ~/.ssh ; vagrant ssh-config >> ~/.ssh/config
 
-5. Edit the generated configuration: Add the ``publicIP`` addresses from ``grid.nix`` to ssh config **Host** match blocks (**not** HostName) so the ``Host`` lines all read like::
-
-    Host payments 192.168.67.21
-      HostName 127.0.0.1
-      User vagrant
-      [...]
-
   Latest Morph honors the ``SSH_CONFIG_FILE`` environment variable (`since 3f90aa88 (March 2020, v 1.5.0) <https://github.com/DBCDK/morph/commit/3f90aa885fac1c29fce9242452fa7c0c505744ef#diff-d155ad793bd62e6ea4c44ba985049ecb13a4f4f32f799791b2bce695a16c0101>`_), so in the future this should get a bit more convenient.
 
 6. Add your SSH key to ``users.nix`` so you'll be able to log in after deploying the new configuration::
@@ -56,4 +49,3 @@ Use the local development environment
     morph upload-secrets grid.nix
 
   You should now be able to log in with the users and keys you set in your ``users.nix`` file.
-
diff --git a/morph/grid/local/Vagrantfile b/morph/grid/local/Vagrantfile
index 7ad95ca872a72e5da6c11b3269e2a824cf8a55f9..fd374a2cc26da63262a16fdf462e235fc5523fd1 100644
--- a/morph/grid/local/Vagrantfile
+++ b/morph/grid/local/Vagrantfile
@@ -8,7 +8,7 @@ Vagrant.configure("2") do |config|
   # For a complete reference, please see the online documentation at
   # https://docs.vagrantup.com.
 
-  config.vm.define "payments" do |config|
+  config.vm.define "payments.localdev" do |config|
     config.vm.hostname = "payments"
     config.vm.box = "esselius/nixos"
     config.vm.box_version = "20.09"
@@ -20,7 +20,7 @@ Vagrant.configure("2") do |config|
     config.vm.provision "shell", inline: "sudo mv /tmp/payments-localdev-ssl/* /var/lib/letsencrypt/live/payments.localdev/"
   end
 
-  config.vm.define "storage1" do |config|
+  config.vm.define "storage1.localdev" do |config|
     config.vm.hostname = "storage1"
     config.vm.box = "esselius/nixos"
     config.vm.box_version = "20.09"
@@ -28,7 +28,7 @@ Vagrant.configure("2") do |config|
     config.vm.network "private_network", ip: "192.168.67.22"
   end
 
-  config.vm.define "storage2" do |config|
+  config.vm.define "storage2.localdev" do |config|
     config.vm.hostname = "storage2"
     config.vm.box = "esselius/nixos"
     config.vm.box_version = "20.09"
@@ -36,7 +36,7 @@ Vagrant.configure("2") do |config|
     config.vm.network "private_network", ip: "192.168.67.23"
   end
 
-  config.vm.define "monitoring" do |config|
+  config.vm.define "monitoring.localdev" do |config|
     config.vm.hostname = "monitoring"
     config.vm.box = "esselius/nixos"
     config.vm.box_version = "20.09"
@@ -54,4 +54,3 @@ Vagrant.configure("2") do |config|
   end
 
 end
-
diff --git a/morph/grid/local/config.json b/morph/grid/local/config.json
index 38f00367bf2fa36ad7663c89f7849146783b8515..3d377cc0e1ebbdec0dff421c806c901e2e5ce06d 100644
--- a/morph/grid/local/config.json
+++ b/morph/grid/local/config.json
@@ -1,4 +1,5 @@
-{ "publicStoragePort": 8898
+{ "domain": "localdev"
+, "publicStoragePort": 8898
 , "ristrettoSigningKeyPath": "./secrets/ristretto.signing-key"
 , "stripeSecretKeyPath": "./secrets/stripe.secret"
 , "monitoringvpnKeyDir": "./secrets/monitoringvpn"
diff --git a/morph/grid/local/grid.nix b/morph/grid/local/grid.nix
index 1729425d91bc02f56fa895d04731ffcc74971cbe..5345a16198e79dd8c91c8566fb62480ce5cea51a 100644
--- a/morph/grid/local/grid.nix
+++ b/morph/grid/local/grid.nix
@@ -10,47 +10,45 @@ let
     monitoringvpnKeyDir = toString ./. + "/${rawConfig.monitoringvpnKeyDir}";
   };
 
-  payments = let publicIPv4 = "192.168.67.21"; in {
+  payments = {
     imports = [
       gridlib.issuer
-      (gridlib.hardware-virtual ({ inherit publicIPv4; }))
+      (gridlib.hardware-virtual ({ publicIPv4 = "192.168.67.21"; }))
       (gridlib.customize-issuer (config // {
           monitoringvpnIPv4 = "172.23.23.11";
       }))
     ];
   };
 
-  storage1 = let publicIPv4 = "192.168.67.22"; in {
+  storage1 = {
     imports = [
       gridlib.storage
-      (gridlib.hardware-virtual ({ inherit publicIPv4; }))
+      (gridlib.hardware-virtual ({ publicIPv4 = "192.168.67.22"; }))
       (gridlib.customize-storage (config // {
-        inherit publicIPv4;
         monitoringvpnIPv4 = "172.23.23.12";
         stateVersion = "19.09";
       }))
     ];
   };
 
-  storage2 = let publicIPv4 = "192.168.67.23"; in {
+  storage2 = {
     imports = [
       gridlib.storage
-      (gridlib.hardware-virtual ({ inherit publicIPv4; }))
+      (gridlib.hardware-virtual ({ publicIPv4 = "192.168.67.23"; }))
       (gridlib.customize-storage (config // {
-        inherit publicIPv4;
         monitoringvpnIPv4 = "172.23.23.13";
         stateVersion = "19.09";
       }))
     ];
   };
 
-  monitoring = let publicIPv4 = "192.168.67.24"; in {
+  monitoring = {
     imports = [
       gridlib.monitoring
-      (gridlib.hardware-virtual ({ inherit publicIPv4; }))
+      (gridlib.hardware-virtual ({ publicIPv4 = "192.168.67.24"; }))
       (gridlib.customize-monitoring {
-        inherit hostsMap publicIPv4 vpnClientIPs nodeExporterTargets;
-        inherit (config) monitoringvpnKeyDir;
+        inherit hostsMap vpnClientIPs nodeExporterTargets;
+        inherit (config) domain monitoringvpnKeyDir;
         monitoringvpnIPv4 = "172.23.23.1";
         stateVersion = "19.09";
       })
diff --git a/morph/grid/production/config.json b/morph/grid/production/config.json
index ef7dc53649febcd7beb7901bb3608204df197059..21e080d587ae2713a73f756b2b7e078d843b2a95 100644
--- a/morph/grid/production/config.json
+++ b/morph/grid/production/config.json
@@ -1,4 +1,5 @@
-{ "publicStoragePort": 8898
+{ "domain": "private.storage"
+, "publicStoragePort": 8898
 , "ristrettoSigningKeyPath": "./secrets/ristretto.signing-key"
 , "stripeSecretKeyPath": "./secrets/stripe.secret"
 , "monitoringvpnKeyDir": "./secrets/monitoringvpn"
diff --git a/morph/grid/production/grid.nix b/morph/grid/production/grid.nix
index 7a8e29bb58da584d07660663912a1c993146787b..ae51174b4f15a72ca0c1d1798b067ecb1db64bb3 100644
--- a/morph/grid/production/grid.nix
+++ b/morph/grid/production/grid.nix
@@ -21,25 +21,29 @@ let
     ];
   };
 
-  monitoring = let publicIPv4 = "monitoring.private.storage"; in {
+  monitoring = {
     imports = [
       gridlib.monitoring
       gridlib.hardware-aws
       (gridlib.customize-monitoring {
-        inherit hostsMap publicIPv4 vpnClientIPs nodeExporterTargets;
-        inherit (config) monitoringvpnKeyDir;
+        inherit hostsMap vpnClientIPs nodeExporterTargets;
+        inherit (config) domain monitoringvpnKeyDir;
         monitoringvpnIPv4 = "172.23.23.1";
         stateVersion = "19.09";
       })
     ];
   };
 
-  defineStorageNode = name: { vpnIP, stateVersion }: let nodecfg = import "${./.}/${name}-config.nix"; in {
+  defineStorageNode = name: { vpnIP, stateVersion }:
+  let
+    nodecfg = import "${./.}/${name}-config.nix";
+    hardware ="${./.}/${name}-hardware.nix";
+  in {
     imports = [
       # Get some of the very lowest-level system configuration for this
       # node.  This isn't all *completely* hardware related.  Maybe some
       # more factoring is in order, someday.
-      "${./.}/${name}-hardware.nix"
+      hardware
 
       # Slightly awkwardly, enable some of our hardware / network / bootloader options.
       ../../../nixos/modules/100tb.nix
@@ -102,7 +106,6 @@ in {
   network = {
     description = "PrivateStorage.io Production Grid";
   };
-
-  "payments.privatestorage.io" = payments;
+  inherit payments;
   inherit monitoring;
 } // storageNodes
diff --git a/morph/grid/testing/config.json b/morph/grid/testing/config.json
index a44b465f7f293f9d70c369a076c30b6cf810924f..c069bbed531e63a425e16a1838dedbfd2f17374d 100644
--- a/morph/grid/testing/config.json
+++ b/morph/grid/testing/config.json
@@ -1,4 +1,5 @@
-{ "publicStoragePort": 8898
+{ "domain": "privatestorage-staging.com"
+, "publicStoragePort": 8898
 , "ristrettoSigningKeyPath": "./secrets/ristretto.signing-key"
 , "stripeSecretKeyPath": "./secrets/stripe.secret"
 , "monitoringvpnKeyDir": "./secrets/monitoringvpn"
diff --git a/morph/grid/testing/grid.nix b/morph/grid/testing/grid.nix
index 8e68558a13c750eebac48c40dc0822d7f24db1bf..19eefd9d50d49094a0bcf87698f8c8091032e1fa 100644
--- a/morph/grid/testing/grid.nix
+++ b/morph/grid/testing/grid.nix
@@ -21,25 +21,24 @@ let
     ];
   };
 
-  storage001 = let publicIPv4 = "3.120.26.190"; in {
+  storage001 = {
     imports = [
       gridlib.storage
       ./testing001-hardware.nix
       (gridlib.customize-storage (config // {
-        inherit publicIPv4;
         monitoringvpnIPv4 = "172.23.23.12";
         stateVersion = "19.03";
       }))
     ];
   };
 
-  monitoring = let publicIPv4 = "18.156.171.217"; in {
+  monitoring = {
     imports = [
       gridlib.monitoring
       gridlib.hardware-aws
       (gridlib.customize-monitoring {
-        inherit hostsMap publicIPv4 vpnClientIPs nodeExporterTargets;
-        inherit (config) monitoringvpnKeyDir;
+        inherit hostsMap vpnClientIPs nodeExporterTargets;
+        inherit (config) domain monitoringvpnKeyDir;
         monitoringvpnIPv4 = "172.23.23.1";
         stateVersion = "19.09";
       })
diff --git a/morph/lib/customize-issuer.nix b/morph/lib/customize-issuer.nix
index 410bce47db83381f42949a4b8dd3f552f1b0bc5c..28edb72e7e0b74879e9e676113c327f50b040d40 100644
--- a/morph/lib/customize-issuer.nix
+++ b/morph/lib/customize-issuer.nix
@@ -26,6 +26,15 @@
   # A string giving the VPN IPv4 address for this system.
 , monitoringvpnIPv4
 
+  # A string giving the domain name associated with this grid.  This is meant
+  # to be combined with the hostname for this system to produce a
+  # fully-qualified domain name.  For example, an issuer might have "payments"
+  # as its hostname and belong to a grid with the domain
+  # "example-grid.invalid".  This ``domain`` parameter should have the value
+  # ``"example-grid.invalid"`` for the system figure out that
+  # ``payments.example-grid.invalid`` is the name of this system.
+, domain
+
   # A set mapping usernames as strings to SSH public keys as strings.  For
   # each element of the site, the indicated user is configured on the system
   # with the indicated SSH key as an authorized key.
@@ -43,7 +52,16 @@
   # to allow.
 , allowedChargeOrigins
 , ...
-}: {
+}:
+{ config, ... }: {
+  # The morph default deployment target the name of the node in the network
+  # attrset.  We don't always want to give the node its proper public address
+  # there (because it depends on which domain is associated with the grid
+  # being configured and using variable names complicates a lot of things).
+  # Instead, just tell morph how to reach the node here - by using its fully
+  # qualified domain name.
+  deployment.targetHost = "${config.networking.hostName}.${config.networking.domain}";
+
   deployment.secrets = {
     "ristretto-signing-key".source = ristrettoSigningKeyPath;
     "stripe-secret-key".source = stripeSecretKeyPath;
@@ -51,6 +69,8 @@
     "monitoringvpn-preshared-key".source = "${monitoringvpnKeyDir}/preshared.key";
   };
 
+  networking.domain = domain;
+
   services.private-storage.sshUsers = sshUsers;
   services.private-storage.monitoring.vpn.client = {
     enable = true;
diff --git a/morph/lib/customize-monitoring.nix b/morph/lib/customize-monitoring.nix
index 331a2dbd7c9875acbf72ae22da786f4bc868cfc0..c50eb5062b35480d0b3d296cfaea8abd999f36c5 100644
--- a/morph/lib/customize-monitoring.nix
+++ b/morph/lib/customize-monitoring.nix
@@ -11,9 +11,7 @@
   # See ``customize-issuer.nix``.
 , monitoringvpnKeyDir
 , monitoringvpnIPv4
-
-  # XXX To be removed
-, publicIPv4
+, domain
 
   # A list of VPN IP addresses as strings indicating which clients will be
   # allowed onto the VPN.
@@ -30,12 +28,17 @@
   # A string giving the NixOS state version for the system.
 , stateVersion
 , ...
-}: {
-  deployment.targetHost = publicIPv4;
+}:
+{ config, ... }: {
+  # See customize-issuer.nix for an explanatoin of targetHost value.
+  deployment.targetHost = "${config.networking.hostName}.${config.networking.domain}";
+
   deployment.secrets = {
     "monitoringvpn-private-key".source = "${monitoringvpnKeyDir}/server.key";
     "monitoringvpn-preshared-key".source = "${monitoringvpnKeyDir}/preshared.key";
   };
+
+  networking.domain = domain;
   networking.hosts = hostsMap;
 
   services.private-storage.monitoring.vpn.server = {
diff --git a/morph/lib/customize-storage.nix b/morph/lib/customize-storage.nix
index b9d25e985977bc77d08c21fd869644ab170c3b64..0a08743633126b5898e61a877e62a7b58314b34e 100644
--- a/morph/lib/customize-storage.nix
+++ b/morph/lib/customize-storage.nix
@@ -7,6 +7,7 @@
 , monitoringvpnEndpoint
 , monitoringvpnIPv4
 , sshUsers
+, domain
 
   # An integer giving the value of a single pass in byte×months.
 , passValue
@@ -15,21 +16,24 @@
   # advertisements and on which to listen for storage connections.
 , publicStoragePort
 
-  # XXX To be removed
-, publicIPv4
-
   # A string giving the NixOS state version for the system.
 , stateVersion
 , ...
-}: {
+}:
+{ config, ... }: {
+  # See customize-issuer.nix for an explanatoin of targetHost value.
+  deployment.targetHost = "${config.networking.hostName}.${config.networking.domain}";
+
   deployment.secrets = {
     "ristretto-signing-key".source = ristrettoSigningKeyPath;
     "monitoringvpn-secret-key".source = "${monitoringvpnKeyDir}/${monitoringvpnIPv4}.key";
     "monitoringvpn-preshared-key".source = "${monitoringvpnKeyDir}/preshared.key";
   };
 
+  networking.domain = domain;
+
   services.private-storage = {
-    inherit sshUsers publicIPv4 passValue publicStoragePort;
+    inherit sshUsers passValue publicStoragePort;
   };
 
   services.private-storage.monitoring.vpn.client = {
diff --git a/nixos/modules/private-storage.nix b/nixos/modules/private-storage.nix
index 52720e618973c57b41aade87585c7ab758abff22..38e224709e783b9590de73d728e4d6ca134e5adb 100644
--- a/nixos/modules/private-storage.nix
+++ b/nixos/modules/private-storage.nix
@@ -18,6 +18,12 @@ let
   # NOTE: This is promised by the service privacy policy.  It *may not* be
   # raised without following the process for updating the privacy policy.
   max-incident-age = "29d";
+
+  fqdn = "${
+    assert config.networking.hostName != null; config.networking.hostName
+    }.${
+    assert config.networking.domain != null; config.networking.domain
+    }";
 in
 {
   imports = [
@@ -38,12 +44,13 @@ in
         The package to use for the Tahoe-LAFS daemon.
       '';
     };
-    services.private-storage.publicIPv4 = lib.mkOption
-    { default = "127.0.0.1";
+    services.private-storage.publicAddress = lib.mkOption
+    { default = "${fqdn}";
       type = lib.types.str;
-      example = lib.literalExample "192.0.2.0";
+      example = lib.literalExample "storage.example.invalid";
       description = ''
-        An IPv4 address to advertise for this storage service.
+        A publicly-visible address to use in Tahoe-LAFS advertisements for
+        this storage service.
       '';
     };
     services.private-storage.introducerFURL = lib.mkOption
@@ -63,7 +70,7 @@ in
       '';
     };
     services.private-storage.issuerRootURL = lib.mkOption
-    { default = "https://issuer.privatestorage.io/";
+    { default = "https://issuer.${config.networking.domain}/";
       type = lib.types.str;
       example = lib.literalExample "https://example.invalid/";
       description = ''
@@ -122,7 +129,7 @@ in
           # First, in the syntax which it uses to listen.
           "tub.port" = "tcp:${toString cfg.publicStoragePort}";
           # Second, in the syntax it advertises to in the fURL.
-          "tub.location" = "tcp:${cfg.publicIPv4}:${toString cfg.publicStoragePort}";
+          "tub.location" = "tcp:${cfg.publicAddress}:${toString cfg.publicStoragePort}";
         };
         storage =
         { enabled = true;
diff --git a/nixos/modules/tests/private-storage.nix b/nixos/modules/tests/private-storage.nix
index cbf4c5937ca6780ce9e931d6ceec91c29643fbc3..353abc891fafd1cc988e47a1befa530a012470dc 100644
--- a/nixos/modules/tests/private-storage.nix
+++ b/nixos/modules/tests/private-storage.nix
@@ -115,7 +115,7 @@ in {
         ];
         services.private-storage = {
           enable = true;
-          publicIPv4 = "storage";
+          publicAddress = "storage";
           introducerFURL = introducerFURL;
           issuerRootURL = issuerURL;
           inherit ristrettoSigningKeyPath;