Safeguard
Supply Chain Security•

CoAP Explained: RFC 7252, DTLS and OSCORE for Constrained Devices

CoAP is a compact REST protocol over UDP for small devices. How its 4 byte header, Observe and block transfers work, and how DTLS and OSCORE secure it.

Safeguard Security Team
39 min read

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 -ulnp and coap-client against 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

AspectHTTP/1.1CoAP (RFC 7252)
TransportTCPUDP by default; DTLS for security; TCP, TLS and WebSockets via RFC 8323
EncodingText headers4 byte binary header plus compact options
MethodsGET, POST, PUT, DELETE and othersGET, POST, PUT, DELETE; FETCH, PATCH and iPATCH added by RFC 8132
Response codesThree digit status codesClass and detail, written c.dd, such as 2.05 Content
ReliabilityFrom TCPBuilt in: Confirmable messages with retransmission
Server pushNot in the base modelObserve (RFC 7641)
DiscoveryNot 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.

RFCSubjectDateRelationship
RFC 7252CoAPJune 2014Base specification. Updated by RFC 7959, 8613, 8974, 9175 and 9876; never obsoleted.
RFC 7641ObserveSeptember 2015Updated by RFC 8323
RFC 7959Block-wise transfersAugust 2016Updates RFC 7252
RFC 7925TLS/DTLS 1.2 profile for IoTJuly 2016Profile for constrained devices
RFC 8323CoAP over TCP, TLS, WebSocketsFebruary 2018Updates RFC 7641 and 7959
RFC 8613OSCOREJuly 2019Updates RFC 7252
RFC 8974Extended tokens, stateless clientsJanuary 2021Updates RFC 7252 and 8323
RFC 9175Echo, Request-Tag, token processingFebruary 2022Updates RFC 7252
RFC 9147DTLS 1.3April 2022Obsoletes RFC 6347 (DTLS 1.2); updated by RFC 9853 (March 2026)
RFC 9528EDHOCMarch 2024Key exchange for OSCORE
RFC 9668EDHOC with CoAP and OSCORENovember 2024Combines EDHOC with the first OSCORE request
RFC 9876Content-Format registry proceduresNovember 2025Updates 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) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
FieldSizeMeaning
Version (Ver)2 bitsAlways 1; unknown versions are silently ignored
Type (T)2 bits0 Confirmable (CON), 1 Non-confirmable (NON), 2 Acknowledgement (ACK), 3 Reset (RST)
Token Length (TKL)4 bits0 to 8 bytes; RFC 8974 uses values 13 and 14 for extended tokens up to 65,804 bytes
Code8 bitsClass and detail, c.dd: 0 request, 2 success, 4 client error, 5 server error; 0.00 is Empty
Message ID16 bitsDuplicate detection; matches ACK or RST to its message
Token0 to 8 bytesMatches a response to its request
OptionsVariableType, length, value encoding with delta compressed option numbers
Payload Marker1 byte, 0xFFPresent only if a payload follows
PayloadRest of datagramThe 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.NameDefined in
6ObserveRFC 7641
9OSCORERFC 8613
11Uri-PathRFC 7252
12Content-FormatRFC 7252
14Max-AgeRFC 7252
15Uri-QueryRFC 7252
21EDHOCRFC 9668
23Block2RFC 7959
27Block1RFC 7959
35Proxy-UriRFC 7252
252EchoRFC 9175
292Request-TagRFC 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:

ParameterDefault
ACK_TIMEOUT2 seconds
ACK_RANDOM_FACTOR1.5
MAX_RETRANSMIT4
NSTART1 (outstanding interactions per server)
DEFAULT_LEISURE5 seconds (for multicast responses)
PROBING_RATE1 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:

