diff --git a/docs/source/ops/README.rst b/docs/source/ops/README.rst
index 8007d8dfbb8a42d27aed4fb014423b7f91742252..b78e5ef82c0ae4dad88e65e9517f3fc6ec7bdfd2 100644
--- a/docs/source/ops/README.rst
+++ b/docs/source/ops/README.rst
@@ -9,3 +9,5 @@ This contains documentation regarding running PrivateStorageio.
 .. include::
       monitoring.rst
 
+.. include::
+      generating-keys.rst
diff --git a/docs/source/ops/generating-keys.rst b/docs/source/ops/generating-keys.rst
new file mode 100644
index 0000000000000000000000000000000000000000..afe2ece4009f761aea56acd24fcbf627b985cadb
--- /dev/null
+++ b/docs/source/ops/generating-keys.rst
@@ -0,0 +1,50 @@
+Generating keys
+===============
+
+``config.json`` has the paths for the Ristretto and the Stripe secret key files.
+
+Here is a Ristretto key you can use, randomly generated just now::
+
+  SILOWzbnkBjxC1hGde9d5Q3Ir/4yLosCLEnEQGAxEQE=
+
+Generate your own like this::
+
+  [flo@la:~/PrivateStorageio]$ nix-shell
+  [nix-shell:~/PrivateStorageio]$ nix-shell -p zkapissuer.components.exes.PaymentServer-generate-key
+  [nix-shell:~/PrivateStorageio]$ PaymentServer-generate-key
+  SILOWzbnkBjxC1hGde9d5Q3Ir/4yLosCLEnEQGAxEQE=
+
+Make sure you write it into the key file `without any leading or trailing white space, also without newlines <https://github.com/LeastAuthority/python-challenge-bypass-ristretto/issues/37>`_.
+For example::
+
+  echo -n "SILOWzbnkBjxC1hGde9d5Q3Ir/4yLosCLEnEQGAxEQE=" > ristretto.signing-key
+
+For the Stripe key any random bytes with a little light formatting "work" - at least to make our software happy - but if you want to be able to interact with Stripe and have payments (even pretend payments) move all the way through the system you should get a Stripe account and generate a key w/ them.
+Lauri can get you added to our "dev" Stripe account, too, though I forget how important that is for ad hoc dev/testing.
+
+I think this will work for generating random Stripe secret keys (that our software will load, I think, but Stripe will reject)::
+
+  >>> import base64, os
+  >>> print((b"sk_test_" + base64.b64encode(os.urandom(25)).strip(b"=")).decode("ascii"))
+  sk_test_Dr+XLVjkC0oO3Zw8Ws0yWtDLqR1sM+/fmw
+
+Public keys are the same but "pk_test" instead of "sk_test" ("test" is for "test mode" key that can only process pretend txns; for real txns there are keys with "live" embedded).
+
+The ZKAPIssuer.service needs a working TLS certificate and expects it in the certbot directory for the domain you configured, in my case::
+
+  openssl req -x509 -newkey rsa:4096 -nodes -keyout privkey.pem -out cert.pem -days 3650
+  touch chain.pem
+
+Move the three .pem files into the payment's server ``/var/lib/letsencrypt/live/payments.localdev/`` directory and issue a ``sudo systemctl restart zkapissuer.service``.
+
+Create Wireguard VPN key pairs in ``PrivateStorageSecrets/monitoringvpn/`` or where you have them::
+
+  for i in "172.23.23.11" "172.23.23.12" "172.23.23.13" "server"; do
+    wg genkey | tee ${i}.key | wg pubkey > ${i}.pub
+  done
+
+And a shared VPN key for "post-quantum resistance"::
+
+  wg genpsk > preshared.key
+
+
diff --git a/morph/grid/local/README.rst b/morph/grid/local/README.rst
index 8887c297bc0e2aa300e27f86ff7a0f08535028a5..38981f75bfb82dbac31269f8abdcd086ca7e4c8d 100644
--- a/morph/grid/local/README.rst
+++ b/morph/grid/local/README.rst
@@ -33,46 +33,6 @@ If you run an older Nixpkgs, retrieve and use the latest Vagrant development ver
   NIX_PATH=nixpkgs=https://github.com/NixOS/nixpkgs/archive/refs/heads/master.tar.gz nix-shell -p vagrant
 
 
