CoAP, the Constrained Application Protocol, is a small web transfer protocol for devices with kilobytes of memory on slow, lossy networks. It keeps the REST model of HTTP (GET, POST, PUT and DELETE on URIs, with response codes and content types) but runs over UDP by default, with a 4 byte binary header and its own lightweight reliability layer. The defining standard is RFC 7252 (June 2014), and its security comes from DTLS, which protects each hop, or OSCORE (RFC 8613, July 2019), which protects the request and response end to end through proxies.
This post covers the message format, confirmable exchanges, Observe (RFC 7641), block-wise transfer (RFC 7959), CoAP over TCP and WebSockets (RFC 8323), and the three security layers: DTLS 1.3 (RFC 9147), OSCORE and the EDHOC key exchange (RFC 9528). Then it covers the weakness classes the standards warn about, the libcoap commands a defender uses to check ports 5683 and 5684 on their own devices, and how CoAP compares with MQTT, LwM2M and HTTP. Companion posts cover MQTT 5 and the mesh layers CoAP often rides on in Zigbee, Thread and Matter explained.
Key takeaways
- CoAP is an application layer protocol over UDP and DTLS (RFC 7252), with TCP, TLS and WebSockets added by RFC 8323. IANA registers port 5683 (
coap) and 5684 (coaps) on UDP and TCP. - Every CoAP over UDP message starts with a 4 byte header: version, type (Confirmable, Non-confirmable, Acknowledgement, Reset), token length, code and a 16 bit message ID, then a token of 0 to 8 bytes, options and an optional payload.
- A Confirmable message is retransmitted with exponential back off, from an initial 2 to 3 second timeout, up to 4 times.
- RFC 7252 defines four security modes: NoSec, PreSharedKey, RawPublicKey and Certificate. NoSec has no protocol security, and the standard singles out internet reachable NoSec nodes as an amplification risk.
- DTLS protects each hop and ends at every proxy. OSCORE protects the payload and most options end to end, through proxies.
- The libcoap example server listens on plain 5683 and, when built with DTLS, on 5684 too. Check with
ss -ulnpandcoap-clientagainst your own devices.
What CoAP is for, the layer and the standard
The RFC 7252 abstract describes CoAP as a specialised web transfer protocol for constrained nodes and low-power, lossy networks, typically 8 bit microcontrollers with little ROM and RAM, aimed at machine to machine uses such as smart energy and building automation. The design goal is to let a sensor be a web server: a thermostat exposes /temp and /setpoint, a gateway sends GET and PUT to them, and a proxy can translate between CoAP and HTTP so ordinary web applications can reach the device.
How the REST model over UDP differs from HTTP
| Aspect | HTTP/1.1 | CoAP (RFC 7252) |
|---|---|---|
| Transport | TCP | UDP by default; DTLS for security; TCP, TLS and WebSockets via RFC 8323 |
| Encoding | Text headers | 4 byte binary header plus compact options |
| Methods | GET, POST, PUT, DELETE and others | GET, POST, PUT, DELETE; FETCH, PATCH and iPATCH added by RFC 8132 |
| Response codes | Three digit status codes | Class and detail, written c.dd, such as 2.05 Content |
| Reliability | From TCP | Built in: Confirmable messages with retransmission |
| Server push | Not in the base model | Observe (RFC 7641) |
| Discovery | Not built in | /.well-known/core in CoRE Link Format (RFC 6690) |
The difference that matters most for security is the transport: TCP's handshake proves a client can receive at its claimed address, UDP does not, so a NoSec CoAP server answers whatever source address a packet carries. That fact drives most of the weakness classes below; see UDP explained for background.
Where it sits in the stack
CoAP is an application layer protocol. On a constrained device the stack is typically an IEEE 802.15.4 radio, 6LoWPAN compression, IPv6, UDP, optionally DTLS, then CoAP carrying CBOR or JSON. RFC 7252 splits CoAP into a message layer that handles UDP's unreliability and a request/response layer above it.
The defining standards
All are IETF Standards Track RFCs; dates are from the RFC Editor.
| RFC | Subject | Date | Relationship |
|---|---|---|---|
| RFC 7252 | CoAP | June 2014 | Base specification. Updated by RFC 7959, 8613, 8974, 9175 and 9876; never obsoleted. |
| RFC 7641 | Observe | September 2015 | Updated by RFC 8323 |
| RFC 7959 | Block-wise transfers | August 2016 | Updates RFC 7252 |
| RFC 7925 | TLS/DTLS 1.2 profile for IoT | July 2016 | Profile for constrained devices |
| RFC 8323 | CoAP over TCP, TLS, WebSockets | February 2018 | Updates RFC 7641 and 7959 |
| RFC 8613 | OSCORE | July 2019 | Updates RFC 7252 |
| RFC 8974 | Extended tokens, stateless clients | January 2021 | Updates RFC 7252 and 8323 |
| RFC 9175 | Echo, Request-Tag, token processing | February 2022 | Updates RFC 7252 |
| RFC 9147 | DTLS 1.3 | April 2022 | Obsoletes RFC 6347 (DTLS 1.2); updated by RFC 9853 (March 2026) |
| RFC 9528 | EDHOC | March 2024 | Key exchange for OSCORE |
| RFC 9668 | EDHOC with CoAP and OSCORE | November 2024 | Combines EDHOC with the first OSCORE request |
| RFC 9876 | Content-Format registry procedures | November 2025 | Updates RFC 7252 |
A correct implementation today reads RFC 7252 together with at least RFC 7959, 8613, 8974 and 9175.
How CoAP works on the wire
The message format
RFC 7252 Section 3 defines the format. Each CoAP over UDP message occupies the data section of exactly one UDP datagram.
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Ver| T | TKL | Code | Message ID |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Token (if any, TKL bytes) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options (if any) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|1 1 1 1 1 1 1 1| Payload (if any) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Field | Size | Meaning |
|---|---|---|
| Version (Ver) | 2 bits | Always 1; unknown versions are silently ignored |
| Type (T) | 2 bits | 0 Confirmable (CON), 1 Non-confirmable (NON), 2 Acknowledgement (ACK), 3 Reset (RST) |
| Token Length (TKL) | 4 bits | 0 to 8 bytes; RFC 8974 uses values 13 and 14 for extended tokens up to 65,804 bytes |
| Code | 8 bits | Class and detail, c.dd: 0 request, 2 success, 4 client error, 5 server error; 0.00 is Empty |
| Message ID | 16 bits | Duplicate detection; matches ACK or RST to its message |
| Token | 0 to 8 bytes | Matches a response to its request |
| Options | Variable | Type, length, value encoding with delta compressed option numbers |
| Payload Marker | 1 byte, 0xFF | Present only if a payload follows |
| Payload | Rest of datagram | The resource representation |
The message ID belongs to the message layer: it detects duplicates and matches an ACK to its CON. The token belongs to the request/response layer: it ties a response to a request, even when the response arrives later in a different message. A response is bound to its request only by the token, which is why RFC 9175 tightened the token rules for secured exchanges.
Options
Options play the role of HTTP headers: URI path and query, content format, cache lifetime, conditional tags and extensions. Each is encoded as a 4 bit delta from the previous option number, a 4 bit length and the value, so options appear in number order. Bits in the option number mark it as critical (a receiver that does not understand it must reject the message), unsafe to forward, or excluded from the cache key, which lets new options be added without breaking old proxies.
Common entries in the IANA "CoAP Option Numbers" registry (last updated 11 August 2026 when fetched):
| No. | Name | Defined in |
|---|---|---|
| 6 | Observe | RFC 7641 |
| 9 | OSCORE | RFC 8613 |
| 11 | Uri-Path | RFC 7252 |
| 12 | Content-Format | RFC 7252 |
| 14 | Max-Age | RFC 7252 |
| 15 | Uri-Query | RFC 7252 |
| 21 | EDHOC | RFC 9668 |
| 23 | Block2 | RFC 7959 |
| 27 | Block1 | RFC 7959 |
| 35 | Proxy-Uri | RFC 7252 |
| 252 | Echo | RFC 9175 |
| 292 | Request-Tag | RFC 9175 |
Max-Age defaults to 60 seconds. Content-Format uses small integers instead of media type strings, for example 0 for text/plain;charset=utf-8, 40 for application/link-format and 50 for application/json.
Methods and response codes
The IANA registries list GET (0.01), POST (0.02), PUT (0.03) and DELETE (0.04), plus FETCH, PATCH and iPATCH (0.05 to 0.07) from RFC 8132. Responses include 2.01 Created, 2.04 Changed, 2.05 Content, 4.01 Unauthorized, 4.03 Forbidden, 4.04 Not Found and 5.03 Service Unavailable. RFC 7959 added 2.31 Continue and 4.08 Request Entity Incomplete, and RFC 8323 registered signalling codes 7.01 to 7.05 (CSM, Ping, Pong, Release, Abort).
Confirmable and non-confirmable exchanges
CoAP over UDP has four message types, and they combine into a few standard exchange shapes.
Piggybacked response (the common case)
Client Server
| CON [MID 0x7d34] GET /temp Token 0x31 |
| -----------------------------------> |
| |
| ACK [MID 0x7d34] 2.05 Content Token 0x31, payload "22.3"
| <----------------------------------- |
Separate response (server needs time)
Client Server
| CON [MID 0x7d35] GET /slow Token 0x32 |
| -----------------------------------> |
| ACK [MID 0x7d35] (Empty, 0.00) |
| <----------------------------------- |
| ... later ... |
| CON [MID 0x23bb] 2.05 Content Token 0x32
| <----------------------------------- |
| ACK [MID 0x23bb] (Empty, 0.00) |
| -----------------------------------> |
Non-confirmable request
Client Server
| NON [MID 0x7d36] GET /temp Token 0x33 |
| -----------------------------------> |
| NON [MID 0xa0f1] 2.05 Content Token 0x33
| <----------------------------------- |
A Confirmable message asks for an acknowledgement. The sender picks a random initial timeout between ACK_TIMEOUT and ACK_TIMEOUT times ACK_RANDOM_FACTOR, doubles it on each retransmission, and gives up after MAX_RETRANSMIT retransmissions or on a Reset. A Non-confirmable message is fire and forget but still carries a message ID for duplicate detection. An Acknowledgement may carry the response itself (a piggybacked response). A Reset says the receiver got the message but cannot process it, for example after a reboot.
RFC 7252 Section 4.8 gives the default transmission parameters:
| Parameter | Default |
|---|---|
| ACK_TIMEOUT | 2 seconds |
| ACK_RANDOM_FACTOR | 1.5 |
| MAX_RETRANSMIT | 4 |
| NSTART | 1 (outstanding interactions per server) |
| DEFAULT_LEISURE | 5 seconds (for multicast responses) |
| PROBING_RATE | 1 byte per second |
From these, the standard derives EXCHANGE_LIFETIME of 247 seconds (how long a message ID stays in use for a CON exchange) and NON_LIFETIME of 145 seconds. When the path MTU is unknown, RFC 7252 says good upper bounds are 1,152 bytes for the message and 1,024 bytes for the payload, to avoid IP fragmentation.
Discovery and multicast
A GET to /.well-known/core returns the server's resources in CoRE Link Format (RFC 6690). For group communication, IANA assigned the IPv4 "All CoAP Nodes" address 224.0.1.187 and the IPv6 variable scope address FF0X::FD; CoAP needs only the link-local and site-local scopes.
Observe (RFC 7641)
Polling wastes energy on battery devices, so RFC 7641 lets a client register interest in a resource. The client sends a GET with the Observe option set to 0 (register). The server returns the current representation with an Observe option and then sends a new notification, carrying the same token, each time the resource changes. A GET with Observe set to 1 deregisters.
Client Server
| GET /temp Observe: 0 Token 0x4a | registration
| -----------------------------------> |
| 2.05 Content Observe: 12 "22.3" | current state
| <----------------------------------- |
| 2.05 Content Observe: 44 "22.8" | change
| <----------------------------------- |
| 2.05 Content Observe: 60 "23.1" | change
| <----------------------------------- |
The Observe value in notifications lets the client detect reordering; RFC 7641 treats it as a 24 bit serial number, and a notification arriving more than 128 seconds after the previous one counts as newer regardless. A server sending mostly NON notifications must send a CON one at least every 24 hours so departed clients are dropped; the libcoap server docs note that it sends every fifth notification as Confirmable even when NON notifications are enabled.
Block-wise transfer (RFC 7959)
IP fragmentation is fragile on constrained links, so RFC 7959 moves segmentation into CoAP. Block2 carries a response body in pieces (a large GET such as a firmware image) and Block1 a request body (a large PUT or POST). Each Block option value has three fields:
| Field | Meaning |
|---|---|
| NUM | The block number |
| M | "More" flag: set when this is not the last block |
| SZX | Size exponent; the block size is 2 to the power of (SZX + 4), so 16 to 1,024 bytes. SZX value 7 is reserved in RFC 7959 and used by RFC 8323 for BERT. |
Client Server
| GET /fw Block2: 0/0/64 |
| -----------------------------------> |
| 2.05 Block2: 0/1/64 (64 bytes) |
| <----------------------------------- |
| GET /fw Block2: 1/0/64 |
| -----------------------------------> |
| 2.05 Block2: 1/1/64 (64 bytes) |
| <----------------------------------- |
| ... and so on ... |
| 2.05 Block2: 9/0/64 (last block) |
| <----------------------------------- |
Size1 and Size2 announce the total size. RFC 7252 recommends small blocks as an amplification control: a 10 byte request for a 1,000 byte resource can get an 80 byte response with a 64 byte block instead of a 1,016 byte one.
CoAP over TCP, TLS and WebSockets (RFC 8323)
Where UDP is blocked, or CoAP must run in a browser, RFC 8323 carries it over TCP, TLS or WebSockets. The transport is reliable, so the Type and Message ID fields are removed; the header carries a length, the token length, the code and the token. Each connection opens with a Capabilities and Settings Message (CSM), which can raise Max-Message-Size above the 1,152 byte default and announce block-wise support for reliable transports (BERT). RFC 8323 adds the coap+tcp, coaps+tcp, coap+ws and coaps+ws schemes. CoAP over TLS defaults to TCP port 5684 with the ALPN identifier "coap" (required on other ports); WebSocket clients offer the subprotocol "coap", and coaps+ws defaults to port 443.
Ports and IANA registrations
From the IANA Service Name and Transport Protocol Port Number Registry, as fetched:
| Service name | Port | Transport | Description | Reference |
|---|---|---|---|---|
| coap | 5683 | UDP | Constrained Application Protocol | RFC 7252 |
| coap | 5683 | TCP | Constrained Application Protocol (CoAP) | RFC 8323 |
| coaps | 5684 | UDP | DTLS-secured CoAP | RFC 7252 |
| coaps | 5684 | TCP | Constrained Application Protocol (CoAP) | RFC 7301, RFC 8323 |
Versions and history
CoAP is still version 1. It evolved through extension RFCs, so the real question is which RFCs an implementation supports.
| Period | What changed |
|---|---|
| 2014 | RFC 7252: message format, methods, options, caching, CoAP and HTTP proxying, multicast and the DTLS 1.2 (RFC 6347) binding. NoSec and RawPublicKey are mandatory to implement, with mandatory cipher suites TLS_PSK_WITH_AES_128_CCM_8 and TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8. |
| 2015 to 2016 | Observe (RFC 7641), block-wise transfer (RFC 7959) and the RFC 7925 DTLS 1.2 profile for IoT. |
| 2018 | RFC 8323: TCP, TLS and WebSockets, mainly for NAT and firewall traversal and browsers. |
| 2019 | OSCORE (RFC 8613) adds protection that survives proxies. |
| 2021 to 2022 | Extended tokens (RFC 8974); Echo, Request-Tag and stricter token rules (RFC 9175); DTLS 1.3 (RFC 9147). |
| 2024 | EDHOC (RFC 9528) and its CoAP and OSCORE integration (RFC 9668). |
| 2025 to 2026 | Content-Format registry procedures (RFC 9876); DTLS Connection ID return routability check (RFC 9853). |
In the field, many devices still run DTLS 1.2 in PSK mode, and others run NoSec behind a link layer key. The OMA Lightweight M2M standard, built on CoAP, shows the direction of travel. Its 1.2.2 transport specification says LwM2M 1.0 used CoAP over UDP and SMS "with or without DTLS", 1.1 added CoAP over TCP, Non-IP transports and OSCORE, and 1.2 added HTTP and MQTT bindings.
How DTLS, OSCORE and EDHOC secure CoAP
The four security modes in RFC 7252
RFC 7252 is explicit that "CoAP itself does not provide protocol primitives for authentication or authorization"; those come from DTLS, IPsec or object security.
| Mode | DTLS | How peers are authenticated | Scheme and port |
|---|---|---|---|
| NoSec | Off | Not at all at the CoAP level | coap, 5683 |
| PreSharedKey | On | Pre-shared keys, each listing the nodes it may be used with | coaps, 5684 |
| RawPublicKey | On | A key pair without a certificate, validated out of band | coaps, 5684 |
| Certificate | On | An X.509 certificate chained to a common trust root | coaps, 5684 |
Two details matter. A pre-shared key held by more than two nodes only proves group membership, not which peer you are talking to. And RFC 7252 says a NoSec system is secured only by keeping attackers from sending or receiving packets on the network with the CoAP nodes.
DTLS: hop by hop protection
DTLS is TLS adapted to datagrams, with handshake retransmission and a cookie exchange that checks the client is reachable at its claimed address before the server does expensive work. Everything inside a record, including the whole CoAP message, is encrypted and integrity protected between the two DTLS endpoints.
RFC 9147's own list of the main changes from DTLS 1.2 includes a shorter handshake, AEAD ciphers only, removal of older algorithms, HelloRetryRequest for the cookie, a new key schedule, a smaller record layer, Connection IDs and encrypted sequence numbers. Its abstract notes DTLS 1.3 matches TLS 1.3 "with the exception of order protection / non-replayability": record replay detection is optional. Servers SHOULD use the cookie exchange unless amplification is not a concern, and SHOULD send no more than three times what an unverified client sent.
DTLS ends at each hop. RFC 7252 Section 11.2 points out that proxies are by nature in the middle, so they break any DTLS or IPsec protection a direct exchange would have.
OSCORE: end to end protection through proxies
OSCORE (RFC 8613) protects the message rather than the transport. The sender encrypts and authenticates the parts a proxy does not need as a COSE_Encrypt0 object and carries it as the payload of an outer message with the OSCORE option (number 9). The outer code is a placeholder: POST and 2.04 Changed, or FETCH and 2.05 Content when Observe is used.
RFC 8613 sorts every message field into a class:
| Class | Protection | Examples |
|---|---|---|
| E | Encrypted and integrity protected, carried inside the COSE object | The real method or response code, Uri-Path, Uri-Query, Content-Format, payload, most options |
| I | Integrity protected only, visible to proxies | None defined at the time of RFC 8613 |
| U | Unprotected, visible to and changeable by proxies | Uri-Host, Uri-Port, Proxy-Scheme, the outer Observe and Block options |
The security context is derived with HKDF from a Master Secret, optional Master Salt and ID Context, a Sender ID and a Recipient ID. Defaults are AES-CCM-16-64-128 (COSE algorithm 10) and HKDF SHA-256. The Sender ID can be empty and at most 7 bytes with the default AEAD, and the combination of Master Secret, Master Salt, ID Context and Sender ID must be unique so no key and nonce pair repeats. Replay protection uses the sender sequence number and a sliding window, 32 by default. A rebooting device must not reuse sequence numbers or accept replays; RFC 8613 Appendix B covers storing state or resynchronising with Echo. OSCORE and DTLS can be combined: DTLS protects each hop, OSCORE the content end to end.
EDHOC: setting up OSCORE keys
OSCORE needs a shared Master Secret, and provisioning one per pair gives no forward secrecy. EDHOC (RFC 9528) is "a very compact and lightweight authenticated Diffie-Hellman key exchange with ephemeral keys" providing mutual authentication, forward secrecy and identity protection, reusing COSE, CBOR and CoAP to keep code small.
Initiator (I) Responder (R)
| message_1: method, suites, G_X, C_I, EAD_1 |
| ------------------------------------------> |
| message_2: G_Y, encrypted(C_R, ID_CRED_R, |
| Signature_or_MAC_2, EAD_2) |
| <------------------------------------------ |
| message_3: encrypted(ID_CRED_I, |
| Signature_or_MAC_3, EAD_3) |
| ------------------------------------------> |
| message_4 (optional) |
| <------------------------------------------ |
| |
| Both sides call EDHOC_Exporter to derive the |
| OSCORE Master Secret and Master Salt |
EDHOC has four methods, choosing signature or static Diffie-Hellman keys for each side, and implementations must support cipher suites 2 and 3 (AES-CCM, SHA-256, P-256, ES256, differing only in MAC length). RFC 9528 gives example totals of 101 bytes (static DH keys, key ID) to 242 bytes (signatures, certificate hash), and says that with raw public keys over CoAP, EDHOC can be under one seventh of a DTLS 1.3 handshake. Messages go in POST requests to /.well-known/edhoc, and RFC 9668 lets message_3 ride on the first OSCORE request via the EDHOC option (number 21). For authorisation, RFC 9203 (August 2022) defines an OSCORE profile of the OAuth based ACE framework, binding access tokens to OSCORE keys.
Security properties: what CoAP does and does not guarantee
| Property | NoSec CoAP over UDP | CoAP over DTLS | CoAP with OSCORE |
|---|---|---|---|
| Confidentiality | None | Whole message, per hop | Class E fields end to end; Class U fields and the outer header are visible |
| Integrity | None | Whole message, per hop | Class E and I fields end to end |
| Peer authentication | None | Per hop, by PSK, raw public key or certificate | End to end, by possession of the security context |
| Replay protection | Message ID duplicate detection only, which is not a security control | Optional record replay detection | Replay window on sequence numbers; reboot handling required |
| Request and response binding | Token, guessable if short | Weak unless RFC 9175 token rules are followed | Built in: the response is cryptographically bound to the request |
| Source address validation | None | Cookie exchange | None by itself; the Echo option provides it |
| Protection through proxies | Not applicable | Ends at each proxy | Survives forwarding proxies |
Weakness classes, explained as concepts
These come from the Security Considerations sections of RFC 7252, 7641, 7959, 8613, 9147 and 9175, and describe what goes wrong and why the design allows it.
Amplification from unauthenticated endpoints
RFC 7252 Section 11.3 notes that a response may be much larger than the request, and since UDP cannot verify a source address, a forged address turns a NoSec server into a traffic multiplier aimed at someone else. The standard flags NoSec nodes that attackers can reach and that can reach potential victims as the main concern. Observe makes it worse because one request yields many notifications, so RFC 7641 requires a server without client authentication to strictly limit notifications between client acknowledgements. Multicast adds fan out, and RFC 7252 says servers should not accept multicast requests that cannot be authenticated. The mitigations in the standards are small blocks for unauthenticated requests, the DTLS cookie exchange, and RFC 9175's short 4.01 Unauthorized response carrying an Echo option, which only a client really at that address can answer.
Spoofing and response forgery
RFC 7252 Section 11.4 lists what forged packets can do to a NoSec exchange: a fake Reset cancels a Confirmable message, a fake ACK stops retransmission, and whole responses, multicast requests or Observe notifications can be forged. An off path attacker must guess the message ID and token, so short or predictable tokens weaken that barrier. Only DTLS or OSCORE make forgeries cryptographically detectable.
Replay and request freshness
NoSec CoAP has nothing beyond short duplicate detection, and DTLS replay detection is optional. OSCORE has a replay window, but RFC 8613 Section 12.4 warns an attacker may try to force a reboot so a device loses its window, and that servers sharing one context can be replayed against if their windows drift. A verified OSCORE request proves who made it, not that it is current, so RFC 8613 recommends Echo where freshness matters, such as commands to a lock.
NoSec mode exposure
NoSec is mandatory to implement and common in sample code. The libcoap example server listens on 5683 and also on port plus one for DTLS when built with it, so a test server with DTLS credentials still answers plain CoAP. A device protected only by its mesh key is exposed the moment a border router, VPN or port forward puts 5683 on a routed network.
Proxy trust
A proxy terminates DTLS and sees everything; RFC 8613 notes a compromised intermediary could delete resources on sensors and falsify commands to actuators. RFC 7252 also forbids shared caches from treating coaps responses as public. With OSCORE, proxies can still see and change Class U fields, analysed in RFC 8613 Appendix D.5.
Block-wise and Observe state exhaustion
RFC 7959 Section 7 warns that a Block1 request with a high block number can push a naive server into building a huge representation, that misleading sizes can trigger buffer bugs in "poor implementations", and that partial transfers can hide content from inspection that does not reassemble blocks. All blocks of a protected resource must pass the same checks. Observe registrations are state too.
Weak keys, shared credentials and implementation bugs
RFC 7252 Section 11.6 warns that many nodes lack good entropy and must not generate their own keys, are prone to timing side channels, and can be physically tampered with, so a fleet wide key means one extracted key compromises all. Most real incidents, though, are parser and DTLS stack bugs in option handling, block reassembly and record processing, such as this libcoap advisory. Keeping the CoAP and DTLS libraries current is part of securing the protocol.
Secure configuration, with defender side check commands
Only run these commands against systems you own or are authorised to test. The examples use the documentation address 192.0.2.10 for a device; replace it with your own. The coap-client and coap-server options below come from the libcoap manual pages at libcoap.net (develop branch documentation, 4.3.5 series), and ss comes from the Linux ss(8) man page.
1. Find every CoAP listener
On a Linux gateway, list listening sockets with their processes. The ss(8) man page defines -u (UDP), -t (TCP), -l (listening), -n (numeric) and -p (owning process).
sudo ss -ulnp | grep -E ':(5683|5684)\b'
sudo ss -tlnp | grep -E ':(5683|5684)\b'
A UDP listener on 5683 bound to a routable address is a NoSec endpoint unless you meant it to be. Also check for WebSocket ports, which the example server's -w option opens separately.
2. Confirm NoSec is off from another host
The libcoap docs use .well-known/core as the discovery example, and -B sets how long coap-client waits (default 90 seconds).
coap-client -m get -B 5 coap://192.0.2.10/.well-known/core
With NoSec off this should time out. A resource list means the device answers unauthenticated CoAP on that address; repeat from other segments to confirm your firewall rules. Then repeat over DTLS with real credentials: -u and -k for PSK, or -c, -j and -R for a client certificate, key and trusted roots.
coap-client -m get -B 5 -u device-identity -k 0x00112233445566778899aabbccddeeff \
coaps://192.0.2.10/.well-known/core
coap-client -m get -B 5 -c client.pem -j client.key -R trust-roots.pem \
coaps://192.0.2.10/.well-known/core
The right credentials should succeed and a wrong key or untrusted certificate should fail. Never add -n, which the docs describe as disabling remote peer certificate checking; a pass with it proves nothing.
3. Check which devices answer multicast discovery
The libcoap docs show discovery to the IPv6 link-local All CoAP Nodes address, with -N for a non-confirmable request:
coap-client -m get -N -B 5 "coap://[ff02::fd%eth0]/.well-known/core"
Anything that answers and does not need group requests should have multicast restricted. On the example server, -g joins a group and -r limits multicast to /, /async and /.well-known/core. The docs also note DTLS over multicast is not supported, so treat multicast requests as unauthenticated.
4. Bind only what you mean to expose
The example server takes -A for the listen address and -p for the port. In your own libcoap application, the server endpoint docs say PSK or PKI settings must be configured before the endpoint is created, and each endpoint is created with a protocol such as COAP_PROTO_UDP, COAP_PROTO_DTLS, COAP_PROTO_TLS or COAP_PROTO_WSS. A server that should never speak NoSec simply never creates an unsecured endpoint; review that code rather than relying on a firewall alone.
5. Get DTLS settings right
- The libcoap server docs warn that a PSK identity hint (
-h) or the identity matching files disable DTLS 1.3 negotiation, so check whether your PSK setup has pinned you to DTLS 1.2. - Use per device credentials; a fleet wide PSK is a group key, not an identity.
- Keep the cookie exchange on, as RFC 9147 says by default.
- On DTLS 1.2, follow the RFC 7925 profile; with Connection IDs, read RFC 9853.
6. Configure OSCORE carefully
The libcoap coap-oscore-conf(5) page lists the keys: master_secret and sender_id are required, recipient_id is required for clients, replay_window defaults to 32 (1 to 63), aead_alg to 10 (AES-CCM-16-64-128) and hkdf_alg to direct+HKDF-SHA-256. A minimal client file, with placeholder values:
master_secret,hex,"0102030405060708090a0b0c0d0e0f10"
master_salt,hex,"9e7ca92223786340"
sender_id,hex,"01"
recipient_id,hex,"00"
replay_window,integer,32
Generate a real Master Secret with a good random source, as RFC 8613 Section 12.3 requires. Pass the file with -E and a sequence file after the comma; the docs explain this keeps sequence numbers increasing across restarts so the peer's anti-replay window is not tripped.
coap-client -E client-oscore.conf,client-oscore.seq -m get -B 5 coap://192.0.2.10/temp
Leave rfc8613_b_1_2 at its default of true so the server uses the RFC 8613 Appendix B.1.2 reboot procedure, and check that an unprotected request to the same resource is refused.
7. Limit what unauthenticated requests can cost
- Keep blocks small for unverified clients (the example server's
-bsets 16 to 1,024 bytes). - Answer unverified clients with 4.01 and an Echo option before sending anything large, per RFC 9175.
- Cap dynamic resource creation (
-d) and rate limit NON transmissions (-I, packets per minute).
8. Log and inventory
Turn on logging you will read (-v for CoAP, -V for the DTLS library, -a for syslog on the example tools) and keep an inventory of which CoAP and DTLS library versions are in your firmware, gateways and cloud services. Our UDP services audit covers checking every UDP port you expose.
When to use CoAP versus the alternatives
| CoAP | MQTT | LwM2M | HTTP | |
|---|---|---|---|---|
| Model | Request/response REST, plus Observe | Publish/subscribe through a broker | Device management and telemetry objects, built on CoAP | Request/response REST |
| Transport | UDP; TCP, TLS, WebSockets (RFC 8323) | TCP, TLS, WebSockets | CoAP over UDP, SMS, TCP, Non-IP; HTTP and MQTT bindings since 1.2 | TCP (HTTP/1.1, HTTP/2) or QUIC (HTTP/3) |
| Security | DTLS per hop or OSCORE end to end; NoSec otherwise | TLS per hop to the broker; no end to end layer in the standard | DTLS, TLS or OSCORE per the transport specification | TLS per hop |
| Works through proxies end to end | Yes, with OSCORE | No; the broker sees everything | Yes, with OSCORE | No; TLS ends at each proxy |
| Best fit | Battery devices on 6LoWPAN or Thread, direct device addressing, sleepy nodes | Many devices streaming telemetry to many consumers | Fleet provisioning, firmware update and remote configuration | Gateways and devices with plenty of memory and bandwidth |
Choose CoAP when the device itself is addressed, when every byte and radio wake up costs battery, or when you need end to end protection through gateways. Choose MQTT for decoupled many to many telemetry through a broker (see our MQTT 5 explainer). Choose LwM2M when the job is fleet management: bootstrap, firmware update and standard objects on top of CoAP. Choose HTTP when the device has the resources and web tooling matters more; see QUIC and HTTP/3 explained. For smart home stacks running over Thread, see Zigbee, Thread and Matter and the beginner Matter explainer.
Where Safeguard fits
Safeguard does not inspect, filter or block CoAP traffic; it covers the software that implements it. Software composition analysis and container scanning (generally available) show which CoAP and DTLS library versions sit in your gateways and images, and for C firmware with vendored code, an SPDX or CycloneDX SBOM from your build can be analysed the same way (generally available). EPSS, KEV and reachability analysis (generally available) rank what to fix first, and policies and gates (generally available) keep a known vulnerable version from shipping again.
Common mistakes
- Treating NoSec as "secured by the mesh". It holds only while attackers cannot reach the network; border routers, VPNs and port forwards change that without touching the device.
- Shipping the example server or its defaults. Sample servers listen on plain 5683 as well as 5684. Create only secure endpoints.
- One PSK for the whole fleet. A key shared by more than two nodes authenticates group membership only, and one extracted key compromises the group.
- Assuming DTLS is end to end. It ends at every proxy and gateway; use OSCORE when intermediaries are not fully trusted.
- Assuming DTLS stops replays. Replay detection is optional. Use OSCORE replay windows or Echo for commands that must not run twice.
- Answering multicast and large requests from anyone. Restrict multicast to the resources that need it, keep blocks small and use Echo for unverified clients.
- Testing with certificate checks off, or losing track of library versions. A test with peer checking disabled proves nothing, and you cannot know you have a parser fix without knowing which library version each image carries.
FAQ
What port does CoAP use?
IANA registers port 5683 for coap and 5684 for coaps, each on both UDP and TCP. Plain CoAP over UDP uses 5683, DTLS-secured CoAP uses UDP 5684, and CoAP over TLS uses TCP 5684 with the ALPN identifier "coap". CoAP over secure WebSockets defaults to port 443.
Is CoAP encrypted?
Not by default. CoAP in NoSec mode has no encryption, integrity or authentication. Encryption comes from running it over DTLS (the coaps scheme), over TLS for CoAP over TCP, or by protecting messages with OSCORE.
CoAP vs MQTT: what is the difference?
CoAP is request/response REST, usually over UDP, where the device can be the server. MQTT is publish/subscribe over TCP, where every client connects to a broker that routes by topic. CoAP has an end to end security option (OSCORE); MQTT relies on TLS to the broker.
What is OSCORE?
OSCORE (RFC 8613) is Object Security for Constrained RESTful Environments. It encrypts and authenticates a CoAP request or response with COSE inside the message, so the protection holds end to end even when the message passes through proxies that DTLS would have to terminate.
What does EDHOC do?
EDHOC (RFC 9528) is a compact authenticated Diffie-Hellman key exchange with ephemeral keys. It gives two endpoints mutual authentication, forward secrecy and identity protection, and its main use is to derive the Master Secret and Master Salt for an OSCORE security context.
Does CoAP use DTLS 1.2 or DTLS 1.3?
RFC 7252 was written against DTLS 1.2, which is still widely deployed. DTLS 1.3 (RFC 9147, April 2022) obsoleted the DTLS 1.2 specification and is supported by newer libraries. Which one you get depends on both endpoints and their DTLS library configuration.
How does CoAP Observe work?
The client sends a GET with the Observe option set to 0. The server returns the current state and then sends a notification with the same token each time the resource changes, numbered so the client can spot reordering. A GET with Observe set to 1, or a Reset in reply to a notification, ends the observation.
Is CoAP safe to expose to the internet?
Not in NoSec mode. RFC 7252 singles out NoSec nodes reachable from the internet as an amplification risk, and anyone who can reach them can send requests and read the traffic. Expose only DTLS or OSCORE protected endpoints with per device credentials, and verify from outside that 5683 does not answer.
Fix it without waiting for upstream
CoAP security bugs usually live in a library your software depends on: the CoAP implementation that parses options and reassembles blocks, or the DTLS library underneath it. Those are open source components you can inventory and upgrade like any other.
Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.
3. Gold Registry: start from components that are already fixed
registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | The Gold scan GitHub Action in CI, plus Safeguard policies and gates |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch (early access) |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
See how Safeguard would help
These demos show dependency, SBOM and remediation workflows, not CoAP traffic monitoring. You can download Safeguard and follow the CLI docs to try them on your own projects.
This demo sets up the Safeguard runner on your own machine, approves it with its device code and then scans from the terminal. Pointed at the container image for a CoAP gateway or a cloud service that receives device telemetry, the same scan lists the CoAP and DTLS libraries inside with their versions and any known vulnerabilities.
Click through it at your own pace, or open it full screen.
This demo is a real CLI session: install, open the interactive terminal, ask it to fix the lodash CVE in a repository, and watch it read the finding and advisory, ask before each command, move lodash to 4.17.21, run npm install and npm test with approval, and print a summary without pushing anything. The same approve each step flow applies when the dependency to bump is a CoAP or DTLS library pinned in a gateway service's manifest.
Click through it at your own pace, or open it full screen.
This demo connects Claude to the Safeguard MCP server with OAuth and lets you choose which tools Claude can use. Once connected, you can ask in plain language which of your projects include a particular CoAP or DTLS library version, and get the answer from your own scan data rather than from memory.
Click through it at your own pace, or open it full screen.
Quick check
A gateway forwards CoAP requests between a cloud service and a sensor, and both hops use DTLS. Can the gateway change the sensor's readings?
Yes. DTLS protects each hop separately, so the gateway decrypts, can alter, and re-encrypts every message. OSCORE between the cloud service and the sensor would let the gateway forward messages without reading or changing their protected parts.
Why does RFC 9175 suggest answering an unknown client with a short 4.01 response that carries an Echo option?
Because UDP cannot prove where a request came from. Continuing only when the client repeats the Echo value confirms it really receives packets at its claimed address, so a large response cannot be aimed at a forged one.
Sources
- RFC 7252: The Constrained Application Protocol (CoAP), June 2014
- RFC 7641: Observing Resources in CoAP, September 2015
- RFC 7959: Block-Wise Transfers in CoAP, August 2016
- RFC 7925: TLS/DTLS Profiles for the Internet of Things, July 2016
- RFC 8323: CoAP over TCP, TLS, and WebSockets, February 2018
- RFC 8613: Object Security for Constrained RESTful Environments (OSCORE), July 2019
- RFC 8974: Extended Tokens and Stateless Clients in CoAP, January 2021
- RFC 9175: CoAP Echo, Request-Tag, and Token Processing, February 2022
- RFC 9147: The Datagram Transport Layer Security (DTLS) Protocol Version 1.3, April 2022
- RFC 9203: The OSCORE Profile of the ACE Framework, August 2022
- RFC 9528: Ephemeral Diffie-Hellman Over COSE (EDHOC), March 2024
- RFC 9668: Using EDHOC with CoAP and OSCORE, November 2024
- RFC 9853: Return Routability Check for DTLS 1.2 and 1.3, March 2026
- RFC 9876: Updates to the IANA Registration Procedures for CoAP Content-Formats, November 2025
- IANA: Constrained RESTful Environments (CoRE) Parameters
- IANA: Service Name and Transport Protocol Port Number Registry
- libcoap: coap-server(5) manual
- libcoap: coap-client(5) manual
- libcoap: coap-oscore-conf(5) manual
- libcoap: coap_endpoint_server(3) manual
- OMA SpecWorks: Lightweight M2M Transport Bindings, V1.2.2, 13 June 2024
- OMA SpecWorks: LwM2M specifications
- man7.org: ss(8)
Last reviewed: 23 October 2026. Library options and defaults change between releases; check the documentation for the libcoap and DTLS library versions you run.