# ROS 2 QoS tutorial: reliability, durability and depth

> Fix silent ROS 2 topics: how QoS reliability, durability and depth interact, which profiles to use, and how to diagnose a mismatch fast.

Canonical: https://roboticsengineering.in/ros2-qos-tutorial/ | Published: 2026-10-28 | Updated: 2026-10-28 | Author: Jignesh Panchal

**Most “my subscriber receives nothing” bugs in ROS 2 are QoS incompatibilities, not network problems.** The publisher and subscriber must agree on reliability and durability before a single message flows.

This guide explains the QoS policies, the defaults, the four settings that cause almost every incompatibility, and the command that tells you exactly what each endpoint is offering.

## What QoS controls

Quality of Service is a contract between a publisher and a subscriber. It decides whether messages are retried, whether late joiners get old data, how many messages are buffered, and how long a sample stays valid.

| Policy | Common values | Effect |
| --- | --- | --- |
| Reliability | reliable, best effort | Retries and acknowledgements, or fire and forget |
| Durability | volatile, transient local | Late subscribers get old samples or not |
| History | keep last, keep all | Which samples are buffered |
| Depth | 1 to hundreds | How many samples are buffered |
| Deadline | duration | Expected update period |
| Lifespan | duration | How long a sample remains valid |
| Liveliness | automatic, manual | How a node proves it is alive |

Two policies decide compatibility: reliability and durability. A subscriber asking for reliable data cannot connect to a publisher offering best effort. A subscriber asking for transient local data cannot connect to a publisher offering volatile.

## The defaults

ROS 2 defaults to reliable, volatile, keep last, depth 10. That is a sensible default for commands and state. It is a poor default for high-rate sensor data, where reliable delivery can back up the publisher and add latency.

- **Commands and services:** reliable, volatile, small depth.
- **Sensor streams:** best effort, volatile, depth 5 or 1.
- **Maps and static data:** reliable, transient local, depth 1.
- **Long-running state:** reliable, transient local, depth 1.
- **High-rate telemetry:** best effort, volatile, shallow queue.

## Finding the incompatibility

The first command to run when a topic is silent is `ros2 topic info` with the verbose flag. It lists every publisher and subscriber with its QoS profile.

```
ros2 topic info /scan --verbose
ros2 topic echo /scan --qos-reliability best_effort
ros2 topic hz /scan
```

If the publisher offers best effort and your subscriber asks for reliable, no data arrives and no error appears in the terminal. That silence is the classic symptom. Set the subscriber to best effort, or change the publisher if you control it.

The same applies in reverse with durability. A map published with transient local can be received by a late subscriber; the same topic published volatile only reaches subscribers that were already connected. That is why a saved map sometimes appears only when you restart the node in the right order.

## QoS profiles in code

Rather than setting each policy, use a named profile where one fits. The sensor data profile is best effort, volatile, keep last, depth 5. The system default is reliable, volatile, keep last, depth 10.

```
from rclpy.qos import qos_profile_sensor_data

self.create_subscription(LaserScan, '/scan', self.callback,
                         qos_profile_sensor_data)
```

When you need a custom profile, build it explicitly and keep it in one place. Scattering QoS settings across files guarantees a mismatch eventually.

```
from rclpy.qos import QoSProfile, ReliabilityPolicy, DurabilityPolicy

latched = QoSProfile(depth=1)
latched.reliability = ReliabilityPolicy.RELIABLE
latched.durability = DurabilityPolicy.TRANSIENT_LOCAL
```

## Reliable is not always better

Reliable delivery guarantees the message eventually arrives, retrying as needed. On a congested Wi-Fi link, that can mean an ever-growing queue, stale data and increasing latency. For a camera or lidar stream, the newest frame matters and an old frame is worthless.

- Best effort drops late messages instead of queueing them.
- Reliable can hide a bandwidth problem until the queue explodes.
- Depth controls how much backlog you are willing to hold.
- For control commands, reliable is usually correct.
- For perception input, best effort is usually correct.

## Timeouts, deadlines and liveliness

A deadline declares the expected update rate. A subscriber can detect that a publisher has missed it. Liveliness declares how a node proves it is still running. Both are useful for safety-relevant monitoring, and both are easy to misconfigure into false alarms.

Start with reliability and durability. Add deadline and liveliness only when you are building a watchdog that genuinely needs them, and test the behaviour when a node is paused, not just when it crashes.

