Nebula - Secure Private Overlay Network for AI Agents
Step-by-step guide to configure Nebula VPN for private AI agent communication in your homelab.

Nebula is a scalable overlay networking tool designed to secure distributed systems. It creates a private, encrypted mesh network where nodes communicate peer-to-peer without exposing services to the public internet.
Perfect for homelab setups where you want your AI agents (Hermes Agent, custom LLM servers, etc.) to communicate privately and securely.
GitHub: https://github.com/slackhq/nebula
Why Nebula for AI Agents?
- Private network: AI agents only accessible within your overlay network
- Peer-to-peer: Direct communication between nodes, no central relay needed
- Zero configuration: Automatic discovery via lighthouse nodes
- Lightweight: Minimal overhead compared to traditional VPNs
- Cloud-native: Works across different cloud providers and homelab setups
Architecture Overview
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Laptop │ │ Homelab Hub │ │ Remote PC │
│ 10.20.0.2 │ │ 10.20.0.3 │ │ 10.20.0.4 │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
└───────────────────┼───────────────────┘
│
┌──────┴──────┐
│ Lighthouse │
│ 10.20.0.1 │
└─────────────┘
All nodes connect to a lighthouse for discovery, then communicate directly via encrypted peer-to-peer tunnels.
Setup Guide
On Lighthouse server
Create nebula/docker-compose.yml:
services:
nebula:
image: nebulaoss/nebula:1.10.3 # latest | https://hub.docker.com/r/nebulaoss/nebula/tags
network_mode: host
cap_add:
- NET_ADMIN
volumes:
- ./config:/config
restart: unless-stopped
Generate Certificate Authority (CA)
cd nebula
docker compose run --rm -w /config --entrypoint /nebula-cert nebula ca -name "Kyzlab, Inc" -duration 87600h
# Output:
# config
# ├── ca.key ← DO NOT share (master key)
# └── ca.crt ← Share with all nodes (public cert)
Generate Lighthouse Certificate
docker compose run --rm -w /config --entrypoint /nebula-cert nebula sign -name "lighthouse" -ip "10.20.0.1/24"
# Output:
# config
# ├── lighthouse.key
# └── lighthouse.crt
Configure Lighthouse
Create nebula/config/config.yml:
mkdir -p config
curl -L -o config/config.yml https://raw.githubusercontent.com/slackhq/nebula/master/examples/config.yml
Modify nebula/config/config.yml as following:
pki:
ca: /config/ca.crt
cert: /config/lighthouse.crt
key: /config/lighthouse.key
static_host_map: {}
lighthouse:
am_lighthouse: true
serve_dns: true
dns:
# The DNS host defines the IP to bind the dns listener to. This also allows binding to the nebula node IP.
host: 10.20.0.1 # ← Nebula IP of Lighthouse
port: 53
hosts: []
listen:
host: "0.0.0.0"
punchy:
punch: true
respond: true
relay:
am_relay: true # ← Nodes having Public IPs
use_relays: true
firewall:
outbound:
# Allow all outbound traffic from this node
- port: any
proto: any
host: any
inbound:
# Allow all traffic between any nebula hosts
- port: any
proto: any
host: any
Start Nebula Lighthouse:
docker-compose up -d
Allow the port and reload (if needed)
sudo ufw allow 4242/udp
sudo ufw reload
Generate Node Certificates
On Lighthouse server, for each node (laptop, homelab server, remote PC):
# Laptop
docker compose run --rm -w /config --entrypoint /nebula-cert nebula sign -name "laptop" -ip "10.20.0.2/24"
# Homelab server
docker compose run --rm -w /config --entrypoint /nebula-cert nebula sign -name "homelab" -ip "10.20.0.3/24"
# Remote PC
docker compose run --rm -w /config --entrypoint /nebula-cert nebula sign -name "remote-pc" -ip "10.20.0.4/24"
Each generates:
nebula/config/<node-name>.crtnebula/config/<node-name>.key
Now that we have the node certificates, copy the following 3 files to each corresponding node:
nebula/config/ca.crt(identical on all machines)nebula/config/<node-name>.crtnebula/config/<node-name>.key
Then deploy Nebula on each node/host.
On each node
Create nebula/docker-compose.yml (the same as docker-compose.yml of the Lighthouse):
services:
nebula:
image: nebulaoss/nebula:1.10.3 # latest | https://hub.docker.com/r/nebulaoss/nebula/tags
network_mode: host
cap_add:
- NET_ADMIN
volumes:
- ./config:/config
restart: unless-stopped
Create nebula/config/config.yml:
mkdir -p config
curl -L -o config/config.yml https://raw.githubusercontent.com/slackhq/nebula/master/examples/config.yml
Rename:
nebula/config/<node-name>.crt→nebula/config/node.crtnebula/config/<node-name>.key→nebula/config/node.key
Modify nebula/config/config.yml as following:
pki:
ca: /config/ca.crt # ← the same as "ca.crt" on Lighthouse server!
cert: /config/node.crt
key: /config/node.key
static_host_map:
# Syntax → "{nebula ip}": ["{routable ip/dns name}:{routable port}"]
"10.20.0.1": ['<LIGHTHOUSE PUBLIC IP OR DNS>:4242']
lighthouse:
am_lighthouse: false
hosts:
- "10.20.0.1"
punchy:
punch: true
respond: true
relay:
relays: # ← Nebula IPs of the nodes having public IPs
- 10.20.0.1
am_relay: false
use_relays: true
firewall:
outbound:
# Allow all outbound traffic from this node
- port: any
proto: any
host: any
inbound:
# Allow all traffic between any nebula hosts
- port: any
proto: any
host: any
Important: Replace <LIGHTHOUSE PUBLIC IP OR DNS> with your lighthouse server's public IP or domain.
Start Nebula Services:
docker compose up -d
Now, AI agents on all nodes can communicate peer-to-peer privately and directly.
Verify the Overlay Network
Ping Between Nodes
# From laptop to homelab
ping 10.20.0.3
# From homelab to remote PC
ping 10.20.0.4
SSH Over Nebula Network
ssh user@10.20.0.3
Transfer Large Files
scp largefile.tar.gz user@10.20.0.3:/home/user/
Troubleshooting
- Can't ping: Check firewall rules, ensure both nodes have Nebula running
- Lighthouse not found: Verify
static_host_maphas correct lighthouseIP:port. - Certificate errors: Ensure
ca.crtis identical on all nodes - Slow connections: Check NAT traversal; add
punch: trueto config if behind NAT