CoAP Protocol Deep Dive: Constrained Application Protocol for Low-Power IoT in 2026
Last Updated: October 2026
A temperature sensor that reports every few minutes has a strange engineering problem. The measurement is two bytes. Everything around it, the connection setup, the headers, the acknowledgements, the keep-alives, is orders of magnitude larger, and on a coin cell or an AA pack it is the radio, not the sensor, that decides how long the device lives. The CoAP protocol, short for Constrained Application Protocol, is the IETF’s answer: a binary, UDP-based, REST-shaped protocol whose smallest message is four bytes and whose reliability is something you opt into per message.
It matters in 2026 for two reasons. First, CoAP is no longer just a research curiosity from the 6LoWPAN era. It is the transport underneath LwM2M, the dominant open device-management standard for cellular IoT, and it now has a credible modern security story built on DTLS 1.3, OSCORE and EDHOC. Second, the old folk wisdom (“CoAP is MQTT for UDP”, “CoAP is always lower power”) is wrong in ways that cost real projects money.
What this covers: the constrained-device problem and where CoAP fits among alternatives, the message format and reliability machine down to the byte, Observe and block-wise transfer as mechanisms rather than features, the security options (DTLS 1.3, OSCORE, EDHOC) and what each actually protects, a decision framework against MQTT, the failure modes that bite in production, and a practical checklist.
What Changed for October 2026
This is a ground-up rewrite of the April 2026 version of this article. If you read the earlier post, treat its specifics as superseded. The corrections matter, so here they are explicitly.
- RFC 9177 is not CoCoA. The earlier post attributed CoAP congestion control to RFC 9177. That RFC is “Constrained Application Protocol (CoAP) Block-Wise Transfer Options Supporting Robust Transmission”, the Q-Block options. Advanced congestion control (CoCoA) was never published as an RFC in that number. The base protocol’s congestion behavior is the fixed backoff schedule in RFC 7252.
- OSCORE does not rewrite the URI to a well-known path. The earlier example showed a protected request sent to
/.well-known/oscore. In RFC 8613 the outer request keeps its destination and the outer method is POST (or FETCH when Observe is used), with the real method, options and payload inside the ciphertext. OSCORE also is not stateless: the server keeps a replay window and sequence numbers. - Device class numbers were wrong. RFC 7228 defines Class 0 as far below 10 KiB of RAM, and Class 1 as roughly 10 KiB RAM and 100 KiB flash. The earlier numbers were inflated by several times.
- Duplicate detection windows were understated. The earlier text said servers remember message IDs for about four seconds. RFC 7252 defines EXCHANGE_LIFETIME as 247 seconds for default parameters.
- The all-CoAP-nodes IPv6 address was wrong. The multicast group is ff0x::fd with the scope chosen by the sender (for example ff02::fd link-local), not a fixed ff03::fd.
- Unsourced latency and battery figures were removed. The earlier comparison table quoted first-request latencies in milliseconds and “months versus years” battery claims with no source. They are gone. Where numbers appear below they are protocol constants from RFCs, arithmetic you can check, or labelled illustrative.
- New material. LwM2M 1.2.2 (published June 2024), RFC 9175 (Echo and Request-Tag), the IETF drafts on attacks against CoAP and on corrections to RFC 7252, CoAP over reliable transports, the Resource Directory in RFC 9176, and a decision framework against MQTT.
Context and Background
The constrained-device problem was formalized in RFC 7228, which sorts devices into classes by RAM and flash. Class 0 devices are far below 10 KiB of RAM and 100 KiB of flash and usually cannot even run a full IP stack; they sit behind a gateway. Class 1 devices have roughly 10 KiB of RAM and 100 KiB of flash. They can speak IP, but a full HTTP, TLS and TCP stack does not fit comfortably. Class 2 devices, around 50 KiB of RAM and 250 KiB of flash, can run most protocols but still pay for them in energy. CoAP was designed for Class 1 and up, and it is the reason a Class 1 node can expose a RESTful interface at all.
The second constraint is the network. IEEE 802.15.4 frames carry at most 127 bytes in total, and link-layer headers and security consume a good part of that, leaving on the order of 100 bytes for everything above. That is why 6LoWPAN compresses IPv6 headers and why CoAP keeps messages small enough to avoid fragmentation. RFC 7252 recommends that a CoAP message fit in a single IP datagram, with 1152 bytes as the upper bound for a message and 1024 bytes for a payload, and the constrained-network guidance is much tighter than that. Cellular low-power networks such as NB-IoT and LTE-M have different limits but the same bias: every byte sent keeps the radio awake longer. For the radio side of that trade, see our comparison of LoRaWAN, NB-IoT and LTE-M for industrial IoT.
CoAP’s competitors occupy different niches. MQTT is a publish/subscribe protocol over TCP that needs a broker and a long-lived connection. HTTP/2 and HTTP/3 are rich and widely tooled but heavier. The standards body view, from the IETF CoRE (Constrained RESTful Environments) working group, is that you should be able to translate between HTTP and CoAP at a proxy because both share REST semantics: URIs, methods, response codes and content formats. That translation property is CoAP’s quiet superpower in an industrial architecture, and it is why it appears in IIoT edge gateway designs as the device-facing side with MQTT or HTTP on the northbound side.
The core specification is RFC 7252, published in 2014. It has been extended rather than replaced: Observe (RFC 7641), block-wise transfer (RFC 7959), CoAP over TCP, TLS and WebSockets (RFC 8323), OSCORE (RFC 8613), DTLS 1.3 (RFC 9147), EDHOC (RFC 9528), and the Echo and Request-Tag options (RFC 9175). A “bis” document collecting corrections and clarifications to RFC 7252 exists as an IETF draft, and a separate draft catalogues attacks on CoAP deployments even when a security protocol is in use. Neither was an RFC when I checked in October 2026, so treat both as guidance in progress rather than normative text. For a quantitative look at protocol latency under controlled conditions, our IoT protocol latency benchmark covers methodology that this article deliberately does not repeat.
How the CoAP Protocol Works: A Two-Layer Design for Lossy Links
CoAP is a binary request/response protocol over UDP in which reliability, ordering and congestion behavior are handled by a thin messaging layer instead of TCP. Each message is either confirmable (retransmitted until acknowledged) or non-confirmable (sent once), and requests are matched to responses by a token. Everything else, Observe, block-wise transfer, security, is layered on top as options.