## Overriding QoS from a launch file

Most drivers and Nav2 nodes expose QoS as parameters. Overriding it in the launch file keeps the change visible and reversible, and avoids editing a package you do not own.

```
# params/scan.yaml
/scan_filter:
  ros__parameters:
    qos_overrides:
      /scan.subscription:
        reliability: best_effort
        depth: 5
```

The node must have QoS overrides enabled, which is the default for rclcpp and rclpy nodes. Once the override is applied, the node reports the new policy in `ros2 topic info --verbose`, which is the easiest way to confirm it took effect.

## Recording and replaying sensor data

`ros2 bag record` subscribes with a default profile that may not match a best-effort sensor stream. On a lidar topic this shows up as a recording with almost no messages, or a warning about incompatible QoS. Pass a matching profile explicitly.

```
ros2 bag record /scan /imu/data --qos-profile sensor_data
ros2 bag play ros1_bag --qos-profile sensor_data
```

When replay, publisher and subscriber policies disagree, the bag plays silently and the subscriber sees nothing. The same verbose topic command diagnoses it: compare the bag publisher profile with the node subscription profile.

## Choosing a depth that does not hurt

Depth is the size of the queue. On a control topic, a deep queue means stale commands are still delivered after a pause, which is dangerous. On a sensor topic, a deep queue means memory growth and latency under load. Depth 1 keeps the newest command, which is usually what a controller wants.

- Control commands: depth 1, reliable, volatile.
- Joint states and odometry: depth 5 to 10, reliable.
- Camera and lidar: depth 1 to 5, best effort.
- Static maps and descriptions: depth 1, transient local.
- Diagnostics: depth 10, reliable, unless bandwidth is tight.

## Debugging checklist

- Confirm the topic name and message type match exactly.
- Run `ros2 topic info --verbose` and compare QoS on both sides.
- Check that the publisher is actually publishing with `ros2 topic hz`.
- Try a matching QoS from the command line before changing code.
- Check the ROS domain ID; two machines on different domains never see each other.
- Check the RMW implementation is the same on both ends.
- Look for a firewall blocking multicast discovery.

Related: [ROS 2 topics, services and actions](https://roboticsengineering.in/ros2-topics-services-actions/), [ROS 2 tutorial for beginners](https://roboticsengineering.in/ros2-tutorial-beginners/), [TF2 tutorial](https://roboticsengineering.in/ros2-tf2-tutorial/), [sensor fusion](https://roboticsengineering.in/ros2-sensor-fusion-robot-localization/), [micro-ROS](https://roboticsengineering.in/micro-ros-tutorial/).

**Why is my ROS 2 subscriber not receiving messages?**

The most common cause is a QoS mismatch, usually a reliable subscriber on a best-effort sensor topic. Run ros2 topic info –verbose and compare reliability and durability on both sides.

**What is the difference between reliable and best effort QoS?**

Reliable retries until delivery and keeps older messages queued. Best effort sends once and drops what cannot be delivered promptly, which suits high-rate sensor data.

**What does transient local durability do?**

It keeps the last sample available so subscribers that connect later still receive it. It is used for maps, static transforms and late-joining dashboards.

**What are the default QoS settings in ROS 2?**

Reliable, volatile, keep last, depth 10. Many sensor drivers override this with a sensor data profile of best effort, volatile, keep last, depth 5.

**How do I set QoS from the command line?**

Pass flags such as –qos-reliability best_effort and –qos-durability transient_local to ros2 topic echo, or use a saved profile with –qos-profile.

Run the talker and listener examples from [the WSL2 setup guide](https://roboticsengineering.in/ros2-windows-wsl2-setup/) first, then change QoS profiles and watch what drops.

Inactive nodes should not publish at all, and the [lifecycle nodes tutorial](https://roboticsengineering.in/ros2-lifecycle-nodes-tutorial/) explains which callbacks own the publishers.

## Sources and further reading

- [ROS 2 documentation](https://docs.ros.org/) — QoS concepts and policy reference.
- [ROS 2 design documents](https://design.ros2.org/) — QoS design rationale.
- [ROS 2 tutorials](https://docs.ros.org/en/rolling/Tutorials/) — topic inspection and debugging.
- [RMW implementations](https://github.com/ros2/rmw) — middleware behaviour and compatibility.
