linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
  • Introduction
  • Lessons
  • How it works
  • Simulator
  • Knowledge base
  • Interview prep
Index
Categories
All entries
Footer
linuxlab-TutorialsPricingAboutPrivacy & cookies
Copyright © 2026 LinuxLab. All rights reserved.
home/linux/kb/Networking: L4 and above/coap

kb/network-l4 ── Networking: L4 and above ── beginner

CoAP: REST for Constrained Devices over UDP

CoAP is REST over UDP for low-power IoT devices. 4-byte header, GET/POST/PUT/DELETE, response codes like HTTP. Observe for notifications. DTLS for security. Used in LwM2M, Thread.

view as markdownaka: coap, coap-protocol, dtls, constrained-application-protocol

Why CoAP

Classic HTTP does not work on heavily constrained devices:

  • Small microcontroller RAM (tens of KB)
  • Battery-powered (cannot hold TCP sessions open)
  • Lossy radio (LoRa, Zigbee, Thread mesh)
  • Not all devices have a full IPv4 stack

An HTTP parser is complex and headers are large (even a minimal GET is ~80 bytes).

CoAP (Constrained Application Protocol, RFC 7252, 2014) is a REST-like protocol designed specifically for them. It runs over UDP, with a minimal header and REST semantics. A device can act as both client and server at the same time.

Applications:

  • LwM2M (Lightweight M2M from OMA) - device management over CoAP
  • Thread (mesh network for smart home) - CoAP in the native protocol stack
  • ZigBee Pro - CoAP option
  • OMA Lightweight Object Tree - sensors/actuators in LwM2M
  • Energy management in HEMS

CoAP vs HTTP - semantics

HTTPCoAP
TCPUDP (TCP variant RFC 8323)
Text headersBitfields, options
Headers ~80+ bytesHeader ~4 bytes
Methods GET/POST/...Same 4 + block extensions
URLsURIs, Uri-Path/Uri-Query options
Response codes 200/404/...Encoded as c.dd (5 classes x 32)
Stateless request/responseConfirmable / Non-confirmable
EventSource/SSE/WebSocket for pushObserve-pattern native

The idea: the same mental model (URL, method, status) compressed for radio and small hardware.

Wire format

The minimal CoAP header is 4 bytes:

 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) ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • Ver - version (1)
  • T - message type: CON (confirmable), NON (non-confirmable), ACK, RST
  • TKL - token length (0-8 bytes)
  • Code - 8 bits, format c.dd (3-bit class + 5-bit detail). Method: 0.01=GET, 0.02=POST, 0.03=PUT, 0.04=DELETE Response: 2.05=Content (approx 200), 4.04=Not Found (approx 404), 5.00=Internal Error
  • Message ID - 16-bit, for duplicate detection
  • Token - 0-8 bytes, for request-response correlation (differs from Message ID: one request can produce many responses via observe)
  • Options - URI path, content-format, ETag, Observe, ...
  • 0xFF - payload delimiter

A minimal GET with empty payload is 4 bytes plus URI options.

Confirmable vs Non-confirmable

CoAP runs over UDP and has no built-in reliability. The message type field handles this:

CON (Confirmable) - reliable

client --CON GET /sensor (mid=42)--> server
client <--ACK 2.05 Content (mid=42)-- server

An ACK is required. Without one, the sender retries with exponential backoff: 2s, 4s, 8s, 16s, 32s, up to 5 attempts (RFC 7252).

With piggyback, the server sends the ACK with the ready response already included (one packet).

NON (Non-confirmable) - fire-and-forget

client --NON GET /sensor (mid=42)--> server

No ACK. If the packet is lost, it is lost. Use this for high-rate telemetry where a single dropped message does not matter.

This is similar to mqtt QoS 0 (NON) and QoS 1 (CON).

Methods and status codes

Methods (RFC 7252):

  • GET 0.01 - retrieve a resource
  • POST 0.02 - create
  • PUT 0.03 - update
  • DELETE 0.04 - delete

Extensions:

  • FETCH 0.05 (RFC 8132) - GET with body (for complex queries)
  • PATCH 0.06, iPATCH 0.07 (RFC 8132)

Response classes:

  • 2.xx - Success: 2.01 Created, 2.02 Deleted, 2.03 Valid (ETag), 2.04 Changed, 2.05 Content
  • 4.xx - Client error: 4.00 Bad Request, 4.04 Not Found, 4.05 Method Not Allowed, 4.06 Not Acceptable, 4.13 Request Entity Too Large
  • 5.xx - Server error: 5.00 Internal, 5.03 Service Unavailable

URI and options

A URI like coap://gw.example/sensors/temp?units=C maps on the wire to:

  • Uri-Host (option 3) = "gw.example"
  • Uri-Path (option 11) = "sensors", "temp" (per segment)
  • Uri-Query (option 15) = "units=C"

Options are typed: 28 predefined, custom ones use the elective/critical flag in the option number.

Each option is encoded with delta-coding from the previous one, keeping it compact.

Observe pattern - server-pushed updates

RFC 7641. The client sends GET with Observe option = 0:

client --GET /temperature, Observe=0--> server
client <--2.05 Content "21.5", Observe=1-- server
client <--2.05 Content "21.6", Observe=2-- server (after 30 s)
client <--2.05 Content "21.7", Observe=3-- server
...

The server sends pushes each time the resource changes (or on a timer). The Observe counter grows monotonically, which helps detect lost or out-of-order responses.

To cancel, send GET with Observe = 1, or simply RST in response to the next push.

This is CoAP's equivalent of EventSource/WebSocket for push, but without an open session (UDP).

Block-wise transfer

