**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](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`:

```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)**

```bash
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**

```bash
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`:

```bash
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:

```yaml
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:

```bash
docker-compose up -d
```

Allow the port and reload (if needed)

```bash
sudo ufw allow 4242/udp
sudo ufw reload
```

**Generate Node Certificates**

On Lighthouse server, for each node (laptop, homelab server, remote PC):

```bash
# 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>.crt`
- `nebula/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>.crt`
- `nebula/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):

```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
```

Create `nebula/config/config.yml`:

```bash
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.crt`
- `nebula/config/<node-name>.key` → `nebula/config/node.key`

Modify `nebula/config/config.yml` as following:

```yaml
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:

```bash
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_

```bash
# From laptop to homelab
ping 10.20.0.3

# From homelab to remote PC
ping 10.20.0.4
```

_SSH Over Nebula Network_

```bash
ssh user@10.20.0.3
```

_Transfer Large Files_

```bash
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_map` has correct lighthouse `IP:port`.
- **Certificate errors**: Ensure `ca.crt` is **identical on all nodes**
- **Slow connections**: Check NAT traversal; add `punch: true` to config if behind NAT

## References

- [Nebula GitHub](https://github.com/slackhq/nebula)
- [Quick Start Guide](https://nebula.defined.net/docs/guides/quick-start/)
- [Ubuntu Configuration Guide](https://oneuptime.com/blog/post/2026-03-02-how-to-configure-nebula-mesh-vpn-on-ubuntu/view)
