Troubleshooting ZeroTier VPN: Common Issues and Fixes
ZeroTier is a software-defined networking solution that creates secure, peer-to-peer virtual networks across virtually any infrastructure. Unlike traditional VPNs that route all traffic through a central server, ZeroTier attempts to establish direct peer-to-peer connections between nodes, falling back to relay servers only when necessary. While this architecture delivers impressive performance and flexibility, it also introduces a unique set of troubleshooting challenges that developers and network administrators must understand.
Why Troubleshooting ZeroTier Matters
When ZeroTier works, it feels almost magical — devices on opposite sides of the planet communicate as if they were on the same local network. When it breaks, however, the decentralized nature of the system can make root causes difficult to identify. Misconfigured firewalls, NAT traversal failures, stale network states, and identity conflicts can all manifest as vague connectivity issues. A systematic troubleshooting approach is essential to minimize downtime, especially in production environments where ZeroTier might be backing remote access to internal services, IoT fleets, or multi-cloud infrastructure.
Understanding the ZeroTier Architecture
Before diving into specific issues, it helps to understand the components involved. Each ZeroTier node has a 10-digit node ID derived from its identity keypair. Networks are identified by a 16-digit network ID. Communication happens over UDP, typically on port 9993. The ZeroTier controller (hosted at my.zerotier.com or self-hosted) manages network membership and configuration, while root servers (also called "planets") assist with peer discovery and relaying when direct connections fail.
Key Diagnostic Commands
The ZeroTier CLI is your primary troubleshooting tool. On most systems, it is available as zerotier-cli and requires root or administrator privileges. Here are the commands you will use most frequently:
# Check the ZeroTier service status
sudo zerotier-cli status
# List all joined networks and their status
sudo zerotier-cli listnetworks
# List all known peers and their connection paths
sudo zerotier-cli listpeers -j
# Get detailed info about a specific network
sudo zerotier-cli info -j
# Leave a network
sudo zerotier-cli leave <network-id>
# Join a network
sudo zerotier-cli join <network-id>
The listpeers command is particularly valuable because it shows whether peers are connected directly (DIRECT), through a relay (RELAY), or not at all. This single piece of information often points you toward the root cause of connectivity problems.
Common Issue 1: Node Cannot Join a Network
One of the most frequent problems is a node that appears to join a network but never receives an IP address or never shows as authorized in the ZeroTier Central dashboard. This typically stems from one of several causes.
Checking Authorization Status
By default, ZeroTier networks require manual authorization of each joining node. After running zerotier-cli join, you must log into the ZeroTier Central web interface and authorize the new member. If the node remains unauthorized, it will not receive a network configuration or IP address.
# After joining, check the network status
sudo zerotier-cli listnetworks
# Output will look something like:
# 200 listnetworks <nwid> <name> <mac> <status> <type> <dev> <managed ips>
# 200 listnetworks 1a2b3c4d5e6f7g8h zerotier-net aa:bb:cc:dd:ee:ff ACCESS_DENIED zt0 -
If the status reads ACCESS_DENIED, the node has not been authorized on the controller. Navigate to the network's members page in ZeroTier Central and check the box next to the node's ID. Within a few seconds, the node should receive its configuration.
Verifying the ZeroTier Service is Running
If the join command itself fails, the ZeroTier daemon may not be running. Check the service status using your system's service manager:
# On systemd-based Linux distributions
sudo systemctl status zerotier-one
# On macOS
sudo launchctl list | grep zerotier
# On Windows (PowerShell)
Get-Service ZeroTierOneService
If the service is stopped, start it and check the logs for errors:
# Start the service on Linux
sudo systemctl start zerotier-one
# View recent logs on Linux
sudo journalctl -u zerotier-one -n 50 --no-pager
# View logs on macOS
cat /Library/Application\ Support/ZeroTier/One/zerotier-one.log
# View logs on Windows
Get-EventLog -LogName Application -Source "ZeroTierOne" -Newest 20
Common Issue 2: Peers Stuck in RELAY Mode
When two ZeroTier nodes cannot establish a direct UDP connection, they fall back to relaying traffic through ZeroTier's root servers. This works, but latency increases significantly and throughput drops. If listpeers shows your peers as RELAY instead of DIRECT, you likely have a NAT or firewall problem.
Diagnosing NAT and Firewall Issues
ZeroTier uses UDP hole punching to traverse NATs, which requires outbound UDP traffic on port 9993 to be allowed. Many corporate firewalls and some home routers block or throttle this traffic. Start by confirming that the node can reach ZeroTier's root servers:
# Check if UDP port 9993 is reachable from your node
# On Linux/macOS, use nc (netcat):
echo -n "test" | nc -u -w2 50.7.252.138 9993
# Alternatively, check if the ZeroTier process is listening
sudo netstat -ulnp | grep 9993
# or
sudo ss -ulnp | grep 9993
If the ZeroTier process is not listening on port 9993, there may be a configuration problem or a port conflict. Check for conflicting services:
# Find any process using port 9993
sudo lsof -i :9993
sudo fuser 9993/udp
Configuring Port Forwarding for Difficult NATs
In environments with symmetric NAT (common in some cellular networks and corporate setups), UDP hole punching may fail consistently. In these cases, manually forwarding UDP port 9993 to the ZeroTier node can force a direct connection. The exact steps depend on your router, but the general approach is:
- Assign a static LAN IP to the ZeroTier node
- Configure your router to forward UDP port 9993 to that IP
- Restart the ZeroTier service and re-check peer status
# After configuring port forwarding, restart ZeroTier
sudo systemctl restart zerotier-one
# Wait a few seconds, then check peer connections
sleep 5
sudo zerotier-cli listpeers | grep -v RELAY
If peers still show as RELAY after port forwarding, the issue may be on the remote node's side. Both endpoints need to be able to communicate for a direct connection to form.
Common Issue 3: Connected But No IP Address
Sometimes a node shows as OK in listnetworks but has no managed IP address assigned. This usually means the network configuration on the controller does not include an IP assignment for the node, or there is a subnet conflict on the local machine.
Checking IP Assignments in ZeroTier Central
In the ZeroTier Central dashboard, navigate to the network's settings and verify that an IP address pool is configured under "IPv4 Auto-Assign." If you are using managed IPs, ensure each member has an address assigned. You can also check the local configuration:
# Get detailed network info in JSON format
sudo zerotier-cli listnetworks -j | python3 -m json.tool
# Check the local network interface
ip addr show zt0
# or on macOS:
ifconfig zt0
Resolving Subnet Conflicts
If the ZeroTier network's subnet overlaps with a physical network the node is already connected to, the operating system may refuse to add the route. For example, if your office Wi-Fi uses 192.168.1.0/24 and your ZeroTier network also uses that range, routing conflicts will occur. The fix is to change the ZeroTier network to use a non-overlapping subnet, such as 10.147.17.0/24 or 172.29.0.0/24.
# Check all routes to identify conflicts
ip route show
# or on macOS:
netstat -rn
# Look for overlapping subnets between zt0 and other interfaces
ip route | grep -E "192\.168\.|10\.|172\.(1[6-9]|2[0-9]|3[01])\."
Common Issue 4: Identity Conflicts and Cloned Nodes
ZeroTier identities are stored in a file called identity.secret. If you clone a virtual machine or container that already has ZeroTier installed, the clone will have the same identity as the original. This causes both nodes to fight over the same peer identity, leading to intermittent connectivity and bizarre routing behavior.
Detecting Identity Clones
If two nodes report the same node ID but behave inconsistently, you likely have an identity conflict. Check the node ID on each machine:
# Get the node ID
sudo zerotier-cli info
# Output: 200 info <node-id> <version> <online status>
# Example: 200 info 1a2b3c4d5e 1.12.2 ONLINE
Fixing Cloned Identities
To fix this, generate a new identity on the cloned node. Stop the ZeroTier service, remove the identity files, and restart:
# Stop the ZeroTier service
sudo systemctl stop zerotier-one
# Remove the identity files (Linux paths shown)
sudo rm /var/lib/zerotier-one/identity.secret
sudo rm /var/lib/zerotier-one/identity.public
# Restart the service - it will generate a new identity
sudo systemctl start zerotier-one
# Verify the new node ID
sleep 3
sudo zerotier-cli info
After generating a new identity, you will need to rejoin all networks and re-authorize the node in ZeroTier Central. This is why it is critical to never clone running ZeroTier installations without first removing identity files.
Common Issue 5: Connectivity Drops Intermittently
Intermittent disconnections are among the hardest issues to troubleshoot because they are transient. Common causes include aggressive NAT timeouts, flapping network interfaces, and DNS resolution problems for the ZeroTier root servers.
Tuning Keepalive and NAT Settings
Some NAT devices close UDP mappings quickly if no traffic flows. ZeroTier sends keepalive packets, but you may need to tune the frequency. Check the local configuration file:
# View the local ZeroTier configuration
cat /var/lib/zerotier-one/local.conf
# If it does not exist, create a minimal config with tuned settings
sudo tee /var/lib/zerotier-one/local.conf << 'EOF'
{
"settings": {
"primaryPort": 9993,
"secondaryPort": 0,
"tertiaryPort": 0,
"portMappingEnabled": true,
"softwareUpdate": "disable",
"allowManagementFrom": []
}
}
EOF
# Restart to apply changes
sudo systemctl restart zerotier-one
Monitoring Connection Stability
For ongoing monitoring, you can script periodic checks of peer status and log the results. This helps correlate drops with external events:
#!/bin/bash
# zero-tier-monitor.sh - Log ZeroTier peer status every 60 seconds
LOGFILE="/var/log/zerotier-monitor.log"
while true; do
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
STATUS=$(sudo zerotier-cli status | awk '{print $3}')
PEERS=$(sudo zerotier-cli listpeers | tail -n +2 | wc -l)
DIRECT=$(sudo zerotier-cli listpeers | grep DIRECT | wc -l)
RELAY=$(sudo zerotier-cli listpeers | grep RELAY | wc -l)
echo "$TIMESTAMP status=$STATUS peers=$PEERS direct=$DIRECT relay=$RELAY" >> "$LOGFILE"
sleep 60
done
Run this script in the background or as a systemd service, and review the log after a day or two to identify patterns in connectivity drops.
Common Issue 6: DNS Resolution Problems
ZeroTier does not automatically provide DNS for network members unless you configure it. If nodes can ping each other by IP but cannot resolve hostnames, you need to set up DNS within the ZeroTier network configuration.
Configuring Managed DNS
In ZeroTier Central, navigate to your network settings and look for the DNS section. You can specify a DNS server and a domain suffix. However, managed DNS requires the ZeroTier client to support it, and some older versions or certain operating systems may not honor the setting. As an alternative, you can configure DNS manually on each node:
# On Linux, add DNS for the ZeroTier interface using systemd-resolved
# First, find the zt interface name
ZT_IFACE=$(sudo zerotier-cli listnetworks | awk 'NR>1 {print $7}')
echo "ZeroTier interface: $ZT_IFACE"
# Set DNS server for the ZeroTier interface
sudo resolvectl dns "$ZT_IFACE" 10.147.17.1
sudo resolvectl domain "$ZT_IFACE" ~zerotier.local
# Verify
resolvectl status "$ZT_IFACE"
Best Practices for ZeroTier Reliability
Beyond fixing specific issues, following best practices will prevent many problems from occurring in the first place. A well-architected ZeroTier deployment is far easier to maintain and troubleshoot.
Use Consistent Naming and Documentation
Assign descriptive names to every node in ZeroTier Central. When you have dozens or hundreds of members, trying to identify nodes by their 10-digit ID is error-prone. Use a naming convention that includes the hostname, role, and location.
Pin Stable Subnets
Always choose ZeroTier subnets that do not conflict with common private ranges used by home routers, office networks, and cloud VPCs. The ranges 10.147.17.0/24 and 172.29.x.0/24 are good candidates because they are rarely used by default configurations.
Automate Deployment with Configuration Management
For fleets of nodes, use configuration management tools to ensure consistent ZeroTier installations. Here is an example Ansible task that installs ZeroTier and joins a network:
- name: Install ZeroTier repository key
apt_key:
url: https://download.zerotier.com/contact%40zerotier.com.gpg
state: present
when: ansible_os_family == "Debian"
- name: Add ZeroTier repository
apt_repository:
repo: "deb http://download.zerotier.com/debian/{{ ansible_distribution_release }} {{ ansible_distribution_release }} main"
state: present
when: ansible_os_family == "Debian"
- name: Install ZeroTier
apt:
name: zerotier-one
state: present
update_cache: yes
when: ansible_os_family == "Debian"
- name: Ensure ZeroTier service is running
service:
name: zerotier-one
state: started
enabled: yes
- name: Join ZeroTier network
command: zerotier-cli join {{ zerotier_network_id }}
register: join_result
changed_when: "'200 join OK' in join_result.stdout"
Regularly Update ZeroTier
ZeroTier is actively developed, and updates frequently include improvements to NAT traversal, peer discovery, and stability. Keep your nodes updated, but test updates in a staging environment first to avoid introducing regressions across your entire fleet.
# Check current version
sudo zerotier-cli info
# Update on Debian/Ubuntu
sudo apt update && sudo apt install --only-upgrade zerotier-one
# Update on macOS (if installed via Homebrew)
brew upgrade zertier
# Update on Windows - download the latest installer from zerotier.com
Implement Logging and Alerting
For production deployments, integrate ZeroTier status checks into your existing monitoring stack. A simple health check script can feed data into Prometheus, Datadog, or any other monitoring system:
#!/usr/bin/env python3
"""ZeroTier health check for monitoring systems."""
import json
import subprocess
import sys
def get_zerotier_status():
try:
result = subprocess.run(
["zerotier-cli", "status", "-j"],
capture_output=True, text=True, timeout=5
)
return json.loads(result.stdout)
except Exception as e:
print(f"zerotier_status:0|g\n# error: {e}")
sys.exit(1)
def get_network_status():
try:
result = subprocess.run(
["zerotier-cli", "listnetworks", "-j"],
capture_output=True, text=True, timeout=5
)
return json.loads(result.stdout)
except Exception:
return []
def main():
status = get_zerotier_status()
online = 1 if status.get("online") else 0
print(f"zerotier_online:{online}|g")
networks = get_network_status()
for net in networks:
nwid = net.get("id", "unknown")
net_status = 1 if net.get("status") == "OK" else 0
print(f"zerotier_network_{nwid}_status:{net_status}|g")
if __name__ == "__main__":
main()
Conclusion
ZeroTier is a powerful tool for building virtual networks, but its peer-to-peer architecture means that troubleshooting requires a different mindset than traditional VPNs. By understanding the roles of node identities, network authorization, NAT traversal, and relay fallbacks, you can systematically diagnose and resolve the vast majority of issues you will encounter. The key is to start with the diagnostic commands — status, listnetworks, and listpeers — and work outward from there. Combine this with solid operational practices like consistent naming, non-overlapping subnets, automated deployments, and proactive monitoring, and your ZeroTier networks will remain reliable and easy to manage even as they scale to hundreds of nodes across diverse network environments.