Manage a community vpn/dns network
Go to file
Keith Irwin 324ffeada3
feat: Show PublicKey in new peer configs
2023-12-02 19:12:28 -07:00
back feat: Show PublicKey in new peer configs 2023-12-02 19:12:28 -07:00
etc.sample fix: 🔒 Secure permissions on etc config files 2023-12-02 11:51:10 -07:00
front Final cleanup for distribution 2022-12-04 13:32:36 -07:00
img Added logo 2023-04-08 15:30:51 -06:00
.gitignore fix: 🐛 Fixed .gitignore ignoring wrong file 2023-12-02 11:47:36 -07:00 refactor: 🎨 Moved ./etc to /etc/wagon, mounted wireguard config, and implemented docker-compose.override.yml 2023-12-01 12:24:46 -07:00 #9 Renamed to wagon and added README info 2023-04-08 15:30:25 -06:00 #9 Checkboxes, not bullets 2023-04-08 15:43:14 -06:00 #9 Finished documentation 2023-04-12 22:06:51 -06:00
docker-compose.override.yml.sample refactor: 🏗️ Move network_mode to docker-compose override files 2023-12-02 12:01:13 -07:00
docker-compose.yml fix: 🐛 Remove volumes from docker-compose, set in override 2023-12-02 14:23:22 -07:00


2022-2023 Keith Irwin (
MIT License

wagon is a an api, user dashboard, and admin ui for managing devices and services on a dns-enabled wireguard network. It was built with small web communities in mind.

IP address allocation

wagon manages devices on a wireguard network, 10.X.Y.Z/16 where X is a number associated with the community, configured at the first setup. Servers are hosted on 10.X.0.Z/24, where each Z is a different server. Finally, each user gets a number Y (between 1 and 254) and a /24 subnet for each of their their devices, Z.

For example: Your home pc could be and your phone After configuring wireguard, you can use either device to ping your friend, whose pc is at

IPv6 is also preconfigured for a /96 subnet, with users getting their own /112 network for each /128 device.


wagon manages a bind9 nameserver through the nsupdate command. Device domain names have a similar structure as IP addresses, A.B.C, where:

  • C is the community's top-level-domain (TLD)
  • B is the username
  • A is hostname of the device

The TLD could be anything that isn't already a global TLD like .com. The recursive nameserver takes these private domains as questions and provides private wireguard IPs as answers. It only responds to .mynet queries from its own network on 10.X.0.0/16. This nameserver is then preconfigured in clients' wireguard configs' DNS = setting. Optionally the community's TLD can be set as a search domain for the wireguard interface.

For example: Your home pc could be pc.myuser.mynet and your phone phone.myuser.mynet. Either can ping up phone.myfriend.mynet. These would point to,, and respectively

Servers (IPs with Y=0) have domains of the form A.C, as above. Wildcard subdomains are CNAMEd to their base address, that is, *.phone.myuser.mynet CNAMEs to phone.myuser.mynet and *.myerver.mynet CNAMEs to myserver.mynet. Of course, this means no username can match a server hostname.

The nameserver is also preconfigured for rDNS so you can perform lookups on IP addresses:

$ nslookup   name = pc.myuser.mynet.

Using this setup, the community's entire wireguard network and DNS can be accessed from any device by importing a single wireguard config. One purpose of wagon is to easily generate these device configs.

Certificate authority

wagon also automatically signs SSL certs for its own invented TLD (.mynet) so network-hosted services can be encrypted. The certificates are signed for the IPs and wildcard domains listed above, so, with the right web proxy configuration, a user can use the same certificate to self-host a service on any of these domains:

  • https://[fd69:1337:0:420:f4:11:1:2]/
  • https://mypc.myuser.mynet/
  • https://myservice.mypc.myuser.mynet/
  • https://myotherservice.mypc.myuser.mynet/
  • https://anyotherservice.mypc.myuser.mynet/

The wagon user dashboard provides a server certificate and key that the user can download. Any wireguard-connected device with the community's ca certificate imported in their browser or OS will be able to see a green lock on their browser when visiting these private sites.

These certs can be used for other internet protocols like irc or imap.

Self-hosting and firewalls

Since firewalls and web proxies understand CIDR notation, controlling access to services is easy:

  • Allow to allow access to pc.myuser.mynet only
  • Allow to allow access to any of myuser's devices
  • Allow to allow access to a friend's pc
  • Allow to allow access to any of a friend's devices
  • Allow to allow access to anyone in the community
  • Allow to allow access to any other community (someday, maybe)

Allowing access to virtual webservers is just as simple. For example, I can let my friend access my development server with these allow/deny lines in an nginx vhost config (only showing IPv4, but works with IPv6 too).

server {
	server_name dev.mypc.myuser.mynet;
	listen ssl http2;

	ssl_certificate     /path/to/downloaded/mypc.myuser.mynet/server.crt;
	ssl_certificate_key /path/to/downloaded/mypc.myuser.mynet/server.key;
	ssl_stapling        off;

	allow;  # My devices
	allow;  # My friend's devices
	deny  all;           # Everyone else

	# Proxy to local dev server
	location / {
		proxy_pass http://localhost:8080;


Users can access a dashboard with a list of devices and links to download server.crt and server.key. Users can add and delete these devices, and admins can add/delete devices and users from a seperate admin interface. When adding a new user or device, the dashboard displays a wireguard configuration which must be copied or saved before the page is refreshed.

In this way, there is no central server storing all the private keys, like with most wireguard dashboards. In fact, wagon does not have a database and does not store any data at all; everything is stored in the server's nameserver and wireguard config.

This also means there is no login to the dashboard. Users simply connect to the dashboard over wireguard from any connected device, and wagon will recognize your IP and serve up a list of other devices on that same /24 subnet.


wagon is written in bash and run as a cgi script. It can be run in- or outside of docker. Why bash? Because it has great "SDKs" for wireguard (wg), nameserver updates (nsupdate), and SSL certs (openssl). The libraries used in the scripts are themselves scripts. Each script follows the unix philosophy of handling text through standard means (arguments and stdin to stdout), so individual libraries could be replaced with faster alternatives (c, rust, go) in the future.

Project status

The wireguard dashboard and admin are in a working alpha state. I have it implemented on my own network, gf4, and it seems to be working there. Work is still needed on all these unchecked boxes.

| Feature   | Multiuser daemon | Dashboard CRUD | Documented | Tests |
| wireguard |       [X]        |     [X]        |    [X]     |  [ ]  |
| bind9     |       [X]        |     [X]        |    [X]     |  [ ]  |
| SSL certs |       [X]        |     [X]        |    [X]     |  [ ]  |
| nginx     |       [X]        |     [ ]        |    [ ]     |  [ ]  |
| postfix   |       [X]        |     [ ]        |    [ ]     |  [ ]  |
| asterisk  |       [ ]        |     [ ]        |    [ ]     |  [ ]  |

 [ ] Replace bash scripts with binaries*
 [ ] API documentation
 [ ] A good CLI/TUI

*binaries compiled from languages like c/rust/go/haskell, as long as SDKs are available.


You want to use this half-built product on your server at your own risk? Fine, go right ahead! Take a look at If you have trouble, ask for help in the matrix room below.


If you think you can help with any of these checkboxes, come by on matrix.