Skip to main content
โšก Calmops

ZeroTier: Mesh VPN Setup and Configuration Guide

Introduction

ZeroTier creates a virtual Ethernet network across the internet. Every device on your ZeroTier network can reach every other device as if they were on the same LAN โ€” regardless of NAT, firewalls, or geographic location. Unlike WireGuard (point-to-point) or OpenVPN (hub-and-spoke), ZeroTier is a true mesh: devices connect directly to each other.

ZeroTier vs WireGuard:

WireGuard:  You configure each peer explicitly. Fast, simple, manual.
ZeroTier:   Devices auto-discover each other. Easier to manage at scale.

WireGuard:  ~950 Mbps throughput
ZeroTier:   ~300-600 Mbps throughput (more overhead for mesh management)

WireGuard:  Best for: performance-critical, small number of peers
ZeroTier:   Best for: many devices, IoT, dynamic membership

Quick Start

Step 1: Create a Network

  1. Sign up at my.zerotier.com
  2. Click “Create A Network”
  3. Note your 16-character Network ID (e.g., 8056c2e21c000001)

Step 2: Install ZeroTier

# Linux (Ubuntu/Debian)
curl -s https://install.zerotier.com | sudo bash

# macOS
brew install zerotier

# Windows: download from zerotier.com/download

# Verify
sudo zerotier-cli status
# 200 info <your-node-id> 1.14.0 ONLINE

Step 3: Join Your Network

# Join the network
sudo zerotier-cli join 8056c2e21c000001

# Check status
sudo zerotier-cli listnetworks
# 200 listnetworks 8056c2e21c000001 <name> <mac> OK PRIVATE zt0 10.147.17.x/16

Step 4: Authorize the Device

In ZeroTier Central, go to your network โ†’ Members โ†’ check the box next to your device to authorize it.

# Verify you got an IP
ip addr show zt0
# inet 10.147.17.123/16 brd 10.147.255.255 scope global zt0

# Test connectivity to another device
ping 10.147.17.100

Network Configuration

Managed Routes

Add routes so devices can reach subnets behind each other:

In ZeroTier Central โ†’ Network โ†’ Managed Routes:

Route: 192.168.1.0/24  Via: 10.147.17.10
(This tells all ZeroTier devices: to reach 192.168.1.0/24, go through 10.147.17.10)

On the gateway device (10.147.17.10), enable IP forwarding:

# Enable forwarding
sudo sysctl -w net.ipv4.ip_forward=1
echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf

# Add NAT if needed (for internet access through gateway)
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
sudo iptables -A FORWARD -i zt0 -j ACCEPT
sudo iptables -A FORWARD -o zt0 -j ACCEPT

Access Control Rules

ZeroTier uses flow rules to control traffic between members:

# In ZeroTier Central โ†’ Network โ†’ Flow Rules

# Default: allow all traffic
accept;

# More restrictive: only allow specific ports
# Block all by default
drop;

# Allow SSH from management subnet only
match ethertype ipv4 and ip4.src 10.147.17.0/24 and dport 22;
accept;

# Allow HTTP/HTTPS
match ethertype ipv4 and dport 80;
accept;
match ethertype ipv4 and dport 443;
accept;

# Allow ICMP (ping)
match ethertype ipv4 and ipprotocol icmp;
accept;

ZeroTier CLI Reference

# Status
sudo zerotier-cli status
sudo zerotier-cli info

# Networks
sudo zerotier-cli listnetworks
sudo zerotier-cli join <network-id>
sudo zerotier-cli leave <network-id>

# Peers
sudo zerotier-cli listpeers
# Shows: node-id, latency, version, role (LEAF/PLANET/MOON)

# Network details
sudo zerotier-cli get <network-id> name
sudo zerotier-cli get <network-id> status

# Disconnect/reconnect
sudo systemctl restart zerotier-one

Self-Hosted Controller

For privacy or compliance, run your own ZeroTier controller:

# Install ZeroTier (includes controller)
curl -s https://install.zerotier.com | sudo bash

# The controller API runs on port 9993
# Get your auth token
sudo cat /var/lib/zerotier-one/authtoken.secret

# Create a network via API
curl -X POST "http://localhost:9993/controller/network/$(cat /var/lib/zerotier-one/identity.public | cut -d: -f1)______" \
    -H "X-ZT1-Auth: $(sudo cat /var/lib/zerotier-one/authtoken.secret)" \
    -d '{"name": "my-private-network"}'

# Response includes network ID
# {"id":"8056c2e21c000001","name":"my-private-network",...}

ZeroTierOne Controller API

import httpx

