Connect external agents (no hub domain)
Nội dung này không tồn tại trong ngôn ngữ của bạn
Lumen agents push outbound. They never accept inbound connections, so
the network problem is one-way: every agent must resolve and reach
LUMEN_HUB_URL. If the hub has a public domain + TLS, you’re done —
follow Hub (Docker Compose) or
Hub (binary) and skip this page.
This page is for the harder case: the hub has no public domain (on-prem, NAT, no public IP, no Let’s Encrypt cert) and agents live outside the hub’s local network (other VPS, branch office, customer site). Three paths in order of recommendation:
| Path | When | Cost |
|---|---|---|
| Tailscale | Mixed personal/customer fleets, fastest setup | Free up to 100 devices, magic DNS |
| Cloudflare Tunnel | You control a domain on Cloudflare; want public URL without exposing hub | Free, needs a CF account |
| Self-signed cert + LAN IP | Fully offline customer, agent reachable on LAN/VPN-of-customer’s-choice | Manual CA install per agent |
The three are not exclusive — pick per-agent. A hub can run Tailscale, Cloudflare Tunnel, and bare HTTPS at the same time.
A. Tailscale (recommended)
Tailscale is a zero-config mesh VPN built on
WireGuard. Hub and every agent join the same tailnet; agents reach the
hub at a stable 100.x.y.z address with no port-forwarding, no DNS, no
public IP on either side.
1. Install Tailscale on the hub
Pick the form matching how the hub runs:
Hub on bare Linux / VM
curl -fsSL https://tailscale.com/install.sh | shsudo tailscale up# Auth in browser, then:tailscale ip -4# e.g. 100.64.10.5 — this is the address agents will useHub via Docker Compose — add a Tailscale sidecar that shares the network namespace with the hub container:
services: tailscale-hub: image: tailscale/tailscale:latest hostname: lumen-hub environment: TS_AUTHKEY: tskey-auth-CHANGE_ME TS_STATE_DIR: /var/lib/tailscale TS_USERSPACE: "false" volumes: - tailscale-hub-state:/var/lib/tailscale - /dev/net/tun:/dev/net/tun cap_add: [NET_ADMIN, SYS_MODULE] restart: unless-stopped
lumen-hub: image: ghcr.io/quanla93/lumen-hub:latest network_mode: service:tailscale-hub # share the tailnet IP depends_on: [tailscale-hub] # … rest of hub config
volumes: tailscale-hub-state:Mint the TS_AUTHKEY at https://login.tailscale.com/admin/settings/keys.
Treat it like a password — anyone with it can join your tailnet.
2. Install Tailscale on each agent
Same one-liner on the agent VM:
curl -fsSL https://tailscale.com/install.sh | shsudo tailscale up --authkey tskey-auth-CHANGE_MEIf the agent itself runs in Docker, use the same sidecar pattern with
network_mode: service:tailscale-agent.
3. Point the agent at the hub’s tailnet IP
In the agent’s compose file or systemd env:
LUMEN_HUB_URL: "http://100.64.10.5:8090"Plain HTTP is fine here — Tailscale already encrypts the link with WireGuard. No certs, no domain, no port-forwarding.
MagicDNS variant
If MagicDNS is on in your tailnet, use the hostname instead of the IP:
LUMEN_HUB_URL: "http://lumen-hub:8090"Stable across hub-tailnet-IP changes.
Headscale is a self-hosted Tailscale control plane. Same shape; swap the install URL and login server. Recommended only if you already self-host one.
B. Cloudflare Tunnel
Cloudflare’s cloudflared opens an outbound connection from the
hub to Cloudflare’s edge. Agents reach the hub at
https://lumen.your-domain.com — no port on the hub is exposed to the
internet, but you get a real domain + a real cert for free.
You need: a domain on Cloudflare (free plan is fine).
1. Create the tunnel
# On the hub machinecurl -L --output cloudflared.deb \ https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.debsudo dpkg -i cloudflared.deb
cloudflared tunnel login # opens browser, pick your domaincloudflared tunnel create lumen# → writes ~/.cloudflared/<UUID>.json2. Route it
cloudflared tunnel route dns lumen lumen.your-domain.comThis creates a CNAME in your Cloudflare DNS pointing to the tunnel.
3. Run it
cloudflared tunnel run --url http://127.0.0.1:8090 lumen# or as a service:sudo cloudflared service installVerify:
curl https://lumen.your-domain.com/healthz# ok4. Point agents at the public URL
LUMEN_HUB_URL: "https://lumen.your-domain.com"Cloudflare terminates TLS at the edge; the hub speaks plaintext to
cloudflared on 127.0.0.1. Agents see a valid public cert; no extra
trust setup needed.
Make sure the hub does not also listen on
0.0.0.0:8090— bind to127.0.0.1:8090so the tunnel is the only reachable surface.
C. Self-signed cert + LAN IP
Use this when the customer’s environment is fully offline (no internet out from the agent) or already has its own VPN/private network you want to reuse. Lumen’s agent uses Go’s default HTTPS client — no insecure flag, no SSL bypass. You must install your CA cert into the system trust store on every agent machine.
1. Generate a CA and a hub cert
One-time on any workstation:
# Generate a self-signed CA (10-year)openssl req -x509 -nodes -newkey rsa:4096 -days 3650 \ -keyout lumen-ca.key -out lumen-ca.crt \ -subj "/CN=Lumen Internal CA"
# Generate the hub server cert, signed by that CA.# SAN must list every name/IP an agent will use to reach the hub.cat >hub.cnf <<EOF[req]distinguished_name = dnreq_extensions = extprompt = no[dn]CN = lumen-hub.lan[ext]subjectAltName = @alt[alt]DNS.1 = lumen-hub.lanDNS.2 = lumen-hubIP.1 = 192.168.1.10EOF
openssl req -nodes -newkey rsa:2048 -keyout hub.key -out hub.csr -config hub.cnfopenssl x509 -req -in hub.csr -CA lumen-ca.crt -CAkey lumen-ca.key -CAcreateserial \ -out hub.crt -days 825 -sha256 -extensions ext -extfile hub.cnfReplace 192.168.1.10 with the LAN IP the agents will use.
2. Put a TLS reverse proxy in front of the hub
The hub itself listens plaintext on :8090; terminate TLS in nginx or
Caddy. Caddy minimal config:
{ auto_https off}
lumen-hub.lan:443 192.168.1.10:443 { tls /etc/caddy/hub.crt /etc/caddy/hub.key reverse_proxy 127.0.0.1:8090}Same shape as Hub (binary) § Putting HTTPS in front.
3. Install the CA on every agent
The agent does standard cert validation. Trust the CA system-wide:
# Debian/Ubuntusudo cp lumen-ca.crt /usr/local/share/ca-certificates/sudo update-ca-certificates
# RHEL/Fedora/Almasudo cp lumen-ca.crt /etc/pki/ca-trust/source/anchors/sudo update-ca-trust
# Alpinesudo cp lumen-ca.crt /usr/local/share/ca-certificates/sudo update-ca-certificatesFor the Docker-Compose agent, mount the CA into the container:
services: lumen-agent: image: ghcr.io/quanla93/lumen-agent:latest volumes: - ./lumen-ca.crt:/usr/local/share/ca-certificates/lumen-ca.crt:ro environment: LUMEN_HUB_URL: "https://lumen-hub.lan" # … # `update-ca-certificates` runs at image build, not at start, so # for Docker the simpler path is mounting into /etc/ssl/certs: # - ./lumen-ca.crt:/etc/ssl/certs/lumen-ca.crt:ro4. Point the agent at the hub
Use the same name/IP that appears in the cert SAN:
LUMEN_HUB_URL: "https://lumen-hub.lan"# orLUMEN_HUB_URL: "https://192.168.1.10"If the agent log shows x509: certificate signed by unknown authority,
the CA isn’t trusted — re-check step 3.
Why no
LUMEN_HUB_INSECURE_SKIP_VERIFY? Bypassing cert validation turns “HTTPS” into “HTTP with extra steps”. The agent token rides in theAuthorizationheader on every request; a MITM with an insecure-skip agent steals the token immediately. If you don’t want to manage a CA, use Tailscale instead — Lumen explicitly does not ship that flag.
Verify
After any of the three paths, the smoke test is the same:
# from the agent machinecurl -s "$LUMEN_HUB_URL/healthz"# → ok
# then start the agent and watch logsdocker compose logs -f lumen-agent# expect: msg=ingested cpu=… ram=… disk=…The host should appear on the dashboard within one agent_interval.
Troubleshooting
dial tcp: lookup lumen-hub.lan: no such host
: DNS not resolving. For Tailscale, enable MagicDNS. For LAN, add an
entry to /etc/hosts on the agent, or use the IP directly.
x509: certificate signed by unknown authority
: Self-signed-cert path only. The CA isn’t in the agent’s system trust
store. Re-run update-ca-certificates (or distro equivalent), and
for Docker make sure the CA is mounted into /etc/ssl/certs/ (the
base image doesn’t re-run update-ca-certificates at start).
x509: certificate is valid for X, not Y
: The hostname/IP the agent is using isn’t in the cert’s
subjectAltName. Re-issue with all the names/IPs the agent will use.
Cloudflare Tunnel works on a laptop but agent gets 530
: The tunnel name in DNS no longer matches the running tunnel
(recreated tunnel?). Re-run cloudflared tunnel route dns lumen lumen.your-domain.com.
Tailscale IP changes
: It can after long downtime. Use MagicDNS hostnames in
LUMEN_HUB_URL, not raw 100.x.y.z IPs.
Hub is also exposed publicly by accident
: Bind to 127.0.0.1:8090 (Compose / binary) so only the tunnel or the
Tailscale interface can reach it.