diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 205bedaf0b86d05a8f87ffcf368d6024d2fd57cb..b91f7d5f175a32a49a5ed0788a87146d9b86c3f3 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -23,6 +23,7 @@ unit-tests:
 vulnerability-scan:
   stage: "test"
   script:
+    - "sed -i 's/undefined/\"unundefined\"/' morph/grid/local/secrets/users.nix"
     - "ci-tools/vulnerability-scan security-report.json"
     - "ci-tools/count-vulnerabilities <security-report.json"
   artifacts:
diff --git a/ci-tools/vulnerability-scan b/ci-tools/vulnerability-scan
index 48bf51e071a398f37565717a22b2066d3f905fbe..3162e49511697ed0ea13e0121a67336405ce5225 100755
--- a/ci-tools/vulnerability-scan
+++ b/ci-tools/vulnerability-scan
@@ -21,7 +21,7 @@ OUTPUT=$1
 [ -e scan-target ] && rm -v scan-target
 nix-shell --run '
 set -x
-if morph_result=$(morph build morph/grid/testing/grid.nix 2>&1); then
+if morph_result=$(morph build morph/grid/local/grid.nix 2>&1); then
   object=$(echo "$morph_result" | tail -n 1)
   ln -s "$object" scan-target
 else
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/README.rst b/morph/README.rst
index 1f48d5e0ad30b1f44a1a2cee8c5aa6f0669bcc75..12472518ad8e061764d6812694c306e87553c843 100644
--- a/morph/README.rst
+++ b/morph/README.rst
@@ -55,6 +55,10 @@ This path is **ignored** by git.
 The intended workflow is that the secrets will be maintained on secure storage and a symlink to the correct location created here.
 This keeps the secrets themselves out of the git working tree as an extra protection against unintentionally committing them.
 
+An exception is the ``secrets`` directory in the ``local`` morph grid:
+That directory is fully populated, provided as an example, and mostly: not very secret.
+Do not deploy these keys to machines reachable via the internet.
+
 config.json
 ~~~~~~~~~~~
 
diff --git a/morph/grid/local/.gitignore b/morph/grid/local/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..8000dd9db47c0b9dd34046ec17880dcbb27e5eb9
--- /dev/null
+++ b/morph/grid/local/.gitignore
@@ -0,0 +1 @@
+.vagrant
diff --git a/morph/grid/local/README.rst b/morph/grid/local/README.rst
index 8887c297bc0e2aa300e27f86ff7a0f08535028a5..73bfbbdd2a11922fe696161fe8346d7e10157313 100644
--- a/morph/grid/local/README.rst
+++ b/morph/grid/local/README.rst
@@ -1,111 +1,51 @@
 Set up and use a network of local development VMs
 -------------------------------------------------
 
