ROS 2 QoS Explained: Why Your Topics Silently Drop Messages in the Field
Tom Cosgrove,

TL;DR: ROS 2 QoS (Quality of Service) policies control how DDS delivers your messages. A common field failure is a QoS mismatch: a publisher and a subscriber whose ros 2 qos reliability durability settings are incompatible do not match, so the topic looks alive in ros2 topic list but your callback never fires. Use BEST_EFFORT + small history depth for high-rate sensors, RELIABLE + TRANSIENT_LOCAL for latched configuration and map data, and consider RELIABLE + depth 1 for velocity commands. Diagnose with ros2 topic info /topic --verbose, which prints the QoS profile of discovered endpoints. Everything below can be tested on ROS 2 Humble or Jazzy systems, including a Leo Rover.
Why Does ROS 2 QoS Matter More in the Field Than on Your Desk?
On a wired desktop, many ROS 2 QoS configurations appear to work. Localhost loopback has effectively zero packet loss, ample bandwidth by robotics standards, and no radio interference. The moment you move the same graph onto a mobile robot with a Wi-Fi link to a ground station, three things change at once: the link drops packets, the available bandwidth fluctuates, and nodes start and stop at different times.
ROS 2 is built on DDS (Data Distribution Service), and DDS exposes a set of per-endpoint policies that govern delivery semantics. In ROS 1, TCPROS and a queue size covered many common cases, although ROS 1 also supported other transports. In ROS 2, each publisher and each subscriber declares a QoS profile, and the middleware establishes a match only when those profiles are compatible. Incompatibility is often silent unless you register QoS event callbacks or inspect endpoint information.
That silence is what makes ROS 2 QoS worth understanding before you deploy. A field robot that stops responding to /cmd_vel because a teleop node used a different reliability setting than the base controller is not necessarily a hardware problem, and ros2 node list alone will not reveal the cause. The diagnostic path runs through QoS.
What Are the Core ROS 2 QoS Policies You Actually Need?
The ROS 2 documentation defines a larger set of policies, but five of them cover the vast majority of practical ROS 2 QoS decisions on a mobile robot. Each policy is set independently on the publisher and on the subscriber.
Reliability
Reliability decides whether the middleware retransmits lost samples.
- RELIABLE – the middleware acknowledges and retransmits samples subject to the publisher’s history and resource limits. This can improve delivery at the cost of latency spikes and extra traffic when the link degrades.
- BEST_EFFORT – the middleware sends once and does not retransmit. Lost samples are simply gone.
Compatibility rule: a RELIABLE subscriber cannot receive from a BEST_EFFORT publisher. The publisher offers a weaker guarantee than the subscriber requests, so DDS does not match the endpoints. The reverse works: a BEST_EFFORT subscriber can receive from a RELIABLE publisher.
Durability
Durability decides whether late-joining subscribers receive previously published samples.
- VOLATILE – nothing is retained for late joiners. A subscriber that starts after the publisher gets only new messages.
- TRANSIENT_LOCAL – the publisher retains samples for late joiners, subject to its history and resource limits. With
KEEP_LAST, the number retained is determined by history depth. This is the ROS 2 equivalent of a ROS 1 latched topic.
Compatibility rule: a TRANSIENT_LOCAL subscriber cannot receive from a VOLATILE publisher. This can occur with /map, /tf_static, and robot description topics, where a tool expects retained data that a custom publisher never offered.
History and Depth
History defines how many samples an endpoint retains, subject to DDS resource limits.
- KEEP_LAST with a
depthvalue – retains the lastdepthsamples. Older samples can be discarded. - KEEP_ALL – retains samples subject to the underlying DDS resource limits.
History and depth are not generally part of the publisher/subscriber compatibility check. They do not normally cause a connection failure, but they can cause message loss when publishers, middleware queues, executors, or callbacks cannot keep up. A depth of 1 on a camera subscription whose callback takes 80 ms at 30 Hz means many frames may be dropped. That drop can also be silent, which is why depth deserves as much attention as reliability.
Deadline
Deadline declares the maximum expected period between consecutive messages. A publisher offers a deadline; a subscriber requests one. If the publisher’s offered period is longer than the subscriber’s requested period, the endpoints are incompatible and will not match.
When matched, a registered QoS event callback can report deadline misses, which is a clean way to detect a stalled sensor without writing a separate watchdog timer. On a field robot, a deadline on /scan or /imu/data can turn a silent sensor failure into an explicit event.
Liveliness and Lifespan
Two more policies are worth knowing even if you use them less often. Liveliness tracks whether a publisher is still considered alive, with an associated lease duration. Lifespan sets how long a sample remains valid before the middleware discards it, which can prevent a subscriber from acting on stale commands after a connectivity gap.
How Do You Diagnose a QoS Mismatch in Under a Minute?
A common symptom is that the topic exists, the publisher reports publishing, and the subscriber callback never runs. Here is a diagnostic sequence for ROS 2 Humble and Jazzy.
Start by confirming the topic is present and counting endpoints:
ros2 topic list -tros2 topic info /scan
If you see one publisher and one subscriber but no data, inspect the actual QoS profiles of discovered endpoints:
ros2 topic info /scan --verbose
The output lists publishers and subscribers by node name, with their QoS profiles, including reliability, durability, lifespan, deadline, liveliness, history, and topic type. Compare the publisher block against the subscriber block. A case where the subscriber requests a stronger guarantee than the publisher offers is a likely mismatch.
Verify whether data is flowing at all, independently of your node:
ros2 topic hz /scanros2 topic echo /scan --qos-reliability best_effort --qos-durability volatile
The --qos-* flags on ros2 topic echo are useful when diagnosing sensor topics. The CLI’s usual default subscription is reliable and volatile, so echoing a best-effort sensor topic without an override can reproduce the mismatch you are trying to debug. If echo with explicit best-effort flags works and your node does not, the problem is likely in your node’s QoS profile rather than the sensor.
For deeper inspection, enable logging appropriate for your selected RMW and DDS implementation. QoS incompatibility diagnostics and their configuration are vendor-specific, so consult the middleware’s documentation rather than relying on ROS logger settings alone.
ROS 2 also provides QoS event callbacks. Registering an incompatible-QoS callback on a subscription means your node can report a mismatch instead of sitting silent, which is far better behavior on a robot deployed somewhere you cannot reach with a laptop.
What QoS Profiles Should You Use for Sensors Versus Commands?
ROS 2 ships with predefined QoS profiles that cover most needs. Choosing between them is the main practical ROS 2 QoS decision you make per topic.
High-rate sensor data
Lidar scans, camera images, IMU streams, and point clouds share a property: the newest sample often makes the previous one obsolete. Retransmitting a 50 ms old scan over a congested Wi-Fi link can delay the current one while adding little value. Use the sensor data profile when this trade-off is appropriate:
rclcpp::QoS qos = rclcpp::SensorDataQoS();
auto pub = create_publisher<sensor_msgs::msg::LaserScan>("scan", qos);
In Python:
from rclpy.qos import qos_profile_sensor_data
self.sub = self.create_subscription(LaserScan, 'scan', self.cb, qos_profile_sensor_data)
This profile is BEST_EFFORT, VOLATILE, KEEP_LAST with depth 5. Dropping a scan under load is often acceptable; stalling the entire pipeline is not.
Velocity commands and control
Commands are different. A dropped /cmd_vel message on a differential drive base can mean the robot keeps its previous velocity for one more cycle. A shallow queue helps the robot act on the newest intent rather than draining a backlog of stale commands:
rclcpp::QoS qos(rclcpp::KeepLast(1));
qos.reliable();
qos.durability_volatile();
auto pub = create_publisher<geometry_msgs::msg::Twist>("cmd_vel", qos);
Depth 1 is deliberate. If the controller falls behind, you want the latest command, not a queue of commands issued seconds ago. Pair this with a timeout in the base controller that zeroes velocity when no command arrives within a defined window. On the Leo Rover, this pattern matters because the operator link is Wi-Fi and the firmware needs a deterministic failsafe when the link degrades.
Latched configuration, maps, and static transforms
Data published once that every future subscriber needs requires a TRANSIENT_LOCAL publisher and a subscriber that also requests TRANSIENT_LOCAL:
rclcpp::QoS qos(rclcpp::KeepLast(1));
qos.reliable();
qos.transient_local();
auto pub = create_publisher<nav_msgs::msg::OccupancyGrid>("map", qos);
In Python:
from rclpy.qos import QoSProfile, QoSReliabilityPolicy, QoSDurabilityPolicy, QoSHistoryPolicy
qos = QoSProfile(
history=QoSHistoryPolicy.KEEP_LAST,
depth=1,
reliability=QoSReliabilityPolicy.RELIABLE,
durability=QoSDurabilityPolicy.TRANSIENT_LOCAL)
self.pub = self.create_publisher(OccupancyGrid, 'map', qos)
A subscriber using default volatile QoS on a TRANSIENT_LOCAL publisher can match and receive newly published samples, but it will not receive the retained historical sample. This asymmetry explains a frequent confusion: the connection works, yet the map never appears until the publisher republishes.
Adding a deadline for field monitoring
For a sensor expected at 10 Hz, a deadline slightly above the nominal period gives you a failure signal:
rclcpp::QoS qos = rclcpp::SensorDataQoS();
qos.deadline(std::chrono::milliseconds(150));
Register the corresponding event callback and log or escalate when it fires. This is far more useful in research deployments than discovering after the fact that a lidar went quiet halfway through a dataset.
How Do You Test QoS Behavior Before You Deploy?
Testing ROS 2 QoS on a desk can give misleading results because localhost does not normally drop packets. Two practical approaches make the behavior visible.
First, write a minimal publisher and subscriber pair with deliberately mismatched profiles and confirm you can recognize the signature in ros2 topic info --verbose. Knowing what the mismatch looks like in the output is more valuable in the field than remembering the compatibility table.
Second, introduce artificial loss and latency on the network interface to approximate degraded field conditions. On Linux, tc with the netem queueing discipline does this. Applied to a Wi-Fi interface, it lets you observe how a reliable high-rate topic behaves when retransmissions accumulate, and how a best-effort topic can hold latency while shedding samples. Treat the resulting numbers as indicative of your specific hardware and link, not as universal figures.
A reproducible checklist before any field run:
- Run
ros2 topic info --verboseon every topic in the critical control and perception path and record the profiles. - Confirm that no subscriber requests a stronger reliability, durability, deadline, or liveliness guarantee than its publisher offers.
- Check that high-rate topics use
BEST_EFFORTwhere appropriate and that topics requiring retained data useTRANSIENT_LOCALon both ends. - Verify history depth against measured callback execution time at the expected publish rate.
- Register incompatible-QoS and deadline-missed callbacks on safety-relevant subscriptions where supported by the selected middleware.
- Repeat the checks over the real wireless link, not over localhost or Ethernet.
What Else Affects Message Delivery Beyond QoS?
QoS is necessary but not sufficient. Several other factors produce symptoms that look like QoS problems.
Large messages such as uncompressed camera frames or dense point clouds get fragmented by the DDS transport. On UDP, a single lost fragment can invalidate the whole sample. With BEST_EFFORT, that can mean losing the entire frame rather than a slice of it. Reducing resolution, compressing on the robot, or switching to a shared-memory transport for on-board communication addresses the root cause, which QoS tuning alone cannot.
Executor configuration also matters. A single-threaded executor with a slow callback blocks every other callback in the same node regardless of QoS, and the resulting queue overflow can resemble network loss at first glance. Check message rates and callback execution behavior at both the publishing and subscribing sides before blaming the link.
Finally, the ROS middleware implementation and its default DDS configuration influence behavior. Vendor-specific XML profiles can change buffer sizes, discovery behavior, and multicast settings in ways that interact with QoS. When a configuration works on one machine and not another, compare RMW_IMPLEMENTATION and any vendor profile environment variables before rewriting application code. Teams building custom robotic platforms often standardize these settings across the fleet precisely to eliminate this class of variability.
Frequently Asked Questions About ROS 2 QoS
Why does my topic show a publisher and a subscriber but no messages arrive?
That can be a ROS 2 QoS mismatch signature. DDS discovery can list endpoints independently of whether they matched, so ros2 topic info can count both while no data connection exists. Run ros2 topic info /your_topic --verbose and compare the reliability, durability, deadline, and liveliness settings on each side. A reliable subscriber paired with a best-effort publisher, or a transient-local subscriber paired with a volatile publisher, will not match.
Which ROS 2 QoS combinations are incompatible?
Common incompatible pairings are a RELIABLE subscriber with a BEST_EFFORT publisher, and a TRANSIENT_LOCAL subscriber with a VOLATILE publisher. Deadline is also checked: a subscriber requesting a shorter deadline period than the publisher offers will not match. Liveliness settings can also affect compatibility. The general rule is that the publisher must offer at least the guarantee the subscriber requests.
Does RELIABLE guarantee that no message is ever lost?
No. RELIABLE requests acknowledged delivery and retransmission subject to the publisher’s history and resource limits. If the publisher uses KEEP_LAST with depth 10 and produces faster than the link can drain the queue, samples can be replaced or dropped before delivery completes. Reliability controls retransmission; history and depth control what is available to retransmit.
What history depth should you set for a camera topic?
Set it based on measured callback duration rather than a default. If the callback takes longer than the publish period, no depth saves you from falling behind; it only changes how many frames can accumulate before they are dropped. For perception pipelines that should always process the newest frame, depth 1 with best-effort reliability is often appropriate. For recording and offline processing, a larger depth with reliable delivery may be more appropriate if the transport and storage pipeline can sustain the rate.
How do you subscribe to a best-effort topic from the command line?
Pass the QoS override explicitly: ros2 topic echo /scan --qos-reliability best_effort. Without the flag, the CLI normally subscribes with reliable QoS and will not match a best-effort publisher. For other tools, check the available options in your ROS 2 distribution: ros2 topic hz supports QoS options, while rosbag QoS overrides are typically provided through a QoS override file.
Can QoS be changed after a node starts?
No. QoS profiles of existing endpoints cannot be modified at runtime. A node can destroy and recreate an endpoint with a different profile. Another practical approach is to expose the relevant settings as ROS parameters read during node construction, or to use the QoS overrides feature, which lets you set profiles from a parameter file at launch without recompiling.
Does QoS behave the same across different DDS vendors?
The policy semantics and compatibility rules are defined by the DDS specification and are broadly consistent across implementations. Defaults for buffer sizes, discovery, transport behavior, and support for particular event reporting differ by vendor, which can change observable performance under load even with identical QoS profiles. When comparing behavior between machines, verify that RMW_IMPLEMENTATION matches on both.
Where to Take This Next
Getting ROS 2 QoS right is an important part of making a robot degrade gracefully when the wireless link weakens rather than stop responding without a clear error. Audit the profiles on your control and perception topics, add incompatible-QoS and deadline callbacks to safety-relevant endpoints where supported, and test over the real radio link before you commit to a deployment.
If you are building a platform where these decisions need to be made once and applied consistently across a fleet, Fictionlab’s team works on exactly this kind of integration. Explore custom robotics development to discuss your requirements.