Skip to main content
🌐 Zenoh

ROS 2 Zenoh Guide 2026

Zenoh is a pub/sub protocol that works natively over WiFi, 5G, and WAN — no multicast, no VPN required. With rmw_zenoh, you get cross-network ROS 2 communication for multi-robot fleets with a single router process.

Why Zenoh over DDS?

FeatureDDS (CycloneDDS/FastDDS)Zenoh (rmw_zenoh)
DiscoveryUDP multicast (LAN only)Peer or router-based (works on WiFi/WAN)
Cross-networkRequires bridge/VPNNative via Zenoh router
WiFi robotsMulticast often filtered by APUnicast — no AP restrictions
BandwidthCDR overhead per messageCompact encoding, optional compression
Multi-hostDomain bridge or DDS relaySingle `zenohd` router process
Intra-processSupported (DDS)Supported (shared memory segment)

Install rmw_zenoh (ROS 2 Jazzy / Humble)

# Option 1: apt (Jazzy only — Ubuntu 24.04)
sudo apt install ros-jazzy-rmw-zenoh-cpp

# Option 2: build from source (Humble + Jazzy)
mkdir -p ~/zenoh_ws/src && cd ~/zenoh_ws/src
git clone https://github.com/ros2/rmw_zenoh.git
cd ~/zenoh_ws
rosdep install --from-paths src --ignore-src -r -y
colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release

# Zenoh router binary (needed for multi-host)
cargo install zenohd
# OR download pre-built:
# https://github.com/eclipse-zenoh/zenoh/releases

Switch RMW to Zenoh

Set RMW_IMPLEMENTATION before sourcing ROS 2. All nodes in that shell use Zenoh:

# Single session
export RMW_IMPLEMENTATION=rmw_zenoh_cpp
source /opt/ros/jazzy/setup.bash

# Persist in .bashrc
echo 'export RMW_IMPLEMENTATION=rmw_zenoh_cpp' >> ~/.bashrc

# Verify
ros2 doctor --report | grep -A2 middleware

Note: All nodes communicating together must use the same RMW. Mixed-RMW setups require a bridge (ros1_bridge pattern).

Single-Host Setup (No Router)

On a single machine, Zenoh uses peer-to-peer discovery automatically — no router needed:

# Terminal 1 — publisher
export RMW_IMPLEMENTATION=rmw_zenoh_cpp
ros2 topic pub /chatter std_msgs/msg/String "data: hello zenoh" --rate 5

# Terminal 2 — subscriber
export RMW_IMPLEMENTATION=rmw_zenoh_cpp
ros2 topic echo /chatter

# Confirm Zenoh is active
ros2 topic info /chatter -v | grep -A3 "node name"

Multi-Host Setup: Zenoh Router

For WiFi robots or machines on different subnets, run zenohd on a reachable host (your laptop or server):

# === On the router machine (e.g. 192.168.1.100) ===
zenohd --listen tcp/0.0.0.0:7447

# Alternatively with a config file for more control:
zenohd --config /etc/zenoh/router.json5
# === On Robot 1 (e.g. 192.168.1.50) ===
# Create Zenoh config pointing to the router
cat > ~/zenoh_robot.json5 << 'EOF'
{
  mode: "client",
  connect: {
    endpoints: ["tcp/192.168.1.100:7447"]
  }
}
EOF

export RMW_IMPLEMENTATION=rmw_zenoh_cpp
export ZENOH_ROUTER_CHECK_ATTEMPTS=-1
ros2 run demo_nodes_cpp talker

# === On Laptop ===
cat > ~/zenoh_laptop.json5 << 'EOF'
{
  mode: "client",
  connect: { endpoints: ["tcp/192.168.1.100:7447"] }
}
EOF

export RMW_IMPLEMENTATION=rmw_zenoh_cpp
ros2 topic echo /chatter

Config location: rmw_zenoh reads $ZENOH_ROUTER_CONFIG_URI or defaults to peer mode. Point it to your JSON5: export ZENOH_ROUTER_CONFIG_URI=file:///home/ubuntu/zenoh_robot.json5

Router JSON5 Config Reference

// /etc/zenoh/router.json5 — production router
{
  mode: "router",
  listen: {
    endpoints: ["tcp/0.0.0.0:7447"]
  },
  // Allow robots to scout the router via UDP multicast on same LAN
  scouting: {
    multicast: {
      enabled: true,
      address: "224.0.0.224:7446",
      interface: "auto"
    }
  },
  // Shared memory for intra-host zero-copy
  plugins_loading: { enabled: true },
  plugins: {
    shared_memory: { enabled: true }
  },
  // Limit topic bandwidth (bytes/s per key expression)
  // qos: { ... }  — available in Zenoh 1.x
}

Multi-Robot Fleet Pattern

Namespace each robot to avoid topic collisions, then remap on the router for fleet-level aggregation:

# Robot 1
export RMW_IMPLEMENTATION=rmw_zenoh_cpp
ros2 launch my_robot robot.launch.py robot_namespace:=robot1

# Robot 2
export RMW_IMPLEMENTATION=rmw_zenoh_cpp
ros2 launch my_robot robot.launch.py robot_namespace:=robot2

# Fleet manager on laptop — subscribe to all robots at once
export RMW_IMPLEMENTATION=rmw_zenoh_cpp
ros2 topic echo /robot1/scan   # laser scan from robot 1
ros2 topic echo /robot2/scan   # laser scan from robot 2

# With wildcards (Zenoh key expression)
# ros2 topic echo /**/scan     # NOT standard ROS 2, but works via Zenoh API directly

Bandwidth & Latency Tuning

Shared Memory (same host, zero-copy)

# Enable SHM in Zenoh config — both publisher and subscriber must enable it
export ZENOH_SHM=enabled
# Verify: ros2 topic pub latency drops to <5µs for large messages

Reduce discovery traffic on WiFi

# Set multicast scouting to false on robots (they connect to router only)
# In zenoh_robot.json5:
scouting: { multicast: { enabled: false } }

QoS: limit image topic to 10 Hz on WAN link

ros2 topic pub /camera/image sensor_msgs/msg/Image \
  --qos-history keep-last --qos-depth 1 --rate 10

Zenoh CLI Diagnostics

zenoh-scout

Discover all Zenoh peers and routers on the network

z_pub --key 'ros2/chatter' --value 'hello'

Publish directly via Zenoh key (bypasses ROS 2 type system)

z_sub --key 'ros2/**'

Subscribe to all ROS 2 topics via Zenoh wildcard

ros2 doctor --report | grep middleware

Confirm rmw_zenoh is active in current shell

RUST_LOG=zenoh=debug zenohd

Run router with debug logging for connection issues

ros2 topic bw /scan --rmw-implementation rmw_zenoh_cpp

Measure bandwidth of a topic over Zenoh

Common Issues

Nodes don't discover each other across WiFi

WiFi APs block UDP multicast by default. Switch robots to client mode with a router: set mode:'client' and connect to zenohd IP:7447. Peer mode requires multicast which most APs filter.

ZENOH_ROUTER_CHECK_ATTEMPTS timeout on startup

Set ZENOH_ROUTER_CHECK_ATTEMPTS=-1 to disable the router check (useful when running without a router in peer mode). Or start zenohd before any ROS 2 nodes.

ros2 topic list shows topics but echo gets nothing

Mismatched QoS. Zenoh maps ROS 2 QoS but reliability must match. Try --qos-reliability best_effort on both sides, or set Transient Local for late-joining subscribers.

Next Steps