Large payloads over UDP are a problem (fragmentation, loss). CoAP splits them into blocks via Block1/Block2 options (RFC 7959):

Block1 = 0/1/512    means: block 0, more=1, size 512

The client sends PUT in blocks 0..N with Block1, the last one with more=0. The server reassembles.

Block size is 16-1024 bytes. The default is chosen to fit the underlying MTU (typically 64-256 for LoRa, 512-1024 for WiFi/Thread).

DTLS - security

CoAP does not encrypt by itself. DTLS (Datagram TLS, RFC 6347) is TLS over UDP (like [[tls-handshake|TLS]] but with retransmission and no strict ordering).

CoAP+DTLS = coaps:// on UDP/5684 (vs plain coap:// on 5683).

Modes:

  • NoSec - plain CoAP (for lab use)
  • PSK (Pre-Shared Key) - shared secret, lightweight for constrained devices
  • RPK (Raw Public Key) - cert without X.509 signature chain
  • Certificate - full X.509 (like HTTPS)

PSK is the most common in IoT: the device ships with a factory key that the broker knows.

Modern extensions:

  • OSCORE (RFC 8613) - object security, payload protection end-to-end even through a proxy
  • EDHOC (RFC 9528) - lightweight key exchange for PSK

CoAP vs MQTT

PropertyCoAP[[mqtt|MQTT]]
TransportUDP (or TCP/TLS)TCP/TLS
Modelrequest-response (REST)publish-subscribe
Brokernot neededrequired
Pushobserve-patternsubscribe
Min header4 bytes2 bytes
ReliabilityCON ack/retryQoS 0/1/2
Multicastyes (group communication)no
Best forsensor query, RESTful APItelemetry, fan-out
StackREST-like, familiarspecific
SecurityDTLSTLS (harder on microcontrollers)

In Thread/Matter, CoAP is the base protocol. In classic IoT (industrial, mobile), MQTT is more common.

Multicast - group communication

CoAP supports IP multicast: one request reaches all devices in the multicast group. Useful for discovery and group control:

GET coap://[ff02::fd]:5683/.well-known/core
              --> all CoAP servers on the local link

HTTP has no equivalent. This is useful in smart home scenarios: "all lights off".

Only NON-confirmable messages may be multicast (CON over multicast is logically impossible).

Discovery - /.well-known/core

RFC 6690 - standard resource directory:

GET /.well-known/core
--->
<--- 2.05
     </sensors/temp>;rt="temperature";if="sensor",
     </sensors/humidity>;rt="humidity";if="sensor",
     </actuators/led>;rt="light";if="actuator"

Application-level discovery: what resources exist and what types they are.

When things go wrong

  • Requests disappear - this is UDP. Check the firewall (5683/5684 UDP). Use CON instead of NON to make losses visible.
  • 4.13 Request Entity Too Large - payload exceeds MTU. Use Block1.
  • DTLS handshake fails - PSK mismatch or TLS version mismatch (CoAP DTLS normally uses DTLS 1.2). Try openssl s_client -dtls1_2.
  • Observe stops working behind NAT - the NAT binding expired and the server sends to nowhere. Add a keepalive (CON GET occasionally) or shorten the interval.
  • Sequence number wrap - the Observe counter is 24-bit; on wrap, the comparison logic can fail. Most implementations have handled this.
  • Multicast not arriving - the kernel does not route multicast by default. Run ip route add 224.0.0.0/4 dev eth0. The link layer must also support it (WiFi often filters multicast).

Further reading

  • https://datatracker.ietf.org/doc/html/rfc7252 - original CoAP RFC
  • https://coap.technology/ - tools and articles
  • https://github.com/eclipse/californium (Java) - reference implementation
  • https://github.com/obgm/libcoap (C) - for embedded use

§ команды

bash
coap-client -m get coap://localhost/.well-known/core

Discovery: list resources on a CoAP server (libcoap CLI)

bash
coap-client -m get coap://gw.example/sensors/temp

Simple GET request for a resource

bash
coap-client -m put -e '21.5' coap://gw/actuators/setpoint

PUT with payload to update a resource

bash
coap-client -s 60 -m get coap://gw/sensors/temp

Subscribe (observe) - receive notifications for 60 seconds

bash
coap-client -m get coaps://gw/secret -k mySecretKey -u myIdentity

DTLS+PSK - identity and pre-shared key

bash
coap-client -m get -B 64 coap://gw/big_resource

Block-wise transfer with a block size of 64 bytes

bash
tshark -Y coap -i eth0

Wireshark dissector for CoAP - shows types, options, observe

bash
nc -u localhost 5683

Open a UDP socket to the CoAP port to inspect raw bytes

§ см. также

  • udp-basicsUDP: User Datagram ProtocolUDP delivers datagrams without establishing a connection, without retransmits, and without ordering guarantees. Header is 8 bytes. Use it for DNS, DHCP, QUIC, VoIP, and any case where latency matters more than reliability.
  • tls-handshakeTLS HandshakeTLS is the encryption layer above TCP. Before data flows, both sides run a handshake: they exchange keys, verify the certificate, and agree on a cipher.
  • portPort: How Multiple Services Share One IPA 16-bit number (0-65535) that identifies the **destination process** on a host. IP says which host; port says which process. 80 is HTTP, 443 is HTTPS, 22 is SSH.
  • http-protocolHTTP/1.1, HTTP/2, HTTP/3HTTP/1.1 is a text-based protocol with keep-alive. HTTP/2 is binary with multiplexing over a single TCP connection. HTTP/3 carries HTTP/2 semantics over QUIC/UDP without TCP head-of-line blocking.
Footer
linuxlab-
Copyright © 2026 LinuxLab. All rights reserved.
Tutorials
Pricing
About
Privacy & cookies