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
sudouser. One of them, the lighthouse, needs a public IP address (for example a CubePath VPS). In the examples it is203.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:
| Host | Nebula IP | Groups |
|---|---|---|
lighthouse1 | 10.10.0.1 | none |
app1 | 10.10.0.10 | servers |
admin-laptop | 10.10.0.100 | admins |
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.