class ZeroTierController:
    def __init__(self, host: str = "localhost", port: int = 9993):
        self.base_url = f"http://{host}:{port}"
        with open("/var/lib/zerotier-one/authtoken.secret") as f:
            self.token = f.read().strip()
        self.headers = {"X-ZT1-Auth": self.token}

    def get_node_id(self) -> str:
        resp = httpx.get(f"{self.base_url}/status", headers=self.headers)
        return resp.json()["address"]

    def create_network(self, name: str, subnet: str = "10.147.0.0/16") -> dict:
        node_id = self.get_node_id()
        network_id = f"{node_id}______"

        resp = httpx.post(
            f"{self.base_url}/controller/network/{network_id}",
            headers=self.headers,
            json={
                "name": name,
                "private": True,
                "v4AssignMode": {"zt": True},
                "ipAssignmentPools": [{"ipRangeStart": "10.147.0.1", "ipRangeEnd": "10.147.255.254"}],
                "routes": [{"target": subnet, "via": None}],
            }
        )
        return resp.json()

    def list_members(self, network_id: str) -> list:
        resp = httpx.get(
            f"{self.base_url}/controller/network/{network_id}/member",
            headers=self.headers
        )
        return resp.json()

    def authorize_member(self, network_id: str, member_id: str) -> dict:
        resp = httpx.post(
            f"{self.base_url}/controller/network/{network_id}/member/{member_id}",
            headers=self.headers,
            json={"authorized": True}
        )
        return resp.json()

    def deauthorize_member(self, network_id: str, member_id: str) -> dict:
        resp = httpx.post(
            f"{self.base_url}/controller/network/{network_id}/member/{member_id}",
            headers=self.headers,
            json={"authorized": False}
        )
        return resp.json()

# Usage
controller = ZeroTierController()
network = controller.create_network("production-mesh")
print(f"Network ID: {network['id']}")

Moon (Private Root Server)

For better performance and privacy, run a Moon (private relay/root server):

# On a server with a public IP
sudo zerotier-cli orbit <moon-id> <moon-id>

# Generate moon config
cd /var/lib/zerotier-one
sudo zerotier-idtool initmoon identity.public > moon.json

# Edit moon.json to add your server's public IP
# "stableEndpoints": ["203.0.113.1/9993"]

# Generate the moon file
sudo zerotier-idtool genmoon moon.json

# Copy the .moon file to all clients
# On each client:
sudo mkdir -p /var/lib/zerotier-one/moons.d
sudo cp <moon-id>.moon /var/lib/zerotier-one/moons.d/
sudo systemctl restart zerotier-one

# Verify moon is connected
sudo zerotier-cli listpeers | grep MOON

Common Use Cases

IoT Device Management

# On each IoT device (Raspberry Pi, etc.)
curl -s https://install.zerotier.com | sudo bash
sudo zerotier-cli join YOUR_NETWORK_ID

# Auto-authorize new devices via API (for automated provisioning)
# Run this on your management server when a new device joins
# Auto-authorize new ZeroTier members
import time

def auto_authorize_new_members(network_id: str, controller: ZeroTierController):
    """Automatically authorize new members as they join."""
    known_members = set()

    while True:
        members = controller.list_members(network_id)
        for member_id, member_data in members.items():
            if member_id not in known_members:
                known_members.add(member_id)
                if not member_data.get("authorized"):
                    controller.authorize_member(network_id, member_id)
                    print(f"Auto-authorized new member: {member_id}")
        time.sleep(30)

Multi-Site Office Network

Office A (192.168.1.0/24) โ”€โ”€ZeroTierโ”€โ”€โ†’ All offices
Office B (192.168.2.0/24) โ”€โ”€ZeroTierโ”€โ”€โ†’ All offices
Cloud VPC (10.0.0.0/16)   โ”€โ”€ZeroTierโ”€โ”€โ†’ All offices

ZeroTier Network: 10.147.0.0/16
  Office A gateway: 10.147.0.1
  Office B gateway: 10.147.0.2
  Cloud gateway:    10.147.0.3

Managed Routes in ZeroTier Central:
  192.168.1.0/24 via 10.147.0.1
  192.168.2.0/24 via 10.147.0.2
  10.0.0.0/16    via 10.147.0.3

Monitoring

# Check peer connectivity and latency
sudo zerotier-cli listpeers
# 200 listpeers
# <id> <ip:port> <latency> <version> <role>
# abc123def456 203.0.113.1/9993 12 1.14.0 LEAF

# Check if direct connection or relayed
# latency < 50ms = likely direct
# latency > 100ms = likely relayed through PLANET/MOON

# Network interface stats
ip -s link show zt0

# Watch traffic
sudo tcpdump -i zt0 -n

Troubleshooting

# Device not getting IP
sudo zerotier-cli listnetworks
# Check status: should be "OK" not "ACCESS_DENIED"
# โ†’ Go to ZeroTier Central and authorize the device

# Can't reach other devices
sudo zerotier-cli listpeers
# Check if peers show up with low latency (direct) or high latency (relayed)

# High latency (relayed traffic)
# โ†’ Deploy a Moon server closer to your devices
# โ†’ Check if UDP port 9993 is blocked by firewall

# Allow ZeroTier through firewall
sudo ufw allow 9993/udp

# Check ZeroTier service
sudo systemctl status zerotier-one
sudo journalctl -u zerotier-one -f

Resources

Comments