Nebula is an open-source overlay network originally built at Slack. Each host holds a certificate signed by your own certificate authority, discovers peers through one or more lighthouses and then connects to them directly over encrypted UDP. Group names embedded in the certificates drive a built-in firewall. In this tutorial you will create a CA, set up a lighthouse on a public Ubuntu 24.04 server, join a second host and restrict SSH access by group.

Prerequisites

To follow this tutorial, you will need:

  • Two servers running Ubuntu 24.04 LTS with a non-root sudo user. One of them, the lighthouse, needs a public IP address (for example a CubePath VPS). In the examples it is 203.0.113.10.
  • UDP port 4242 reachable on the lighthouse.
  • A separate, trusted machine (your workstation) to hold the certificate authority key. It can be the lighthouse for a lab, but not in production.

This tutorial uses the overlay range 10.10.0.0/24 with these hosts:

HostNebula IPGroups
lighthouse110.10.0.1none
app110.10.0.10servers
admin-laptop10.10.0.100admins

Step 1 - Downloading the Nebula binaries

Nebula ships as two static binaries: nebula (the daemon) and nebula-cert (the certificate tool). Download the release archive on every host and on your CA machine. Check the releases page for the current version and set it in the variable:

NEBULA_VERSION="v1.9.5"
curl -fLO "https://github.com/slackhq/nebula/releases/download/${NEBULA_VERSION}/nebula-linux-amd64.tar.gz"
tar -xzf nebula-linux-amd64.tar.gz
sudo install -m 0755 nebula nebula-cert /usr/local/bin/

Use nebula-linux-arm64.tar.gz on ARM servers. Confirm the daemon runs:

nebula -version
Version: 1.9.5

Step 2 - Creating the certificate authority

On the CA machine, create a working directory and generate the CA. The CA is valid for one year by default; renew it and re-sign hosts before it expires.

mkdir -p ~/nebula-ca && cd ~/nebula-ca
nebula-cert ca -name "Example Org Nebula CA"

This creates ca.crt, which every host needs, and ca.key, which signs host certificates. Anyone with ca.key can add hosts to your network with any IP and group, so keep it off the network hosts and back it up securely:

chmod 600 ca.key
ls -l
-rw------- 1 your_user your_user 174 ... ca.key
-rw-rw-r-- 1 your_user your_user 243 ... ca.crt

Step 3 - Signing host certificates

Sign one certificate per host with its overlay IP and groups. Groups cannot be changed later without signing a new certificate.

nebula-cert sign -name "lighthouse1" -ip "10.10.0.1/24"
nebula-cert sign -name "app1" -ip "10.10.0.10/24" -groups "servers"
nebula-cert sign -name "admin-laptop" -ip "10.10.0.100/24" -groups "admins"

nebula-cert sign reads ca.crt and ca.key from the current directory by default. Inspect a certificate to confirm the IP and groups:

nebula-cert print -path app1.crt

The output lists the name, ips, groups and validity period of the certificate.

Copy each host's files to it. For the lighthouse:

scp ca.crt lighthouse1.crt lighthouse1.key [email protected]:~

Then, on the lighthouse, move them into place with restrictive permissions:

sudo install -d -m 0750 /etc/nebula
sudo install -m 0644 ~/ca.crt ~/lighthouse1.crt /etc/nebula/
sudo install -m 0600 ~/lighthouse1.key /etc/nebula/
rm ~/ca.crt ~/lighthouse1.crt ~/lighthouse1.key

Repeat for app1 with app1.crt and app1.key.

Step 4 - Configuring the lighthouse

The lighthouse tells hosts where to find each other. It listens on a fixed UDP port and does not need to know about any other lighthouse. Create its configuration:

sudo nano /etc/nebula/config.yml
pki:
  ca: /etc/nebula/ca.crt
  cert: /etc/nebula/lighthouse1.crt
  key: /etc/nebula/lighthouse1.key

static_host_map: {}

lighthouse:
  am_lighthouse: true

listen:
  host: 0.0.0.0
  port: 4242

punchy:
  punch: true

tun:
  dev: nebula1

logging:
  level: info
  format: text

