Requirements
What the panel server, the node servers and the build machine need, and what the agent changes on a node.
On this page
Mistgate needs one Linux server for the panel, one or more Linux servers for the nodes, a domain name, and a machine to build the binaries on. There are no binary releases yet, so you build from source.
The panel server#
| What | Requirement |
|---|---|
| System | Linux on amd64 or arm64. systemd to keep mistgate serve running (an example unit is in Install the panel). |
| Access | root to install. The running panel only needs to bind TCP 443 and 80 and to write its data directory. |
| Domain | A host name whose A (and, if you have IPv6, AAAA) record points at the server, for example panel.example.com. |
| Inbound ports | TCP 443: the decoy site, subscriptions, user pages, the admin (in the prefix and host modes) and the node agents. TCP 80 when the panel gets its certificate from Let's Encrypt. Nothing else, unless you choose a separate admin or agent listener. |
| Outbound | Let's Encrypt over HTTPS when you use it. The ports of your servers on the nodes: the panel checks every server as a client (Hysteria2 over UDP, AmneziaWG over UDP). Cloudflare over HTTPS when you register WARP accounts or switch on the Turnstile captcha. |
| Storage | The data directory, /var/lib/mistgate by default: one SQLite database, the master key and a few small files. |
| Other software | None. The panel is one static binary and runs no other programs. |
keep the panel on a server of its own. A node on the same host would compete with the panel for TCP 443 (Hysteria2 runs an HTTPS site there by default) and TCP 80.
Node servers#
| What | Requirement |
|---|---|
| System | Linux with systemd: Ubuntu 22.04 or newer, or Debian 12 or newer. amd64 or arm64. |
| Access | root over SSH. mistgate-node install must run as root, and the agent runs as root with a narrowed set of capabilities. |
| Address | A public IPv4 address. Clients connect to the node's address: a domain that points at the server, or the IP itself. |
| nftables | The nft command (installed by default on Debian 12 and Ubuntu 22.04+). The agent drives its firewall rules through it. Port hopping needs redirect in the inet family, which kernels from 5.2 have. |
| TUN device | /dev/net/tun for the userspace AmneziaWG backend and for WARP. On a container VPS (OpenVZ, LXC) enable TUN in the hoster's control panel. |
| Clock | Synchronised with NTP. The agent reports a clock that differs from the panel's by more than 30 seconds, and the doctor's time_sync check looks at it too. |
| Inbound ports | The UDP port of each server on the node: Hysteria2 starts at 443, an AmneziaWG profile gets a random port between 10000 and 60000. The port-hopping range of a Hysteria2 profile when you turn it on (from 1024 up, at most 20000 ports). TCP 443 for the HTTPS site Hysteria2 shows to everyone who is not a client, and for Let's Encrypt. TCP 80 for Let's Encrypt when nothing else holds it. |
| Outbound | TCP to the panel (usually 443). Let's Encrypt, DNS, and Cloudflare's WARP endpoints when the node uses WARP. |
| No management port | The agent dials the panel. Nothing on the node has to accept connections from the panel. |
Good to know:
- IPv6 is optional. A node without IPv6 serves over IPv4. A node that has a global IPv6 address but cannot connect out over IPv6 is worse than one without: apps that get AAAA answers stall. The doctor's
ipv6check warns about it. - Containers. On OpenVZ or LXC the agent cannot set its sysctl values (it logs that and carries on), and the AmneziaWG kernel module cannot be installed. The userspace backend works when TUN is enabled.
- AmneziaWG kernel module. Optional. By default AmneziaWG runs in userspace and needs no module. On request, the agent (or
mistgate-node awg prepare-kernel) builds the module on Ubuntu from the Amnezia PPA and on Debian from source. That needs apt, systemd, no container and no Secure Boot. - A clean host works best. The doctor reports what can get in the way: other VPN panels and their containers, Xray, old WireGuard interfaces, nftables NAT rules that are not Mistgate's, and Docker's FORWARD drop policy (it breaks AmneziaWG forwarding).
Building from source#
| Tool | Version |
|---|---|
| Go | 1.27. go.mod names the toolchain (go1.27.1); with GOTOOLCHAIN=auto (the default) an older Go downloads it. |
| Node.js | 22 or newer (the admin SPA is built and embedded into the panel binary). |
| pnpm | 10. |
| make and a POSIX shell | On Windows, Git Bash or WSL. |
| git | make build stamps the version (git describe) and the commit time (git log) into the binaries. Without git the version is 0.0.0-dev and the build time 0. |
| Internet access | For Go modules and npm packages. |
make build produces static Linux binaries (CGO_ENABLED=0) for both architectures:
make build
# bin/mistgate-linux-amd64 bin/mistgate-node-linux-amd64
# bin/mistgate-linux-arm64 bin/mistgate-node-linux-arm64node agents update themselves only when they were built with your release key (RELEASE_KEY=<public key> make build). A node installed from a build without the key has to be updated by hand once. If you plan to use signed updates, make the key before the first build: see Updates.
DNS and certificates#
- Panel. Point the panel's host name straight at the server, without a CDN or proxy in front of it. Node agents pin the panel's own CA and Let's Encrypt validates on the server itself; neither works through a proxy that terminates TLS.
- Panel certificate. Either let the panel get one from Let's Encrypt (
serve --acme-domain), or give it your own certificate files (serve --tls-certand--tls-key). See Install the panel. - Secret admin host. If the admin lives on a secret host name, that name needs its own DNS record and must be covered by a certificate. A Let's Encrypt certificate publishes its names in public Certificate Transparency logs; a wildcard certificate of your own keeps the secret name out of them.
- Agent endpoint. It needs no DNS record and no public certificate. Agents reach it at the panel's address with a secret TLS name (a random label under the panel's domain), and the panel answers that name with a certificate from its own CA.
- Nodes. A Hysteria2 server with a Let's Encrypt certificate needs a domain whose A record points at the node: the node's address, or the profile's Domain (SNI). A node known only by its IP uses the self-signed (pinned) certificate instead. AmneziaWG needs no certificate.
What the agent changes on a node#
The agent changes only what is listed here, and only its own objects: it never edits another program's nftables tables, never redirects a port below 1024, and never lets a port-hopping range cover the sshd ports.
| What | Where | When |
|---|---|---|
| Agent binary | /usr/local/bin/mistgate-node (install --bin changes the path). Self-update puts <binary>.new and <binary>.prev next to it. |
mistgate-node install, self-update |
| Service | /etc/systemd/system/mistgate-node.service, enabled and started. |
mistgate-node install |
| State directory | /var/lib/mistgate-node (mode 0700, files 0600): the node's key and certificate, the panel CA, the panel address, the last applied state, the certificates of its servers, update markers. |
enroll, then the agent |
| sysctl | /etc/sysctl.d/90-mistgate.conf: net.core.default_qdisc = fq and net.ipv4.tcp_congestion_control = bbr, also applied to the running kernel. |
every agent start |
| journald | /etc/systemd/journald.conf.d/90-mistgate.conf: SystemMaxUse=200M, RuntimeMaxUse=200M. journald is restarted when the file changes. |
every agent start |
| Firewall: SSH guard and port hopping | nftables table inet mistgate_node. |
every agent start; when port-hopping ranges change |
| AmneziaWG | Interfaces mgawg<port>, nftables table inet mistgate_awg (NAT for the clients, MSS clamp, no traffic between clients, nothing on the node reachable through the tunnel except ping to the tunnel address). IPv4 forwarding on; IPv6 forwarding when a tunnel has IPv6, after moving interfaces with accept_ra=1 to accept_ra=2 so the host keeps its own IPv6. Forwarding values are set live only. |
while the node runs an AmneziaWG server |
| WARP | Interface mgwarp, routing table 51820 with rules at preferences 90 and 110, nftables table inet mistgate_warp. |
while the node has WARP |
| Resolver | /etc/systemd/resolved.conf.d/90-mistgate.conf (with systemd-resolved), or a rewritten /etc/resolv.conf with the original kept as /etc/resolv.conf.mistgate.bak. |
only when you apply the doctor's resolver fix |
| Journal size | journalctl --vacuum-size=200M --vacuum-time=7d. |
only when you apply the doctor's fix |
| AmneziaWG kernel module | apt packages, the Amnezia PPA on Ubuntu or a source build on Debian, /etc/modules-load.d/amneziawg.conf. Runs as the transient unit mistgate-awg-prepare. |
only when you ask for it |
the SSH guard rate-limits new connections to the sshd ports per source address (per /64 for IPv6): a burst of 10, then 6 a minute. Open sessions are never touched, nothing is banned, loopback is exempt, and when its table is full the guard stops matching rather than lock you out. Tools that open many SSH connections at once from one address may still feel it. The agent finds the sshd ports with sshd -T, the sshd config files and ssh.socket, and uses 22 when it finds nothing.
The service unit#
mistgate-node install writes the unit itself, sized to the host's memory. Its main settings:
| Setting | Value |
|---|---|
| Start | mistgate-node run --state-dir /var/lib/mistgate-node |
| Before start | a crash-loop guard: after an update that crashes three times in a row it puts the previous binary back |
| After every stop | mistgate-node cleanup-net: removes the AmneziaWG and WARP interfaces, the WARP routes and rules and their nftables tables, so a crashed agent leaves no broken routing behind (the agent puts them back when it starts) |
| Restart | Restart=on-failure after 5 s; exit code 78 (not enrolled or retired) is not restarted |
| Memory | GOMEMLIMIT about 60% of RAM (at least 64 MiB), MemoryHigh about 70%, MemoryMax about 85%; LimitNOFILE=65536 |
| Privileges | runs as root with only CAP_NET_ADMIN and CAP_NET_BIND_SERVICE, NoNewPrivileges=yes |
| Files | ProtectSystem=strict: writable are only the state directory, the binary's directory, /etc/sysctl.d and /etc/systemd/journald.conf.d |
| Devices | only /dev/net/tun besides the standard pseudo devices |
| Other | ProtectHome, PrivateTmp, SystemCallFilter=@system-service, UMask=0077 and the rest of the usual systemd sandboxing |
What retiring a node removes, and how to clean a host by hand, is under "Remove a node" in Add a node.