-Generating and deploying keys
-`````````````````````````````
-
-``config.json`` has the paths for the Ristretto and the Stripe secret key files.
-
-Here is a Ristretto key you can use, randomly generated just now::
-
-  SILOWzbnkBjxC1hGde9d5Q3Ir/4yLosCLEnEQGAxEQE=
-
-Generate your own like this::
-
-  [flo@la:~/PrivateStorageio]$ nix-shell
-  [nix-shell:~/PrivateStorageio]$ nix-shell -p zkapissuer.components.exes.PaymentServer-generate-key
-  [nix-shell:~/PrivateStorageio]$ PaymentServer-generate-key
-  SILOWzbnkBjxC1hGde9d5Q3Ir/4yLosCLEnEQGAxEQE=
-
-Make sure you write it into the key file `without any leading or trailing white space, also without newlines <https://github.com/LeastAuthority/python-challenge-bypass-ristretto/issues/37>`_.
-For example::
-
-  echo -n "SILOWzbnkBjxC1hGde9d5Q3Ir/4yLosCLEnEQGAxEQE=" > ristretto.signing-key
-
-For the Stripe key any random bytes with a little light formatting "work" - at least to make our software happy - but if you want to be able to interact with Stripe and have payments (even pretend payments) move all the way through the system you should get a Stripe account and generate a key w/ them.
-Lauri can get you added to our "dev" Stripe account, too, though I forget how important that is for ad hoc dev/testing.
-
-I think this will work for generating random Stripe secret keys (that our software will load, I think, but Stripe will reject)::
-
-  >>> import base64, os
-  >>> print((b"sk_test_" + base64.b64encode(os.urandom(25)).strip(b"=")).decode("ascii"))
-  sk_test_Dr+XLVjkC0oO3Zw8Ws0yWtDLqR1sM+/fmw
-
-Public keys are the same but "pk_test" instead of "sk_test" ("test" is for "test mode" key that can only process pretend txns; for real txns there are keys with "live" embedded).
-
-The ZKAPIssuer.service needs a working TLS certificate and expects it in the certbot directory for the domain you configured, in my case::
-
-  openssl req -x509 -newkey rsa:4096 -nodes -keyout privkey.pem -out cert.pem -days 3650
-  touch chain.pem
-
-Move the three .pem files into the payment's server ``/var/lib/letsencrypt/live/payments.localdev/`` directory and issue a ``sudo systemctl restart zkapissuer.service``.
-
-
 Use the local development environment
 `````````````````````````````````````
 
diff --git a/morph/grid/local/config.json b/morph/grid/local/config.json
index f4273dc5710d5a5bcff78acd219c850d55a17cd5..197ffd69ab30b8059226eb624005e408f71187b5 100644
--- a/morph/grid/local/config.json
+++ b/morph/grid/local/config.json
@@ -1,7 +1,7 @@
 { "publicStoragePort": 8898
 , "ristrettoSigningKeyPath": "../../PrivateStorageSecrets/ristretto.signing-key"
 , "stripeSecretKeyPath": "../../PrivateStorageSecrets/privatestorageio-testing-stripe.secret"
-, "monitoringvpnSecretKeyPath": "../../PrivateStorageSecrets/monitoringvpn/${monitoringvpnIPv4}.key"
+, "monitoringvpnSecretKeyDir": "../../PrivateStorageSecrets/monitoringvpn"
 , "monitoringvpnPresharedKeyPath" : "../../PrivateStorageSecrets/monitoringvpn/preshared.key"
 , "passValue": 1000000
 , "issuerDomain": "payments.localdev"
diff --git a/morph/lib/make-issuer.nix b/morph/lib/make-issuer.nix
index 039e426807386cafc06453a470cb4e7e4b10c674..cbaafd9cf27b1f9c20388789e40e1e13324d1d71 100644
--- a/morph/lib/make-issuer.nix
+++ b/morph/lib/make-issuer.nix
@@ -1,7 +1,7 @@
 { hardware
 , ristrettoSigningKeyPath
 , stripeSecretKeyPath
-, monitoringvpnSecretKeyPath
+, monitoringvpnSecretKeyDir
 , monitoringvpnPresharedKeyPath
 , issuerDomain
 , letsEncryptAdminEmail
@@ -33,7 +33,7 @@
         action = ["sudo" "systemctl" "restart" "zkapissuer.service"];
       };
       "monitoringvpn-secret-key" = {
-        source = monitoringvpnSecretKeyPath;
+        source = monitoringvpnSecretKeyDir + "/${monitoringvpnIPv4}.key";
         destination = "/run/keys/monitoringvpn/client.key";
         owner.user = "root";
         owner.group = "root";
diff --git a/morph/lib/make-monitoring.nix b/morph/lib/make-monitoring.nix
index 4c1e6916399a4ff82cec185fa6c4931ec2f466ee..5fce2735396b009e4084f2a69718f51393c791ae 100644
--- a/morph/lib/make-monitoring.nix
+++ b/morph/lib/make-monitoring.nix
@@ -2,7 +2,7 @@
 , hardware
 , publicStoragePort
 , ristrettoSigningKeyPath
-, monitoringvpnSecretKeyPath
+, monitoringvpnSecretKeyDir
 , monitoringvpnPresharedKeyPath
 , passValue
 , sshUsers
@@ -30,7 +30,7 @@ rec {
 
     secrets = {
       "monitoringvpn-private-key" = {
-        source = monitoringvpnSecretKeyPath;
+        source = monitoringvpnSecretKeyDir + "/server.key";
         destination = "/run/keys/monitoringvpn/server.key";
         owner.user = "root";
         owner.group = "root";
diff --git a/morph/lib/make-testing.nix b/morph/lib/make-testing.nix
index 1eb39ab550b640e44de233ed6cd54d574d7e40f8..26400097b744dae7c1a70d1c8211835301ebfddb 100644
--- a/morph/lib/make-testing.nix
+++ b/morph/lib/make-testing.nix
@@ -2,7 +2,7 @@
 , hardware
 , publicStoragePort
 , ristrettoSigningKeyPath
-, monitoringvpnSecretKeyPath
+, monitoringvpnSecretKeyDir
 , monitoringvpnPresharedKeyPath
 , passValue
 , sshUsers
@@ -26,7 +26,7 @@
         action = ["sudo" "systemctl" "restart" "tahoe.storage.service"];
       };
       "monitoringvpn-secret-key" = {
-        source = monitoringvpnSecretKeyPath;
+        source = monitoringvpnSecretKeyDir + "/${monitoringvpnIPv4}.key";
         destination = "/run/keys/monitoringvpn/client.key";
         owner.user = "root";
         owner.group = "root";
diff --git a/nixos/modules/monitoring/vpn/client.nix b/nixos/modules/monitoring/vpn/client.nix
index 5ea27cd900e6b90f704a57b8d6437c8b1b92a0ee..58991d0532106fad4c417ca09c52abb0aafa664e 100644
--- a/nixos/modules/monitoring/vpn/client.nix
+++ b/nixos/modules/monitoring/vpn/client.nix
@@ -12,6 +12,8 @@ in {
       default = /run/keys/monitoringvpn/client.key;
       description = ''
         File with base64 private key generated by <command>wg genkey</command>.
+        Shorthand to create private and public key:
+        <command>wg genkey | tee peer_A.key | wg pubkey > peer_A.pub</command>
       '';
     };
     presharedKeyFile = lib.mkOption {
@@ -40,15 +42,15 @@ in {
     endpoint = lib.mkOption {
       type = lib.types.str;
       example = lib.literalExample "vpn.monitoring.private.storage:54321";
-      default = "192.168.67.24:54321";
+      default = "192.168.67.24:51820";
       description = ''
         The address and port number of the server to establish the VPN with.
       '';
     };
     endpointPublicKeyFile = lib.mkOption {
       type = lib.types.path;
-      example = lib.literalExample ../../PrivateStorageSecrets/monitoringvpn/server.pub;
-      default = ../../../../morph/PrivateStorageSecrets/monitoringvpn/server.pub;
+      example = lib.literalExample ../PrivateStorageSecrets/monitoringvpn/server.pub;
+      default = ../../../../../PrivateStorageSecrets/monitoringvpn/server.pub;
       description = ''
         File with base64 public key generated by <command>cat private.key | wg pubkey > pubkey.pub</command>.
       '';
diff --git a/nixos/modules/monitoring/vpn/server.nix b/nixos/modules/monitoring/vpn/server.nix
index 0cc7be1ec47a7936d3e405f024725025d4f77f24..b7f8c00cf74b961ac2e2c4228f824ae8f933b0e5 100644
--- a/nixos/modules/monitoring/vpn/server.nix
+++ b/nixos/modules/monitoring/vpn/server.nix
@@ -2,11 +2,11 @@
 
 { lib, config, ... }: let
   cfg = config.services.private-storage.monitoring.vpn;
-  makePeers = map (x: {
-                allowedIPs = [ "${x}/32" ];
-                publicKey = lib.fileContents(cfg.server.pubKeysPath + "/${x}.pub");
-                presharedKeyFile = toString cfg.server.presharedKeyFile;
-              }) cfg.server.vpnClientIPs;
+  clients = map (x: {
+              allowedIPs = [ "${x}/32" ];
+              publicKey = lib.fileContents(cfg.server.pubKeysPath + "/${x}.pub");
+              presharedKeyFile = toString cfg.server.presharedKeyFile;
+            }) cfg.server.vpnClientIPs;
 
 in {
   options.services.private-storage.monitoring.vpn.server = {
@@ -37,7 +37,7 @@ in {
     port = lib.mkOption {
       type = lib.types.port;
       example = lib.literalExample 54321;
-      default = 54321;
+      default = 51820;
       description = ''
         The UDP port to listen on.
       '';
@@ -51,8 +51,8 @@ in {
     };
     pubKeysPath = lib.mkOption {
       type = lib.types.path;
-      example = lib.literalExample ../../../../morph/PrivateStorageSecrets/monitoringvpn;
-      default = ../../../../morph/PrivateStorageSecrets/monitoringvpn;
+      example = lib.literalExample ../PrivateStorageSecrets/monitoringvpn;
+      default = ../../../../../PrivateStorageSecrets/monitoringvpn;
       description = ''
         The path to the directory that holds the public keys.
       '';
@@ -66,7 +66,7 @@ in {
       ips = [ "${cfg.server.ip}/24" ];
       listenPort = cfg.server.port;
       privateKeyFile = toString cfg.server.privateKeyFile;
-      peers = makePeers;
+      peers = clients;
     };
   };
 }