firewall:
  outbound:
    - port: any
      proto: any
      host: any
  inbound:
    - port: any
      proto: icmp
      host: any

Validate the file before starting anything:

sudo nebula -config /etc/nebula/config.yml -test

If the configuration or certificates have a problem, the command prints an error and exits with a non-zero status; otherwise it finishes without errors.

Open the Nebula port in UFW:

sudo ufw allow 4242/udp

Step 5 - Running Nebula as a systemd service

Create a unit file on every host:

sudo nano /etc/systemd/system/nebula.service
[Unit]
Description=Nebula overlay network
Wants=network-online.target
After=network-online.target

[Service]
SyslogIdentifier=nebula
ExecStart=/usr/local/bin/nebula -config /etc/nebula/config.yml
ExecReload=/bin/kill -HUP $MAINPID
Restart=always

[Install]
WantedBy=multi-user.target

Start it on the lighthouse and verify the tunnel interface:

sudo systemctl daemon-reload
sudo systemctl enable --now nebula
ip -br addr show nebula1
nebula1          UNKNOWN        10.10.0.1/24

Step 6 - Configuring a regular host

On app1, the configuration points at the lighthouse. static_host_map maps the lighthouse's Nebula IP to its public address, and listen.port: 0 lets Nebula pick a random port, which helps hole punching behind NAT. The inbound firewall allows ICMP from any Nebula host and SSH only from certificates in the admins group:

sudo nano /etc/nebula/config.yml
pki:
  ca: /etc/nebula/ca.crt
  cert: /etc/nebula/app1.crt
  key: /etc/nebula/app1.key

static_host_map:
  "10.10.0.1": ["203.0.113.10:4242"]

lighthouse:
  am_lighthouse: false
  interval: 60
  hosts:
    - "10.10.0.1"

listen:
  host: 0.0.0.0
  port: 0

punchy:
  punch: true

tun:
  dev: nebula1

logging:
  level: info
  format: text

firewall:
  outbound:
    - port: any
      proto: any
      host: any
  inbound:
    - port: any
      proto: icmp
      host: any
    - port: 22
      proto: tcp
      group: admins

Test the configuration, create the same systemd unit as in Step 5, and start the service:

sudo nebula -config /etc/nebula/config.yml -test
sudo systemctl daemon-reload
sudo systemctl enable --now nebula

Verify the overlay by pinging the lighthouse from app1:

ping -c 3 10.10.0.1
64 bytes from 10.10.0.1: icmp_seq=1 ttl=64 time=0.9 ms

The logs show the handshake with the lighthouse:

sudo journalctl -u nebula -n 20 --no-pager

Look for a line containing Handshake message received with vpnIp=10.10.0.1.

Step 7 - Testing group-based access

Install Nebula on your workstation with the admin-laptop certificate and a configuration like the one in Step 6 (Nebula has packages for macOS and Windows on the releases page). From the laptop, SSH to app1 over the overlay:

The connection works because the laptop's certificate carries the admins group. From the lighthouse, which has no groups, an SSH attempt to 10.10.0.10 hangs, while ping 10.10.0.10 still succeeds. This is the Nebula firewall at work.

Firewall rules support host, group, groups (all listed groups must match) and cidr. After changing rules, reload without dropping tunnels:

sudo systemctl reload nebula

Troubleshooting

Hosts cannot reach the lighthouse. Check that the lighthouse listens with sudo ss -ulnp | grep 4242 and that sudo ufw status allows 4242/udp. The IP and port in static_host_map must be the lighthouse's public address.

Certificate errors in the logs. Run nebula-cert verify -ca /etc/nebula/ca.crt -crt /etc/nebula/app1.crt to confirm the host certificate was signed by the CA it is using and has not expired. Also check the clock with timedatectl, since certificates have validity windows.

Ping works but a service does not. The inbound Nebula firewall is dropping it. Add a rule for the port and group, and also check UFW on the host, which filters traffic on nebula1 like any other interface.

Conclusion

You now have a Nebula overlay with your own certificate authority, a lighthouse on a public server, and hosts whose access is controlled by the groups in their certificates. Next, consider adding a second lighthouse for redundancy, keeping the CA key offline, and scheduling certificate renewal well before the one-year CA lifetime ends.