Figure 1: CoAP protocol layering. The messaging layer handles reliability, the request/response layer handles REST semantics, and security binds either per hop (DTLS) or end to end (OSCORE).
The diagram shows the key architectural idea, and it is worth stating plainly: CoAP separates two concerns that HTTP-over-TCP fuses together. The messaging layer deals with the unreliable datagram: it decides whether a message needs an acknowledgement, retransmits it, and drops duplicates. The request/response layer deals with meaning: methods, response codes, options and tokens. This is why a CoAP response can arrive minutes after the acknowledgement of the request, and why the protocol can run unchanged over TCP in the RFC 8323 variant, where the messaging layer simply disappears.
The messaging layer: four message types and one timer
Every CoAP message has one of four types. A Confirmable (CON) message requires an Acknowledgement (ACK) carrying the same Message ID. A Non-confirmable (NON) message requires none. An ACK acknowledges a CON, and may carry a response piggybacked inside it. A Reset (RST) says the receiver cannot process the message, typically because it has lost the context, and it is also how a client cancels an Observe subscription it no longer wants.
The retransmission rules are the heart of the reliability story, and they are small enough to memorize. RFC 7252 defines ACK_TIMEOUT as 2 seconds and ACK_RANDOM_FACTOR as 1.5, so the first retransmission timeout is chosen uniformly between 2 and 3 seconds. Each retry doubles the timeout, up to MAX_RETRANSMIT of 4 retries. The worst-case schedule is therefore 3 + 6 + 12 + 24 seconds of waiting between transmissions, which is the 45 second MAX_TRANSMIT_SPAN in the specification. After the last retry the sender waits a further timeout, and the specification’s MAX_TRANSMIT_WAIT of 93 seconds is the total bound before giving up. You can verify it: 2 x (2^5 – 1) x 1.5 = 93.
Two other constants govern the network’s health. NSTART is 1, so a client has at most one outstanding interaction per peer. PROBING_RATE is 1 byte per second, the average rate at which a client should send to a peer that is not responding, which stops a flood of retries from a broken device. EXCHANGE_LIFETIME is 247 seconds, the time a message ID must be remembered for duplicate detection. A constrained node that tracks message IDs per peer for 247 seconds, at a few bytes per entry, is a real memory consideration if the device talks to many peers, and it is the main reason low-memory servers often prefer to answer idempotent requests statelessly and tolerate duplicates.
The request/response layer: methods, codes and tokens
CoAP borrows HTTP’s four principal methods: GET, POST, PUT and DELETE, with codes 0.01 to 0.04. RFC 8132 adds FETCH, PATCH and iPATCH, and FETCH matters for security because OSCORE uses it for protected Observe requests. Response codes keep the “class.detail” form: 2.05 Content, 2.04 Changed, 4.04 Not Found, 5.03 Service Unavailable. RFC 8516 adds 4.29 Too Many Requests, which a server can send when a client exceeds a rate, along with a Max-Age style hint on when to return.
The token is what ties a response to a request. It is chosen by the client, is 0 to 8 bytes long in the base protocol, and is echoed by the server. RFC 8974 extends the token length for cases where the token must carry more information. This separation is essential: the Message ID matches an ACK to a CON at the messaging layer, while the token matches a response to a request even when the response is a separate message much later. A client that issues two requests in flight uses two tokens, and a notification stream from an Observe subscription keeps the same token for its whole life.
A worked byte count
Consider a GET for coap://sensor.example/sensors/temp with a 2-byte token. The message is a 4-byte header, 2 bytes of token, one option byte plus 7 bytes for the Uri-Path segment “sensors” (the option delta of 11 and the length of 7 pack into one byte), and one option byte plus 4 bytes for “temp”. The total is 4 + 2 + 8 + 5 = 19 bytes before UDP and IP. The piggybacked 2.05 reply with the payload 23.5 in text format is 4 + 2 + 1 (Content-Format option, value 0 elided) + 1 (payload marker 0xFF) + 4 = 12 bytes. The full exchange is about 31 bytes of CoAP, which fits easily inside a single 802.15.4 frame even after 6LoWPAN header compression. That is the entire reason the protocol exists. It is a worked illustration of the encoding, not a benchmark: real byte counts depend on options, token length and the content format you choose.
Why UDP and why the energy argument is subtler than it looks
Running over UDP removes the three-way handshake and per-connection state. For a node that wakes, sends one datagram and sleeps, that saves round trips. But the energy case for CoAP is not “UDP is cheaper.” It is that radio-on time dominates energy on most constrained hardware, and CoAP lets the application send less, send less often, and not hold a connection open. Observe lets the server push only on change. Non-confirmable messages let telemetry skip the acknowledgement. The ability to answer from a cached or proxied copy lets a sleepy node avoid being contacted at all.
Observe, Block-Wise Transfer and Discovery as Mechanisms
CoAP’s three most useful extensions each solve a specific problem that a constrained device would otherwise hit: polling cost, payloads larger than one datagram, and finding resources on a network without prior configuration. All three are optional, and all three have failure modes that the short versions of this topic usually leave out.
Observe: server push with an explicit freshness rule
RFC 7641 adds the Observe option, option number 6, to a GET. A client sends GET with Observe set to 0 to register, and the server answers with the current state and then sends further responses (notifications) whenever the resource changes. Setting Observe to 1 deregisters. The server identifies the relationship by the client’s endpoint and token, so notifications reuse the original token.