FieldMeaning
NUMThe block number
M"More" flag: set when this is not the last block
SZXSize 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 namePortTransportDescriptionReference
coap5683UDPConstrained Application ProtocolRFC 7252
coap5683TCPConstrained Application Protocol (CoAP)RFC 8323
coaps5684UDPDTLS-secured CoAPRFC 7252
coaps5684TCPConstrained 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.

PeriodWhat changed
2014RFC 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 2016Observe (RFC 7641), block-wise transfer (RFC 7959) and the RFC 7925 DTLS 1.2 profile for IoT.
2018RFC 8323: TCP, TLS and WebSockets, mainly for NAT and firewall traversal and browsers.
2019OSCORE (RFC 8613) adds protection that survives proxies.
2021 to 2022Extended tokens (RFC 8974); Echo, Request-Tag and stricter token rules (RFC 9175); DTLS 1.3 (RFC 9147).
2024EDHOC (RFC 9528) and its CoAP and OSCORE integration (RFC 9668).
2025 to 2026Content-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.

ModeDTLSHow peers are authenticatedScheme and port
NoSecOffNot at all at the CoAP levelcoap, 5683
PreSharedKeyOnPre-shared keys, each listing the nodes it may be used withcoaps, 5684
RawPublicKeyOnA key pair without a certificate, validated out of bandcoaps, 5684
CertificateOnAn X.509 certificate chained to a common trust rootcoaps, 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:

ClassProtectionExamples
EEncrypted and integrity protected, carried inside the COSE objectThe real method or response code, Uri-Path, Uri-Query, Content-Format, payload, most options
IIntegrity protected only, visible to proxiesNone defined at the time of RFC 8613
UUnprotected, visible to and changeable by proxiesUri-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

PropertyNoSec CoAP over UDPCoAP over DTLSCoAP with OSCORE
ConfidentialityNoneWhole message, per hopClass E fields end to end; Class U fields and the outer header are visible
IntegrityNoneWhole message, per hopClass E and I fields end to end
Peer authenticationNonePer hop, by PSK, raw public key or certificateEnd to end, by possession of the security context
Replay protectionMessage ID duplicate detection only, which is not a security controlOptional record replay detectionReplay window on sequence numbers; reboot handling required
Request and response bindingToken, guessable if shortWeak unless RFC 9175 token rules are followedBuilt in: the response is cryptographically bound to the request
Source address validationNoneCookie exchangeNone by itself; the Echo option provides it
Protection through proxiesNot applicableEnds at each proxySurvives 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 -b sets 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

CoAPMQTTLwM2MHTTP
ModelRequest/response REST, plus ObservePublish/subscribe through a brokerDevice management and telemetry objects, built on CoAPRequest/response REST
TransportUDP; TCP, TLS, WebSockets (RFC 8323)TCP, TLS, WebSocketsCoAP over UDP, SMS, TCP, Non-IP; HTTP and MQTT bindings since 1.2TCP (HTTP/1.1, HTTP/2) or QUIC (HTTP/3)
SecurityDTLS per hop or OSCORE end to end; NoSec otherwiseTLS per hop to the broker; no end to end layer in the standardDTLS, TLS or OSCORE per the transport specificationTLS per hop
Works through proxies end to endYes, with OSCORENo; the broker sees everythingYes, with OSCORENo; TLS ends at each proxy
Best fitBattery devices on 6LoWPAN or Thread, direct device addressing, sleepy nodesMany devices streaming telemetry to many consumersFleet provisioning, firmware update and remote configurationGateways 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 situationWhat to use
A fixed version existsAutonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version
You need to know today whether you are affectedGold Open Source lookup and "Am I affected?" checker, free and without login
You want vulnerable versions to stop shippingThe Gold scan GitHub Action in CI, plus Safeguard policies and gates
Upstream has no fix yet, or will not ship oneA custom Gold request; fork and patch (early access)
You want to start clean on new projectsPull 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

Last reviewed: 23 October 2026. Library options and defaults change between releases; check the documentation for the libcoap and DTLS library versions you run.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.