-... using `Vagrant <https://www.vagrantup.com/>`_ to manage VirtualBox VMs [#]_.
-To get started, first install Vagrant and make sure it works.
-One possible way to do it in NixOS:
+... using `Vagrant <https://www.vagrantup.com/>`_ to manage VirtualBox VMs.
+(The author of this documentation wasted a lot of time trying to get Vagrant to work with KVM/libvirt.
+Issues with networking that looked like guest misconfigurations vanished after changing to the better-tested combination of Vagrant and VirtualBox.)
 
-1. Install Vagrant, by adding the packages:
 
-  - ``vagrant`` (orchestrating virtual machines on the command line)
-     - Only use when version >= 2.2.16 has become available.  Else see below.
-  - Optional: ``packer`` (for creating your own VM images)
-
-2. Add configuration to install and enable VirtualBox:
-
-  - ``virtualisation.virtualbox.host.enable = true;``
-
-3. Add your user to the ``vboxusers`` group, for example:
-
-  - ``users.extraGroups.vboxusers.members = [ "flo" "jp" ];``
-
-
-.. [#] The author of this documentation wasted a lot of time trying to get Vagrant to work with KVM/libvirt.  Issues with networking that looked like guest misconfigurations vanished after changing to the better-tested combination of Vagrant and VirtualBox.
-
-
-Pre-Vagrant 2.2.16: Get Vagrant with the required fixes for NixOS guests
-````````````````````````````````````````````````````````````````````````
-
-The Vagrant nixos-guest template `received a critical update on 2021-03-08 <https://github.com/hashicorp/vagrant/commit/990d94ed9d0b3092e855bc1bb9deeeb7aa7792cf>`_ which came out with Vagrant version 2.2.16.
-
-If you run an older Nixpkgs, retrieve and use the latest Vagrant development version like so::
-
-  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
+Use the local development environment
+`````````````````````````````````````
 
-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).
+1. Enter the morph local grid directory::
 
-The ZKAPIssuer.service needs a working TLS certificate and expects it in the certbot directory for the domain you configured, in my case::
+    cd morph/grid/local
 
-  openssl req -x509 -newkey rsa:4096 -nodes -keyout privkey.pem -out cert.pem -days 3650
-  touch chain.pem
+2. Enter the project's nix-shell::
 
-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``.
+    nix-shell ../../../shell.nix
 
-
-Use the local development environment
-`````````````````````````````````````
-
-1. Build and start the VMs::
+3. Build and start the VMs::
 
     VAGRANT_DEFAULT_PROVIDER=virtualbox vagrant up
 
-2. Then, once::
+4. Then, add the Vagrant SSH configuration to your user's ``~/.ssh/config`` file::
 
-    vagrant ssh-config > ./vagrant-ssh-config
+    install -d ~/.ssh ; vagrant ssh-config >> ~/.ssh/config
 
-3. Edit the output: Add the IPs from ``grid.nix`` to the ``vagrant-ssh-config`` **Host match blocks** so the config reads like::
+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 payments1 192.168.67.21
-      HostName 192.168.67.21
+      HostName 127.0.0.1
       User vagrant
-      Port 22
       [...]
 
-4.  Then, make morph use this ssh config either - with newer morph [#]_ - by pointing it to it::
-
-     export SSH_CONFIG_FILE=./vagrant-ssh-config
+  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.
 
-  Or, with older morph, adding the config to your user's ``~/.ssh/config`` file.
+6. Add your SSH key to ``users.nix`` so you'll be able to log in after deploying the new configuration::
 
-  .. [#]  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>`_.
+    $EDITOR secrets/users.nix
 
-5. Then, build and deploy our software to the Vagrant VMs::
+7. Then, build and deploy our software to the Vagrant VMs::
 
     morph build grid.nix
     morph push grid.nix
+    morph deploy grid.nix boot
+    vagrant halt
+    vagrant up
     morph upload-secrets grid.nix
-    morph deploy grid.nix switch
 
-  You will now be able to log in with the users and keys you set in your ``localdev-users.nix`` file.
+  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 81dec21ee701d761b4347f11df43398d78d4b528..82bbef1063b108829261670fdceb2e27af8d6764 100644
--- a/morph/grid/local/Vagrantfile
+++ b/morph/grid/local/Vagrantfile
@@ -14,6 +14,10 @@ Vagrant.configure("2") do |config|
     config.vm.box_version = "20.09"
     config.vm.box_check_update = false
     config.vm.network "private_network", ip: "192.168.67.21"
+    # Add self signed SSL key for zkap-issuer:
+    config.vm.provision "file", source: "secrets/payments-localdev-ssl", destination: "/tmp/payments-localdev-ssl"
+    config.vm.provision "shell", inline: "sudo mkdir -p /var/lib/letsencrypt/live/payments.localdev/"
+    config.vm.provision "shell", inline: "sudo mv /tmp/payments-localdev-ssl/* /var/lib/letsencrypt/live/payments.localdev/"
   end
 
   config.vm.define "storage1" do |config|
@@ -32,6 +36,14 @@ Vagrant.configure("2") do |config|
     config.vm.network "private_network", ip: "192.168.67.23"
   end
 
+  config.vm.define "monitoring1" do |config|
+    config.vm.hostname = "monitoring1"
+    config.vm.box = "esselius/nixos"
+    config.vm.box_version = "20.09"
+    config.vm.box_check_update = false
+    config.vm.network "private_network", ip: "192.168.67.24"
+  end
+
   # To make the VMs assign the static IPs to the network interfaces we need a rebuild:
   config.vm.provision "shell", inline: "echo '{nix.trustedUsers = [ \"@wheel\" \"root\" \"vagrant\" ];}' > /etc/nixos/custom-configuration.nix"
   config.vm.provision "shell", inline: "nixos-rebuild switch"
diff --git a/morph/grid/local/config.json b/morph/grid/local/config.json
index e970c3c6c29498497e5fad19d04ed56a56471ec1..38f00367bf2fa36ad7663c89f7849146783b8515 100644
--- a/morph/grid/local/config.json
+++ b/morph/grid/local/config.json
@@ -1,6 +1,8 @@
 { "publicStoragePort": 8898
-, "ristrettoSigningKeyPath": "../../PrivateStorageSecrets/ristretto.signing-key"
-, "stripeSecretKeyPath": "../../PrivateStorageSecrets/privatestorageio-testing-stripe.secret"
+, "ristrettoSigningKeyPath": "./secrets/ristretto.signing-key"
+, "stripeSecretKeyPath": "./secrets/stripe.secret"
+, "monitoringvpnKeyDir": "./secrets/monitoringvpn"
+, "monitoringvpnEndpoint": "192.168.67.24:51820"
 , "passValue": 1000000
 , "issuerDomains": ["payments.localdev"]
 , "letsEncryptAdminEmail": "florian@privatestorage.io"
diff --git a/morph/grid/local/grid.nix b/morph/grid/local/grid.nix
index fdda12e4537cd281a8d0768f0a95aa0608eca36b..a762186d3aaad642fe24aaf7666853fde79986b3 100644
--- a/morph/grid/local/grid.nix
+++ b/morph/grid/local/grid.nix
@@ -6,27 +6,46 @@ import ../../lib/make-grid.nix {
   config = ./config.json;
   nodes = cfg:
   let
-    sshUsers = import ../../../../PrivateStorageSecrets/localdev-users.nix;
+    sshUsers = import ./secrets/users.nix;
+    vpnClientIPs = [ "172.23.23.11" "172.23.23.12" "172.23.23.13" ]; # TBD: derive automatically
+    # Get absolute vpn key directory path, as a string:
+    monitoringvpnKeyDir = toString ./. + "/${cfg.monitoringvpnKeyDir}";
   in {
-    "payments1" = import ../../lib/make-issuer.nix (rec {
+    "payments1" = import ../../lib/make-issuer.nix (cfg // rec {
       publicIPv4 = "192.168.67.21";
+      monitoringvpnIPv4 = "172.23.23.11";
+      inherit monitoringvpnKeyDir;
       inherit sshUsers;
       hardware = import ./virtual-hardware.nix ({ inherit publicIPv4; });
       stateVersion = "19.03";
-    } // cfg);
+    });
 
-    "storage1" = import ../../lib/make-testing.nix (rec {
+    "storage1" = import ../../lib/make-testing.nix (cfg // rec {
       publicIPv4 = "192.168.67.22";
+      monitoringvpnIPv4 = "172.23.23.12";
+      inherit monitoringvpnKeyDir;
       inherit sshUsers;
       hardware = import ./virtual-hardware.nix ({ inherit publicIPv4; });
       stateVersion = "19.09";
-    } // cfg);
+    });
 
-    "storage2" = import ../../lib/make-testing.nix (rec {
+    "storage2" = import ../../lib/make-testing.nix (cfg // rec {
       publicIPv4 = "192.168.67.23";
+      monitoringvpnIPv4 = "172.23.23.13";
+      inherit monitoringvpnKeyDir;
       inherit sshUsers;
       hardware = import ./virtual-hardware.nix ({ inherit publicIPv4; });
       stateVersion = "19.09";
-    } // cfg);
+    });
+
+    "monitoring1" = import ../../lib/make-monitoring.nix (cfg // rec {
+      publicIPv4 = "192.168.67.24";
+      monitoringvpnIPv4 = "172.23.23.1";
+      inherit vpnClientIPs;
+      inherit sshUsers;
+      inherit monitoringvpnKeyDir;
+      hardware = import ./virtual-hardware.nix ({ inherit publicIPv4; });
+      stateVersion = "19.09";
+    });
   };
 }
diff --git a/morph/grid/local/secrets/monitoringvpn/172.23.23.11.key b/morph/grid/local/secrets/monitoringvpn/172.23.23.11.key
new file mode 100644
index 0000000000000000000000000000000000000000..22f11b7bc230a1a1b08d68850caa375171cc386d
--- /dev/null
+++ b/morph/grid/local/secrets/monitoringvpn/172.23.23.11.key
@@ -0,0 +1 @@
+cLP62YAYoA7FY+OhSLR64DIHekOjGGQlfJAWp5cYP00=
diff --git a/morph/grid/local/secrets/monitoringvpn/172.23.23.11.pub b/morph/grid/local/secrets/monitoringvpn/172.23.23.11.pub
new file mode 100644
index 0000000000000000000000000000000000000000..44c0d84b7ad9c50c75c379fd7589dd33370b3e58
--- /dev/null
+++ b/morph/grid/local/secrets/monitoringvpn/172.23.23.11.pub
@@ -0,0 +1 @@
+GYNjLkoyQ1d3OMymYbgq40WAHIUzrSEGBWXvxqceF00=
diff --git a/morph/grid/local/secrets/monitoringvpn/172.23.23.12.key b/morph/grid/local/secrets/monitoringvpn/172.23.23.12.key
new file mode 100644
index 0000000000000000000000000000000000000000..e717bf7ec42129926ceae6c7a44372738162378e
--- /dev/null
+++ b/morph/grid/local/secrets/monitoringvpn/172.23.23.12.key
@@ -0,0 +1 @@
+qFjBtvJKBchzl2HwFvEDoe3zFzyc10osiRlP8HOk2n0=
diff --git a/morph/grid/local/secrets/monitoringvpn/172.23.23.12.pub b/morph/grid/local/secrets/monitoringvpn/172.23.23.12.pub
new file mode 100644
index 0000000000000000000000000000000000000000..18110f20c3bb734fd229f1bd7f87f37050130b6a
--- /dev/null
+++ b/morph/grid/local/secrets/monitoringvpn/172.23.23.12.pub
@@ -0,0 +1 @@
+veio/0E0sJYOjwp3E8EccCyME1pqjkZr4R6whFMdrhs=
diff --git a/morph/grid/local/secrets/monitoringvpn/172.23.23.13.key b/morph/grid/local/secrets/monitoringvpn/172.23.23.13.key
new file mode 100644
index 0000000000000000000000000000000000000000..6dd5087e19e8e9e97f3b459a30563555d10de155
--- /dev/null
+++ b/morph/grid/local/secrets/monitoringvpn/172.23.23.13.key
@@ -0,0 +1 @@
+8HlKTvxZBAZeww6JaNk9kBPjSfT0pVMbDJbzV67yUGE=
diff --git a/morph/grid/local/secrets/monitoringvpn/172.23.23.13.pub b/morph/grid/local/secrets/monitoringvpn/172.23.23.13.pub
new file mode 100644
index 0000000000000000000000000000000000000000..d80b7abbfa20982e69772e6cd66f20dea946582d
--- /dev/null
+++ b/morph/grid/local/secrets/monitoringvpn/172.23.23.13.pub
@@ -0,0 +1 @@
+4VlUMl9FubrLWaN0pRvfdNjjRBQzfCVLMA2lU7OwPzA=
diff --git a/morph/grid/local/secrets/monitoringvpn/preshared.key b/morph/grid/local/secrets/monitoringvpn/preshared.key
new file mode 100644
index 0000000000000000000000000000000000000000..4389e80504f524ecb9b8ee2e8248882da55a7097
--- /dev/null
+++ b/morph/grid/local/secrets/monitoringvpn/preshared.key
@@ -0,0 +1 @@
+E7KTLVnWMmP/mIEkU8WX2DBZJaeMS2+sYArRZuGT1o4=
diff --git a/morph/grid/local/secrets/monitoringvpn/server.key b/morph/grid/local/secrets/monitoringvpn/server.key
new file mode 100644
index 0000000000000000000000000000000000000000..01058684fdb2438b4bff23f0370939628c3ef18c
--- /dev/null
+++ b/morph/grid/local/secrets/monitoringvpn/server.key
@@ -0,0 +1 @@
+iOp2pk2HWyNgRnke7nJgFwodkTWMyHRIKwe8pk+bN3M=
diff --git a/morph/grid/local/secrets/monitoringvpn/server.pub b/morph/grid/local/secrets/monitoringvpn/server.pub
new file mode 100644
index 0000000000000000000000000000000000000000..188c4b4cdea11ca02add26cba104ce61af0ee4c1
--- /dev/null
+++ b/morph/grid/local/secrets/monitoringvpn/server.pub
@@ -0,0 +1 @@
+ojo+p9ZE03GN66ewoIlrHmyV7ICt+2LV32Prs66JsA4=
diff --git a/morph/grid/local/secrets/payments-localdev-ssl/cert.pem b/morph/grid/local/secrets/payments-localdev-ssl/cert.pem
new file mode 100644
index 0000000000000000000000000000000000000000..5300ed591899cb802f2ceff49ac5aa195bb87d12
--- /dev/null
+++ b/morph/grid/local/secrets/payments-localdev-ssl/cert.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDGTCCAgGgAwIBAgIUM9YnOMe2yYQuCLpYiI2TpZg21AMwDQYJKoZIhvcNAQEL
+BQAwHDEaMBgGA1UEAwwRcGF5bWVudHMubG9jYWxkZXYwHhcNMjEwNjA5MTQzNzU4
+WhcNMzEwNjA3MTQzNzU4WjAcMRowGAYDVQQDDBFwYXltZW50cy5sb2NhbGRldjCC
+ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOBCvkMprdRjD8ECeweMVTFT
+pILu5VA8HC8XK15Y4iE8G48cSYE3m/e6w5g4JvfXgED9eY+1/ZazQXVI6sojnfHL
+Vj9XqsUww3IVtq2Zn+0B8YTrbNxcDH77mUx0mUziAm1bglmoNbd4+q0Fe8FhK/EN
+Jj7UppRjD2ziV9Mw7UT1JjMbfSo7/7ZOV2cWk+hezOywsE+3BM6mju1aHbbYryAd
+EzRwfQKcI/9PC84Bck9e+tEiWQImBf8DguQ+ChuSOmGtP1rI+hq5HfCimH0NCCNS
+iUfNVcubz920FRBeAx9oM0G/u4feeS8T5a1PThZrdhjIycEmU5wsM7F34RzkuEkC
+AwEAAaNTMFEwHQYDVR0OBBYEFAbBfAFOlx4c53CNgsRlobFwUIBbMB8GA1UdIwQY
+MBaAFAbBfAFOlx4c53CNgsRlobFwUIBbMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI
+hvcNAQELBQADggEBACD+47l30gGaZBPvuCMc/CENLmuqZqj7WGGmWcmUAzsdEgHt
+Q1zhj4un4u4qQ78jeT+cjqNK9qartQIjxzYTpn6lKDBpM9sS2G7RdLxOWWRVAXQj
+Cv6l0tEQArIqqMnzmECe0VDCc97kTg6tnMLFeyQL29XSSNIpS0DiQ+NC69fyrF2E
+vt2bAi+QTlN1U1ZGDJqWlwqkpI3xTRDJnmRPuVNrt07gkMWeSJHEI98Qcve7Ujf3
+hFcAnbshJ8iXxbNjTFIQcCJzHq1UF+eZo8BF1ixYmrk/jmaLNBOSy93tOk1XeJfS
+XtV6X9jmAE20V0XV8uCli6cDxQQYthll/1q5JYQ=
+-----END CERTIFICATE-----
diff --git a/morph/grid/local/secrets/payments-localdev-ssl/chain.pem b/morph/grid/local/secrets/payments-localdev-ssl/chain.pem
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/morph/grid/local/secrets/payments-localdev-ssl/privkey.pem b/morph/grid/local/secrets/payments-localdev-ssl/privkey.pem
new file mode 100644
index 0000000000000000000000000000000000000000..36b699fab9e7dac880daf89ae3f9a7ea9ce732ef
--- /dev/null
+++ b/morph/grid/local/secrets/payments-localdev-ssl/privkey.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDgQr5DKa3UYw/B
+AnsHjFUxU6SC7uVQPBwvFyteWOIhPBuPHEmBN5v3usOYOCb314BA/XmPtf2Ws0F1
+SOrKI53xy1Y/V6rFMMNyFbatmZ/tAfGE62zcXAx++5lMdJlM4gJtW4JZqDW3ePqt
+BXvBYSvxDSY+1KaUYw9s4lfTMO1E9SYzG30qO/+2TldnFpPoXszssLBPtwTOpo7t
+Wh222K8gHRM0cH0CnCP/TwvOAXJPXvrRIlkCJgX/A4LkPgobkjphrT9ayPoauR3w
+oph9DQgjUolHzVXLm8/dtBUQXgMfaDNBv7uH3nkvE+WtT04Wa3YYyMnBJlOcLDOx
+d+Ec5LhJAgMBAAECggEARI3in6FkFCLcNAJQHbSWbmfFSIlC7E4Tx4lrpoHBTquT
+OSJKjgez0/zxwdyYfPcRq8xQls/pX2IYxoOt0nEk3T9tdBuWhoUrmfptR5BIxSjs
+7dcSBiLVZxP+ftK98jS8zTVGGaZEFXwUFUQx2qGbzypX4Kkc6wuFMaHXeyXfwk4j
+v3lOTpT7kX/3iVXzQU9Y5m45W1bbt3lSV4yCndOt/4Xg5d4ue6UcUsyg/r8m5wsU
+nCc14L0HsY/VCCRISmDwa4UoqHGfQv6+XXxLT8gJM7H8PkZK8TRV/ZG2Yug63wDS
+xkhMZBodGoYuQpB19Y+NxjwG8Se7RxcCAyS7zcYcCQKBgQD/VWesc2/TplikifQf
+LJl+utiQ9G/o3wAquAKInxQOcUxOm1fsvjgsLfrjpAVj0D3qD0Sf3lSp7z3tpUMf
+MSi9MgNunSU7pKST7jex6BJNU0TTRcFnoqcIlUYiDoLLbkkVlf8Nome88gkwJRKZ
+RjI+6ES9hGaM85QEgjdtRNGXIwKBgQDg2JPfVXYyrOIITus/DHHv8em1RuGCIe6X
+T3M0+GgJ27hOEeneAyzk4aWvVzE3/3lxx55ePGRAz43jPrUxn0kfy/Jx4UMKsEW+
+XzR7CfxTxamUx/mq2bbqk+bbnXZZQedAip4eQ23GbWdrE4/v83JXzWj355yUQCQk
+uuIcda7fowKBgEY20CmmHOxQ5DNrFEy2UQd+jitebJ/XIw6cR2YWiMdn9JnxMf6S
+WJQdmM6cvjayfzQsOqzT0OhiN99wAMNFG3TbmgIDCMgcAH4Flh9AODg3W8fVeNfs
+7I35rq2S2/jhPQvIkbjIHkrhLBGnQDQSD6Mo8C5FiIXePaf3vxI3SIONAoGAZdOv
+pEUf8nM5KmoTP8pzDyePn/kpx7V2SDBDDIozE8PeA/043MKzYjSOxInIUIPyjATL
+RAI1pORabb/Ib2CjzTKf6dMKeZy6+SxEqDQtggLSef7WovlWTYYN1wfIwUOHZ0Nf
+uHTxEhwZ6fRCC3lFH153W04ZK0qhE8FPBXSGbeECgYBcY0QmOd3ao3v0xscKmPlM
+E9MICgwgEJ6pAsaOI30OHqUAtlqrlw5ph3skxlLUpHWW6OUczUUV6nwguk4piuee
+1J9uuA02FlIvvUy5Bsrgqlu0Lj5vJ8Im2Go9dJ8k/m9J5BUgDdPuNwnmjiyNQurd
+Fl50FWx/3WeJB2qK981vmw==
+-----END PRIVATE KEY-----
diff --git a/morph/grid/local/secrets/ristretto.signing-key b/morph/grid/local/secrets/ristretto.signing-key
new file mode 100644
index 0000000000000000000000000000000000000000..8726b7060694499f61cc15ba826f3933b88795a5
--- /dev/null
+++ b/morph/grid/local/secrets/ristretto.signing-key
@@ -0,0 +1 @@
+NAQBkEEUKPDtq8af5anlHvWMjeSVoH56RnpCTy70QwA=
\ No newline at end of file
diff --git a/morph/grid/local/secrets/stripe.secret b/morph/grid/local/secrets/stripe.secret
new file mode 100644
index 0000000000000000000000000000000000000000..ebf3fdabcd222af9ca7ed46bf0b45a2fd18a603e
--- /dev/null
+++ b/morph/grid/local/secrets/stripe.secret
@@ -0,0 +1 @@
+sk_test_Dr+XLVjkC0oO3Zw8Ws0yWtDLqR1sM+/fmw
diff --git a/morph/grid/local/secrets/users.nix b/morph/grid/local/secrets/users.nix
new file mode 100644
index 0000000000000000000000000000000000000000..93a8b660c78fa12b1e20c6d560f78efb1b5684c7
--- /dev/null
+++ b/morph/grid/local/secrets/users.nix
@@ -0,0 +1,4 @@
+# Add your public key. Example: 
+# let key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHx7wJQNqKn8jOC4AxySRL2UxidNp7uIK9ad3pMb1ifF flo@fs-la";
+let key = undefined;
+in { "root" = key; "vagrant" = key; }
diff --git a/morph/grid/local/vagrant-guest.nix b/morph/grid/local/vagrant-guest.nix
index 8505b2f34dba067bf2c39a1645145bc626d30cf8..9e8e6d8ccab25d98d11738ff7df4a574c5cfd724 100644
--- a/morph/grid/local/vagrant-guest.nix
+++ b/morph/grid/local/vagrant-guest.nix
@@ -22,6 +22,9 @@ in
   # Enable the OpenSSH daemon.
   services.openssh.enable = true;
 
+  # Wireguard kernel module
+  boot.extraModulePackages = [ config.boot.kernelPackages.wireguard ];
+
   # Enable DBus
   services.dbus.enable    = true;
 
diff --git a/morph/grid/production/grid.nix b/morph/grid/production/grid.nix
index 69a17602f0499acaa8b45adca5e45c8acc637110..f5735d259dbff27f1d9cabbbca512af81d4550bb 100644
--- a/morph/grid/production/grid.nix
+++ b/morph/grid/production/grid.nix
@@ -6,7 +6,7 @@ import ../../lib/make-grid.nix {
   config = ./config.json;
   nodes = cfg:
     let
-      sshUsers = import ./users.nix;
+      sshUsers = import ./secrets/users.nix;
     in {
     # Here are the hosts that are in this morph network.  This is sort of like
     # a server manifest.  We try to keep as many of the specific details as
diff --git a/morph/grid/production/users.nix b/morph/grid/production/users.nix
deleted file mode 100644
index d3520076636b56c0b07055a135becaf6a77b798f..0000000000000000000000000000000000000000
--- a/morph/grid/production/users.nix
+++ /dev/null
@@ -1,2 +0,0 @@
-let key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGN4VQm3BIQKEFTw6aPrEwNuShf640N+Py2LOKznFCRT exarkun@bottom";
-in { "root" = key; jcalderone = key; }
diff --git a/morph/grid/testing/grid.nix b/morph/grid/testing/grid.nix
index 90acab60b7bf6a12f2c5ff3ff93cb93e479491aa..065cd5faa5a5e90a657d1fd1a38e79266e6b6475 100644
--- a/morph/grid/testing/grid.nix
+++ b/morph/grid/testing/grid.nix
@@ -6,7 +6,7 @@ import ../../lib/make-grid.nix {
   config = ./config.json;
   nodes = cfg:
   let
-    sshUsers = import ./users.nix;
+    sshUsers = import ./secrets/users.nix;
   in {
     "payments" = import ../../lib/make-issuer.nix ({
       publicIPv4 = "18.194.183.13";
diff --git a/morph/grid/testing/users.nix b/morph/grid/testing/users.nix
deleted file mode 100644
index d3520076636b56c0b07055a135becaf6a77b798f..0000000000000000000000000000000000000000
--- a/morph/grid/testing/users.nix
+++ /dev/null
@@ -1,2 +0,0 @@
-let key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGN4VQm3BIQKEFTw6aPrEwNuShf640N+Py2LOKznFCRT exarkun@bottom";
-in { "root" = key; jcalderone = key; }
diff --git a/morph/lib/make-issuer.nix b/morph/lib/make-issuer.nix
index 5625b565c452d1fc9c8bf84eb1720cecd197f124..58b8a4f20496472409c2063a2923bc29f161d68a 100644
--- a/morph/lib/make-issuer.nix
+++ b/morph/lib/make-issuer.nix
@@ -7,10 +7,37 @@
 , sshUsers
 , stateVersion
 , publicIPv4
+, monitoringvpnKeyDir ? null
+, monitoringvpnIPv4 ? null
+, monitoringvpnEndpoint ? null
 , ...
-}: rec {
+}: let
+
+  enableVpn = monitoringvpnKeyDir != null &&
+              monitoringvpnIPv4 != null &&
+              monitoringvpnEndpoint != null;
+
+  vpnSecrets = if !enableVpn then {} else {
+    "monitoringvpn-secret-key" = {
+      source = monitoringvpnKeyDir + "/${monitoringvpnIPv4}.key";
+      destination = "/run/keys/monitoringvpn/client.key";
+      owner.user = "root";
+      owner.group = "root";
+      permissions = "0400";
+      action = ["sudo" "systemctl" "restart" "wireguard-monitoringvpn.service"];
+    };
+    "monitoringvpn-preshared-key" = {
+      source = monitoringvpnKeyDir + "/preshared.key";
+      destination = "/run/keys/monitoringvpn/preshared.key";
+      owner.user = "root";
+      owner.group = "root";
+      permissions = "0400";
+      action = ["sudo" "systemctl" "restart" "wireguard-monitoringvpn.service"];
+    };
+  };
+
+in rec {
   deployment = {
-    targetUser = "root";
     targetHost = publicIPv4;
 
     secrets = {
@@ -30,12 +57,13 @@
         permissions = "0400";
         action = ["sudo" "systemctl" "restart" "zkapissuer.service"];
       };
-    };
+    } // vpnSecrets;
   };
 
   imports = [
     hardware
     ../../nixos/modules/issuer.nix
+    ../../nixos/modules/monitoring/vpn/client.nix
   ];
 
   services.private-storage.sshUsers = sshUsers;
@@ -52,4 +80,11 @@
   };
 
   system.stateVersion = stateVersion;
+
+  services.private-storage.monitoring.vpn.client = if !enableVpn then {} else {
+    enable = true;
+    ip = monitoringvpnIPv4;
+    endpoint = monitoringvpnEndpoint;
+    endpointPublicKeyFile = monitoringvpnKeyDir + "/server.pub";
+  };
 }
diff --git a/morph/lib/make-monitoring.nix b/morph/lib/make-monitoring.nix
new file mode 100644
index 0000000000000000000000000000000000000000..c37ea2297088fafba1b97e8d037c378505c3d84c
--- /dev/null
+++ b/morph/lib/make-monitoring.nix
@@ -0,0 +1,55 @@
+{ publicIPv4
+, hardware
+, publicStoragePort
+, ristrettoSigningKeyPath
+, passValue
+, sshUsers
+, stateVersion
+, monitoringvpnIPv4 ? null
+, monitoringvpnKeyDir ? null
+, vpnClientIPs ? null
+, ... }: let
+
+  enableVpn = monitoringvpnKeyDir != null &&
+              monitoringvpnIPv4 != null &&
+              vpnClientIPs != null;
+
+  vpnSecrets = if !enableVpn then {} else {
+    "monitoringvpn-private-key" = {
+      source = monitoringvpnKeyDir + "/server.key";
+      destination = "/run/keys/monitoringvpn/server.key";
+      owner.user = "root";
+      owner.group = "root";
+      permissions = "0400";
+      action = ["sudo" "systemctl" "restart" "wireguard-monitoringvpn.service"];
+    };
+    "monitoringvpn-preshared-key" = {
+      source = monitoringvpnKeyDir + "/preshared.key";
+      destination = "/run/keys/monitoringvpn/preshared.key";
+      owner.user = "root";
+      owner.group = "root";
+      permissions = "0400";
+      action = ["sudo" "systemctl" "restart" "wireguard-monitoringvpn.service"];
+    };
+  };
+in rec {
+
+  deployment = {
+    targetHost = publicIPv4;
+    secrets = vpnSecrets;
+  };
+
+  imports = [
+    hardware
+    ../../nixos/modules/monitoring/vpn/server.nix
+  ];
+
+  services.private-storage.monitoring.vpn.server = if !enableVpn then {} else {
+    enable = true;
+    ip = monitoringvpnIPv4;
+    inherit vpnClientIPs;
+    pubKeysPath = monitoringvpnKeyDir;
+  };
+
+  system.stateVersion = stateVersion;
+}
diff --git a/morph/lib/make-storage.nix b/morph/lib/make-storage.nix
index 25f3a95bfb5e66a4b42c5f2f82b4fdacbaed4b41..af0867c8b8342e31393f19a76a7cbfc4c95f86c9 100644
--- a/morph/lib/make-storage.nix
+++ b/morph/lib/make-storage.nix
@@ -14,7 +14,6 @@
 , ...
 }: rec {
   deployment = {
-    targetUser = "root";
     targetHost = cfg.publicIPv4;
 
     secrets = {
diff --git a/morph/lib/make-testing.nix b/morph/lib/make-testing.nix
index ed6ba27e70726cb08c38850cd09f08b37897fcfa..f1c1b56fc5444322a8f3a1191fe296fe23528a3e 100644
--- a/morph/lib/make-testing.nix
+++ b/morph/lib/make-testing.nix
@@ -1,7 +1,41 @@
-{ publicIPv4, hardware, publicStoragePort, ristrettoSigningKeyPath, passValue, sshUsers, stateVersion, ... }: rec {
+{ publicIPv4
+, hardware
+, publicStoragePort
+, ristrettoSigningKeyPath
+, passValue
+, sshUsers
+, stateVersion
+, monitoringvpnKeyDir ? null
+, monitoringvpnIPv4 ? null
+, monitoringvpnEndpoint ? null
+, ... }: let
+
+  enableVpn = monitoringvpnKeyDir != null &&
+              monitoringvpnIPv4 != null &&
+              monitoringvpnEndpoint != null;
+
+  vpnSecrets = if !enableVpn then {} else {
+    "monitoringvpn-secret-key" = {
+      source = monitoringvpnKeyDir + "/${monitoringvpnIPv4}.key";
+      destination = "/run/keys/monitoringvpn/client.key";
+      owner.user = "root";
+      owner.group = "root";
+      permissions = "0400";
+      action = ["sudo" "systemctl" "restart" "wireguard-monitoringvpn.service"];
+    };
+    "monitoringvpn-preshared-key" = {
+      source = monitoringvpnKeyDir + "/preshared.key";
+      destination = "/run/keys/monitoringvpn/preshared.key";
+      owner.user = "root";
+      owner.group = "root";
+      permissions = "0400";
+      action = ["sudo" "systemctl" "restart" "wireguard-monitoringvpn.service"];
+    };
+  };
+
+in rec {
 
   deployment = {
-    targetUser = "root";
     targetHost = publicIPv4;
 
     secrets = {
@@ -16,12 +50,13 @@
         # extract it from the tahoe-lafs nixos module somehow?
         action = ["sudo" "systemctl" "restart" "tahoe.storage.service"];
       };
-    };
+    } // vpnSecrets;
   };
 
   imports = [
     hardware
     ../../nixos/modules/private-storage.nix
+    ../../nixos/modules/monitoring/vpn/client.nix
   ];
 
   services.private-storage =
@@ -34,4 +69,11 @@
   };
 
   system.stateVersion = stateVersion;
+
+  services.private-storage.monitoring.vpn.client = if !enableVpn then {} else {
+    enable = true;
+    ip = monitoringvpnIPv4;
+    endpoint = monitoringvpnEndpoint;
+    endpointPublicKeyFile = monitoringvpnKeyDir + "/server.pub";
+  };
 }
diff --git a/nixos/modules/monitoring/vpn/client.nix b/nixos/modules/monitoring/vpn/client.nix
new file mode 100644
index 0000000000000000000000000000000000000000..dbd50b82ef5b09495e332e6fbb7ac5676f5ac322
--- /dev/null
+++ b/nixos/modules/monitoring/vpn/client.nix
@@ -0,0 +1,74 @@
+# Client section of our Monitoring VPN config
+
+{ lib, config, ... }: let
+  cfg = config.services.private-storage.monitoring.vpn;
+
+in {
+  options.services.private-storage.monitoring.vpn.client = {
+    enable = lib.mkEnableOption "PrivateStorageio Monitoring VPN client service";
+    privateKeyFile = lib.mkOption {
+      type = lib.types.path;
+      example = lib.literalExample /run/keys/monitoringvpn/host.key;
+      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 {
+      type = lib.types.path;
+      example = lib.literalExample /run/keys/monitoringvpn/preshared.key;
+      default = /run/keys/monitoringvpn/preshared.key;
+      description = ''
+        File with base64 preshared key generated by <command>wg genpsk</command>.
+      '';
+    };
+    allowedIPs = lib.mkOption {
+      type = lib.types.listOf lib.types.str;
+      example = lib.literalExample [ "172.23.23.1/32" ];
+      default = [ "172.23.23.1/32" ];
+      description = ''
+        Limits which IPs this client receives data from.
+      '';
+    };
+    ip = lib.mkOption {
+      type = lib.types.str;
+      example = lib.literalExample "172.23.23.11";
+      description = ''
+        The IP addresses of the interface.
+      '';
+    };
+    endpoint = lib.mkOption {
+      type = lib.types.str;
+      example = lib.literalExample "vpn.monitoring.private.storage:54321";
+      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;
+      description = ''
+        File with base64 public key generated by <command>cat private.key | wg pubkey > pubkey.pub</command>.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.client.enable {
+    networking.wireguard.interfaces.monitoringvpn = {
+      ips = [ "${cfg.client.ip}/24" ];
+      privateKeyFile = toString cfg.client.privateKeyFile;
+      peers = [
+        {
+          allowedIPs = cfg.client.allowedIPs;
+          endpoint = cfg.client.endpoint;  # meaning: the server.
+          publicKey = lib.fileContents(cfg.client.endpointPublicKeyFile);
+          presharedKeyFile = toString cfg.client.presharedKeyFile;
+          persistentKeepalive = 25;
+        }
+      ];
+    };
+  };
+}
+
diff --git a/nixos/modules/monitoring/vpn/server.nix b/nixos/modules/monitoring/vpn/server.nix
new file mode 100644
index 0000000000000000000000000000000000000000..2374ddc8657fb299fb83155cbabe328cd54c1aaf
--- /dev/null
+++ b/nixos/modules/monitoring/vpn/server.nix
@@ -0,0 +1,72 @@
+# Server section of our Monitoring VPN config
+
+{ lib, config, ... }: let
+  cfg = config.services.private-storage.monitoring.vpn;
+  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 = {
+    enable = lib.mkEnableOption "PrivateStorageio Monitoring VPN server service";
+    privateKeyFile = lib.mkOption {
+      type = lib.types.path;
+      example = lib.literalExample /run/keys/monitoringvpn/server.key;
+      default = /run/keys/monitoringvpn/server.key;
+      description = ''
+        File with base64 private key generated by <command>wg genkey</command>.
+      '';
+    };
+    presharedKeyFile = lib.mkOption {
+      type = lib.types.path;
+      example = lib.literalExample /run/keys/monitoringvpn/preshared.key;
+      default = /run/keys/monitoringvpn/preshared.key;
+      description = ''
+        File with base64 preshared key generated by <command>wg genpsk</command>.
+      '';
+    };
+    ip = lib.mkOption {
+      type = lib.types.str;
+      example = lib.literalExample [ "172.23.23.23" ];
+      description = ''
+        The IP address of the interface.
+      '';
+    };
+    port = lib.mkOption {
+      type = lib.types.port;
+      example = lib.literalExample 54321;
+      default = 51820;
+      description = ''
+        The UDP port to listen on.
+      '';
+    };
+    vpnClientIPs = lib.mkOption {
+      type = lib.types.listOf lib.types.str;
+      example = lib.literalExample [ "172.23.23.23" "172.23.23.42" ];
+      description = ''
+        The IP addresses to allow connections from.
+      '';
+    };
+    pubKeysPath = lib.mkOption {
+      type = lib.types.path;
+      example = lib.literalExample ../PrivateStorageSecrets/monitoringvpn;
+      description = ''
+        The path to the directory that holds the public keys.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.server.enable {
+    networking.firewall.allowedUDPPorts = [ cfg.server.port ];
+
+    networking.wireguard.interfaces.monitoringvpn = {
+      ips = [ "${cfg.server.ip}/24" ];
+      listenPort = cfg.server.port;
+      privateKeyFile = toString cfg.server.privateKeyFile;
+      peers = clients;
+    };
+  };
+}
+
diff --git a/nixos/modules/ssh.nix b/nixos/modules/ssh.nix
index 59ee2fec949be247143041379626f35b7d8bf657..667bdd26215b4e0978781244741dd4c5313cefbd 100644
--- a/nixos/modules/ssh.nix
+++ b/nixos/modules/ssh.nix
@@ -37,6 +37,10 @@
         # password-based authentication at all.
         PermitEmptyPasswords no
 
+        # Agent forwarding is fraught.  It can be used by an attacker to
+        # leverage one compromised system into more.  Discourage its use.
+        AllowAgentForwarding no
+
         # Only allow authentication as one of the configured users, not random
         # other (often system-managed) users.  Possibly this is also
         # superfluous!  NixOS system users have nologin as their shell ... so they
diff --git a/nixos/modules/tests/get-passes.py b/nixos/modules/tests/get-passes.py
index 691a3d26f37125379df85c85481cfdac67741d07..63b59e5700e0fa0c659f04df8fa889a0501f125f 100755
--- a/nixos/modules/tests/get-passes.py
+++ b/nixos/modules/tests/get-passes.py
@@ -95,7 +95,7 @@ def charge_json(voucher):
     return {
         "token": "tok_abcdef",
         "voucher": voucher,
-        "amount": "100",
+        "amount": "650",
         "currency": "USD",
     }
 
diff --git a/nixpkgs-2105.json b/nixpkgs-2105.json
new file mode 100644
index 0000000000000000000000000000000000000000..d441d00995a78a20cc8677a85ced2a675a9502ae
--- /dev/null
+++ b/nixpkgs-2105.json
@@ -0,0 +1,4 @@
+{ "name": "stable2105"
+, "url": "https://releases.nixos.org/nixos/21.05/nixos-21.05.804.5de44c15758/nixexprs.tar.xz"
+, "sha256": "002zvc16hyrbs0icx1qj255c9dqjpdxx4bhhfjndlj3kwn40by0m"
+}
diff --git a/nixpkgs.json b/nixpkgs.json
index a13edba683061306533106d1f2d1e5c6eacaadba..33b343ef3498ae226218f59be257e808e9a88c7e 100644
--- a/nixpkgs.json
+++ b/nixpkgs.json
@@ -1,4 +1,4 @@
 { "name": "nixpkgs"
-, "url": "https://github.com/PrivateStorageio/nixpkgs/archive/d7c46f46c3e6acfcde44bb05877fd5c9f07dfe1b.tar.gz"
-, "sha256": "1vddxhp3ljaq9qqzbpjp2kx34ps7nzbsn4zy31x0748a52rlsh0q"
+, "url": "https://github.com/PrivateStorageio/nixpkgs/archive/8c7a61c658e32eaccf666e5fe818a996c36a988f.tar.gz"
+, "sha256": "1ln0a8c20qykm57wl901lixny1fcfmzgbavd7pbjk6jbnfij59bl"
 }
diff --git a/shell.nix b/shell.nix
index b15b7c66b110a785480142c3b1d34b3d365004f6..6e46c9ca0feaa3ab6fbd22c1228ec786a49e79b6 100644
--- a/shell.nix
+++ b/shell.nix
@@ -1,12 +1,12 @@
 let
-  nixpkgs-pin = builtins.fromJSON (builtins.readFile ./nixpkgs.json);
-  nixpkgs-src = builtins.fetchTarball nixpkgs-pin;
-  nixpkgs = import nixpkgs-src { };
+  nixpkgs = import (builtins.fetchTarball (builtins.fromJSON (builtins.readFile ./nixpkgs.json))) { };
+  stable2105 = import (builtins.fetchTarball (builtins.fromJSON (builtins.readFile ./nixpkgs-2105.json))) { };
 in
 { pkgs ? nixpkgs }:
 pkgs.mkShell {
   NIX_PATH = "nixpkgs=${nixpkgs.path}";
   buildInputs = [
     pkgs.morph
+    stable2105.vagrant
   ];
 }