Figure 2: A CoAP protocol Observe relationship. Registration, notifications of differing reliability, and removal of the subscription when the client sends a Reset.
The part most explanations skip is how a client decides which notification is newest when the network reorders packets. The Observe value in each notification is a 24-bit counter, and RFC 7641 defines freshness arithmetically: with values V1 and V2 and local arrival times T1 and T2, the second notification is newer if V1 is less than V2 and the difference is below 2^23, or V1 is greater than V2 and the difference exceeds 2^23, or more than 128 seconds have passed. The wrap-around rule means the counter can overflow safely, and the 128 second clause means a server that reboots and restarts its counter does not strand clients on stale data forever.
The reliability rules are equally specific. A server must not have more than NSTART (1 by default) notifications outstanding to a client for the same observation. For non-confirmable notifications, a server should not send more than one per round-trip time, and should not exceed one every 3 seconds without knowing better. Crucially, a server must send a confirmable notification at least every 24 hours. That is how the server discovers that a client has vanished: if the client never replies, or replies with a Reset, the server drops the observation. A Reset in response to a notification is the standard cancellation, which is why a client that wants to stop does not need to send anything proactive.
Block-wise transfer: stateless slicing, not atomic transfer
RFC 7959 defines the Block1 option (number 27) for request payloads and Block2 (number 23) for response payloads. A Block option is a 0 to 3 byte unsigned integer that packs three fields: NUM, the block number; M, a flag saying more blocks follow; and SZX, a 3-bit size exponent. Block size is 2^(SZX + 4) bytes, giving 16 to 1024 bytes for SZX values 0 through 6, and SZX 7 is reserved. The byte offset of a block is NUM shifted left by SZX + 4. Size1 (option 60) and Size2 (option 28) let a sender announce the total representation size early.
The design intent is that the server stays almost stateless. As the specification puts it, a server can handle each block transfer separately, with no need for a connection setup or memory of previous transfers. A device serving a firmware image can respond to “give me block 17 at 512 bytes” without remembering that block 16 was just sent.
The trap is that block-wise transfer is not atomic. If the resource changes between block 3 and block 4, the client reassembles a Frankenstein of two versions. The specification recommends comparing the ETag across blocks to detect this. For uploads, 2.31 Continue acknowledges non-final blocks, 4.08 reports missing or out-of-order blocks, and 4.13 reports that the server cannot hold the whole body. Block-wise transfers are also a security surface, because blocks from different operations can be mixed by an attacker. RFC 9175 introduced the Request-Tag option (292) to bind blocks of one request together, and it is the recommended mitigation for the block interchange attack in the IETF attacks draft.
For lossy links, the original block-wise scheme is lock-step: send a block, wait for the ACK, send the next. RFC 9177 adds Q-Block1 and Q-Block2 options that allow several blocks in flight with a congestion-aware pacing, and it is designed for the case where a CoAP transfer shares a lossy network with other traffic. It is a relatively young option and library support is uneven, so check your stack before relying on it. The older RFC 7959 behavior remains the interoperable default.
Discovery: /.well-known/core and the Resource Directory
RFC 6690 defines the CoRE Link Format. A device exposes its resources at /.well-known/core as a comma-separated list of links with attributes such as rt (resource type), if (interface) and obs (observable). A client can ask GET /.well-known/core?rt=temperature and get only the matching entries.
Multicast makes this work without configuration. The IPv4 group is 224.0.1.187, and the IPv6 “All CoAP Nodes” group is ff0x::fd, where x is the scope. A multicast GET to the group elicits responses from every node, which is delightful on a lab bench and risky on a production network: a single multicast request can trigger hundreds of responses, so RFC 7252 requires a randomized response delay (DEFAULT_LEISURE, 5 seconds) and limits multicast to non-confirmable requests.
Multicast fails when nodes sleep. RFC 9176 defines the CoRE Resource Directory, a registry to which endpoints POST their link-format descriptions along with a lifetime, and which clients query with filtering. As the RFC puts it, direct discovery is not practical with sleeping nodes or where multicast is inefficient. The directory answers lookups for sleeping devices, which is exactly the case the protocol most needs to serve. In LwM2M, the registration interface plays this same role.

Figure 3: A CoAP protocol deployment through a border-router proxy. OSCORE protects the message across the proxy. DTLS terminates at the proxy, so the proxy sees plaintext.
Securing CoAP: DTLS 1.3, OSCORE and EDHOC
CoAP security comes in two shapes: a secure channel between two endpoints (DTLS) or protection of the message itself (OSCORE), with EDHOC as a compact way to establish keys for either. The choice is not about which is “stronger” but about where you terminate trust, and Figure 3 is the quickest way to see the difference.
DTLS 1.3: a channel per hop
DTLS secures UDP traffic and is the default binding in RFC 7252 on port 5684 for the coaps scheme. RFC 7252 specifies three credential modes: pre-shared keys, raw public keys and certificates. DTLS 1.3, RFC 9147, obsoletes DTLS 1.2 (RFC 6347) and makes several changes that matter to constrained devices. It uses a unified, variable-length record header. It introduces a dedicated ACK message so a lost handshake flight can be selectively retransmitted instead of resent wholesale. It encrypts record sequence numbers. And it replaces the separate HelloVerifyRequest of DTLS 1.2 with a cookie carried in a HelloRetryRequest, which defends against spoofed-source amplification by forcing a round trip before the server commits state.
The Connection ID extension from RFC 9146 is built into DTLS 1.3, and it is arguably the most valuable feature for IoT. Without it, a DTLS session is bound to the IP address and port, so a NAT rebinding kills the session and forces a full handshake. A device that wakes every hour and finds its NAT mapping changed would pay a handshake each time. With a Connection ID the server recognizes the session by an identifier in the record header, and the session survives address changes. LwM2M 1.2 calls out DTLS 1.3 with Connection ID support for exactly this reason.
The limits of DTLS are structural. It protects one hop. If a border-router proxy sits between sensor and cloud, there are two DTLS sessions, and the proxy sees plaintext. DTLS also adds record framing that lowers the effective path MTU, and RFC 9147 warns implementations to account for it. And a full handshake, especially with certificates, is several flights and often more than a kilobyte of traffic, which is hostile to a device that wakes rarely and sends little. Pre-shared keys shrink this considerably, at the price of key distribution, and session resumption helps if the server keeps state.
OSCORE: protect the message, not the pipe
OSCORE, RFC 8613, protects CoAP at the application layer using COSE. It encrypts and authenticates the request code, the inner options and the payload as one unit, and puts the ciphertext into an outer CoAP message carrying the OSCORE option (number 9). The outer code is POST for requests and 2.04 Changed for responses; when Observe is used, the outer code is FETCH for requests and 2.05 Content for responses, so that intermediaries can still process the Observe option. Options intended for proxies, such as Uri-Host and Proxy-Uri, are left outside, and the rest is hidden.
The consequence is that a proxy can forward, and even cache and cross-protocol translate to some extent, without being able to read or alter the content. That is what Figure 3 shows: the same ciphertext passes through the border router. OSCORE is also lighter on the wire than a DTLS record because its compression strips COSE down to a compact flag byte, a partial IV, and optionally a sender ID, which adds on the order of a dozen bytes per message in typical configurations. Treat that figure as an approximation and measure for your context.
It is not stateless, and the earlier version of this article was wrong to suggest so. Each side keeps an OSCORE security context with a sender sequence number and, on the receiving side, a replay window, 32 bits by default in RFC 8613. A device that loses its sequence number on reboot must not reuse nonces, because nonce reuse with an AEAD cipher is catastrophic, so you need persistent storage or a procedure to re-establish the context. This is the most common OSCORE deployment bug, and it comes with the territory of per-message security without a session.
OSCORE does not hide everything. Outer options, message type, message IDs, token, source and destination addresses and message sizes remain visible. That matters for traffic analysis and, as the IETF attacks draft notes, encryption makes selective blocking harder but not impossible, since an on-path attacker can still see the lengths and addresses.
EDHOC: the missing key-establishment piece
OSCORE needs a security context, and for a long time the options for creating one were pre-provisioned keys or heavy handshakes. RFC 9528 (March 2024) defines EDHOC, Ephemeral Diffie-Hellman Over COSE, a compact authenticated key exchange with mutual authentication, forward secrecy and identity protection. The specification reports complete handshakes of between 101 and 242 bytes depending on the credential type. It runs over CoAP, uses CBOR and COSE so it shares code with OSCORE, and its main purpose is to derive an OSCORE context. In practice the pairing is “EDHOC to set up, OSCORE to protect.”
The earlier version of this article claimed EDHOC takes three messages versus “7 or more round-trips” for DTLS. That comparison is not fair. EDHOC is a three-message exchange, but the byte counts and flights should be compared for the same credential type and the same transport, and DTLS 1.3 with pre-shared keys is a single round trip when no cookie is requested. The real EDHOC advantage is not fewer round trips, it is far fewer bytes, and a context that works through proxies.
Attacks that survive encryption
Whichever you choose, read the IETF draft on attacks against CoAP. It describes five classes that apply even with DTLS, TLS or OSCORE: selective blocking, request delay, response delay and mismatch, request fragment rearrangement during block-wise transfer, and relay attacks. The delay attack is the one that should worry industrial engineers. If a CoAP PUT unlocks a door or opens a valve and an attacker holds the encrypted message for an hour before releasing it, a pure-encryption design will accept it.
The standard mitigations are in RFC 9175. The Echo option (252) makes the server send a challenge that the client must return, proving the request was created after the challenge, which defeats delayed requests and also limits amplification by verifying the client’s address. The Request-Tag option (292) binds blocks to one request. The token rule requires clients using DTLS or TLS to use tokens as a sequence starting at zero for each new secure connection, so a delayed response cannot be matched to the wrong request. For actuators, treat Echo as mandatory for anything with physical consequences, and keep command messages confirmable.
CoAP, LwM2M and the Device-Management Layer
Most CoAP traffic in the field is not hand-written resource trees. It is LwM2M, the Lightweight Machine-to-Machine specification from OMA SpecWorks, which defines a standard object model, a registration and bootstrap flow, and operations over CoAP. LwM2M 1.0 appeared in February 2017, 1.1 in June 2018, and 1.2 in November 2020. The 1.2.2 revision was published in June 2024, and I found no newer release when I checked.
LwM2M’s four interfaces map directly onto CoAP mechanisms. Bootstrap provisions security credentials and server addresses. Registration lets a client register, update and deregister with a server, which is functionally a Resource Directory. Device Management and Service Enablement uses Read, Write, Execute, Create, Delete and Discover. Information Reporting uses Observe and Notify, and 1.2 added observation attributes inside the Observe operation along with edge-detection style notification attributes and confirmable notifications.
Version 1.1 introduced OSCORE for application-layer security, and 1.2 moved to TLS and DTLS 1.3 with Connection ID. Version 1.2 also opened up transports: LwM2M messaging can be carried over MQTT and HTTP in addition to CoAP over UDP, TCP and SMS, plus non-IP bindings. That is worth knowing because it breaks the equation “LwM2M means CoAP” that older material assumes. For a fuller treatment of the object model, firmware update flow and fleet operation, see our LwM2M device management architecture article.
The architectural lesson is that device management, not telemetry, is where CoAP earns its place. Telemetry can ride almost any protocol. A device that must be bootstrapped, rekeyed, queried, reconfigured and updated while asleep most of the time benefits from a protocol that is request/response, has resource discovery and handles block-wise firmware download. That is the shape of CoAP and the shape of LwM2M.
CoAP vs MQTT: A Decision Framework, Not a Winner
CoAP and MQTT solve different problems, and the choice follows from three questions: who needs the data, how the device sleeps, and what the network does to your packets. The comparison tables that rank them on latency or power without stating these conditions are misleading, which is why the earlier table in this article was removed.

Figure 4: A CoAP protocol versus MQTT decision flow. Fan-out and sleep behavior decide the first split, and the network’s treatment of UDP decides the second.
Fan-out. MQTT is built around a broker that decouples producers from consumers. If many services need the same telemetry, a broker with topics, retained messages and shared subscriptions is the natural fit, and our MQTT 5 deep dive covers what the newer version adds. CoAP’s Observe is point-to-point per observer. You can build fan-out with a proxy or the experimental publish/subscribe draft in the CoRE working group, but that is not the strength of the protocol.
Sleep and topology. MQTT needs a TCP connection, or a connectionless variant such as MQTT-SN, to keep a session. A device that sleeps for an hour must reconnect or hold the session open, depending on keep-alive and the broker’s behavior. CoAP’s request/response model, combined with a Resource Directory or an LwM2M queue mode, handles a device that is reachable only briefly. Neither protocol solves the sleepy-server problem alone, and both depend on an intermediary to buffer.
Network behavior. UDP is blocked or heavily rate-limited on some enterprise and carrier networks, and NAT timeouts for UDP are often short. RFC 8323 exists exactly for this: CoAP over TCP, TLS and WebSockets, with signaling messages for capability exchange, ping and pong and orderly release, and no Message ID or message type because the transport is reliable. Its WebSocket binding uses the coap+ws and coaps+ws schemes on ports 80 and 443. When you have to run CoAP through a hostile network, this is the right move, but you give up the energy advantage of UDP and gain a stateful connection, and at that point the case against MQTT weakens considerably.
| Dimension | CoAP over UDP | CoAP over TCP or TLS | MQTT over TCP |
|---|---|---|---|
| Interaction model | Request/response plus Observe | Request/response plus Observe | Publish/subscribe via broker |
| Reliability | Per message, CON or NON | Transport-provided | Three QoS levels, broker-mediated |
| Connection state | None required | One connection | One connection plus session |
| Multicast discovery | Yes | No | No |
| Message-level security | OSCORE available | OSCORE available | Not standard; TLS only |
| Natural fit | Device management, local control | Hostile networks, WebSockets | Fan-out telemetry |
| Main risk | NAT timeouts, UDP filtering | Connection overhead | Broker as a single point |
The table is a qualitative summary of protocol design, not a measurement. For measured latency under a specific test setup, use a benchmark methodology that holds payload, QoS, network and security constant, such as the one discussed in our protocol latency benchmark. If your stack also considers OPC UA or DDS for the plant floor, the industrial messaging protocol comparison positions them relative to CoAP and MQTT.
An opinion worth stating: in industrial architectures the strongest pattern is usually both. CoAP and LwM2M manage the device and carry local control, and an edge gateway publishes the normalized telemetry to MQTT or a stream for consumption by historians and twins. The question is where each protocol’s strengths apply, not which one to eliminate. Smart-home stacks show a related split: Thread uses CoAP internally for network management while Matter defines the application layer above it, as covered in our Thread versus Matter comparison.
Trade-offs, Gotchas, and What Goes Wrong
NAT and UDP timeouts. Many NATs expire idle UDP bindings in well under a minute. An Observe subscription or a server-initiated command then silently stops arriving. Mitigations are a Connection ID with DTLS 1.3, periodic keep-alive requests sized to the real timeout, CoAP over TCP where the network allows it, or a design in which the device always initiates.
Message ID and token reuse after reboot. A device that restarts and begins its Message ID counter at zero can collide with entries still held by a peer for the 247 second EXCHANGE_LIFETIME, and its requests may be treated as duplicates. Randomize the starting value and the token, and under DTLS or TLS follow the RFC 9175 rule of tokens as a per-connection sequence.
OSCORE sequence number loss. If a device loses its sender sequence number and reuses a nonce, confidentiality and integrity collapse. Persist the counter with a safety margin, or re-run EDHOC after an unclean restart.
Amplification. A small request that elicits a large response is an attack tool when the source address can be spoofed. Use the Echo option for unauthenticated large responses, and keep multicast resources minimal.
Proxy caches and sleepy nodes. A proxy that caches a sensor value serves a stale one if Max-Age is too long, and a proxy that holds an Observe relationship hides the device’s real state. Choose Max-Age deliberately: it is the contract between freshness and battery life.
Block-wise transfer of firmware. The transfer is not atomic, so verify an ETag, a hash and a signature on the assembled image. A signed image is the real safeguard, not the transport.
Practical Recommendations
Start from the network and the sleep schedule, not the protocol brand. If your devices are LTE-M or NB-IoT modules that sleep for long periods and need remote management, CoAP with LwM2M is a strong default and the cellular module vendor likely already ships it. If your devices are mains-powered, always connected and feed many consumers, MQTT with a broker is simpler to operate.
Treat security as part of the design. Use DTLS 1.3 with Connection ID where the path has no untrusted intermediary, and OSCORE with EDHOC where proxies, caches or multiple hops are involved. Either way, use Echo for commands with physical effect.
A short checklist for a CoAP deployment:
- Pick CON or NON per resource and write down why; commands are CON, high-rate telemetry may be NON.
- Keep every message below the post-security MTU and use Block2 above it.
- Set Max-Age on every cacheable response deliberately.
- Use DTLS 1.3 Connection ID or a keep-alive tuned to measured NAT timeouts.
- Persist OSCORE sequence numbers and plan context recovery.
- Require Echo for actuator commands and verify firmware by signature, not transport.
- Register devices in a Resource Directory or LwM2M server rather than relying on multicast.
- Test with at least two independent CoAP stacks, for example libcoap, aiocoap or Californium.
Frequently Asked Questions
What is the CoAP protocol used for?
CoAP is used where devices have little memory, power or bandwidth: smart meters, environmental sensors, building controllers, and cellular IoT modules managed with LwM2M. It exposes resources with REST-style methods over UDP, supports discovery, and allows server push through Observe. It is a good fit for device management and local control, and less natural for broad fan-out of telemetry.
Is CoAP better than MQTT?
Neither is better in general. CoAP suits request/response interactions, resource discovery and devices that sleep, and it runs over UDP without a broker. MQTT suits fan-out publish/subscribe through a broker over a persistent TCP connection. Many industrial systems use CoAP or LwM2M at the device edge and MQTT northbound, so the real question is where each belongs.
Does CoAP guarantee delivery?
Only when you ask for it. A Confirmable message is retransmitted up to four times with exponentially increasing timeouts until an acknowledgement arrives, which bounds the wait at 93 seconds by default. A Non-confirmable message is sent once. CoAP does not provide ordering or exactly-once semantics, so applications that need them must use idempotent operations or application-level checks.
Should I use DTLS or OSCORE with CoAP?
Use DTLS 1.3 when the two endpoints talk directly, and enable Connection ID for NAT rebinding. Use OSCORE when messages cross proxies or caches that should not read them, or when you want protection independent of the transport. OSCORE needs a security context, which EDHOC can establish, and it needs reliable storage of sequence numbers to avoid nonce reuse.
Can CoAP run over TCP?
Yes. RFC 8323 defines CoAP over TCP, TLS and WebSockets for networks that block or distort UDP. It removes the message type and Message ID, adds a length field and signaling messages for capabilities, ping and release, and supports coap+ws and coaps+ws URIs. It trades some of the energy advantage of UDP for traversal of restrictive networks.
How does CoAP Observe work?
A client sends a GET with Observe set to 0 to register. The server then sends notifications with the same token each time the resource changes, carrying a 24-bit sequence value that clients use to discard stale or reordered updates. A Reset reply or an Observe value of 1 cancels the subscription, and the server must send a confirmable notification at least every 24 hours to check liveness.
Further Reading
Related articles on this site:
- LwM2M device management architecture for the object model and fleet operations built on CoAP.
- IoT protocol latency benchmarks for MQTT, CoAP, AMQP and HTTP/3 for measurement methodology.
- MQTT 5 features deep dive for the main alternative.
- LoRaWAN vs NB-IoT vs LTE-M for industrial IoT for the radio layer beneath CoAP.
- IIoT edge gateway architecture for where protocol translation happens.
Primary sources:
- RFC 7252, The Constrained Application Protocol for the core protocol and transmission parameters.
- RFC 8613, Object Security for Constrained RESTful Environments and RFC 9528, EDHOC for end-to-end security.
- RFC 7641, Observe, RFC 7959, Block-Wise Transfers and RFC 9175, Echo and Request-Tag.
- OMA LwM2M Core Specification v1.2.2.
By Riju — about
