ChirpStack LoRaWAN Network Server Architecture: Gateway Bridge, ADR, Multicast and FUOTA (Self-Hosted, 2026)

ChirpStack LoRaWAN Network Server Architecture: Gateway Bridge, ADR, Multicast and FUOTA (Self-Hosted, 2026)

ChirpStack LoRaWAN Network Server Architecture: Gateway Bridge, ADR, Multicast and FUOTA (Self-Hosted, 2026)

A LoRaWAN deployment fails quietly. The gateways blink, the sensors report, and the dashboard looks fine until a firmware bug needs patching on 4,000 devices that sit inside walls, or until the battery curve of the whole fleet turns out to be half of what the datasheet promised because every node is shouting at spreading factor 12. Almost all of those problems live in one place: the network server, the component that owns sessions, keys, data rates and downlinks.

ChirpStack LoRaWAN is the open-source network server most teams reach for when they want to own that layer instead of renting it. It is MIT licensed, built in Rust, and ships as a small set of cooperating processes backed by PostgreSQL, Redis and an MQTT broker. Because you operate it yourself, you also inherit decisions that a hosted service hides: how gateways talk to the server, how adaptive data rate behaves, how multicast firmware updates are staged.

You will leave with a component map, a view of what Redis and PostgreSQL each hold, the join and key flow, the ADR decision loop with worked airtime numbers, the multicast and FUOTA pipeline, and a compose stack you can adapt.

What this covers: the component architecture, gateway paths, state stores, join and security, ADR, Class A/B/C and multicast, FUOTA, scaling and high availability limits, and a self-hosting checklist.

Context and Background

LoRaWAN is a media access control layer specified by the LoRa Alliance on top of the LoRa physical layer. The architecture is a star of stars: end devices talk by radio to any gateway in range, gateways forward frames over an IP backhaul to a network server, and the network server routes application payloads to application servers. Gateways are deliberately dumb. They do not hold keys or sessions, so one uplink is usually heard by several gateways, and the network server must deduplicate the copies, validate the message integrity code, pick the best gateway for the reply, and decide when the device may transmit again.

That makes the network server the brain of the system, and the market splits into hosted offerings and self-managed ones. Hosted public networks are convenient but constrain your data path, your ADR policy and your update strategy. A self-hosted server gives you control over all three, in exchange for operating a stateful service. If you are new to the radio layer first, our overview of LoRaWAN covers the physical layer, regional plans and device classes; this post stays at the network-server layer.

ChirpStack is the long-running open-source implementation. Its documentation describes the project as an open-source LoRaWAN network server with a web interface for managing gateways, devices and tenants, data integrations with cloud providers, databases and other services, and a gRPC based API for integration and extension (ChirpStack documentation). The source repository states that PostgreSQL is the default database, that SQLite is selectable for lighter setups, and that its test suite needs PostgreSQL, Redis and Mosquitto, which is a good hint about its runtime dependencies (ChirpStack on GitHub).

One honest limit before we go deeper. This post was written from the official ChirpStack documentation pages that were reachable at writing time, the LoRaWAN specifications as I know them, and a few community threads. Where a number comes from the specification rather than ChirpStack, I say so. Where I am recalling something I could not re-verify, I label it. Check your ChirpStack release notes before relying on any default value.

ChirpStack LoRaWAN Architecture: Components and Data Paths

Direct answer: A ChirpStack deployment is a gateway-side forwarder (Concentratord with the MQTT Forwarder, or a Gateway Bridge for Semtech UDP and Basics Station gateways), an MQTT broker, the ChirpStack network server, PostgreSQL for durable configuration, Redis for hot state, and integrations that push decoded events to your applications.

ChirpStack LoRaWAN architecture showing gateway bridge, MQTT broker, Redis and PostgreSQL

Figure 1: The ChirpStack component map. Gateways reach the server only through MQTT, and the server keeps durable data in PostgreSQL and hot session state in Redis.

The diagram shows the decoupling that defines the design. The network server never speaks the radio or gateway protocol directly. It consumes and produces MQTT messages on well-defined topics, and everything gateway-specific is pushed out to forwarders. This is why you can mix gateways from different vendors in one deployment.

Gateway-side components

The ChirpStack architecture documentation lists four building blocks. ChirpStack Concentratord is an open-source LoRa concentrator daemon. It owns the SX1301, SX1302 or similar concentrator hardware, and forwarder applications interact with it through a ZeroMQ based API. ChirpStack MQTT Forwarder forwards packets to the broker in Protobuf or JSON, uses either the Semtech UDP Packet Forwarder or Concentratord as its gateway backend, and is meant to run on each gateway. ChirpStack Gateway Bridge converts messages from the Semtech UDP Packet Forwarder or Semtech Basics Station into MQTT and can run either on the gateway or in the cloud. The fourth is ChirpStack itself (ChirpStack architecture).

The choice among these is the first real design decision, and it is about backhaul behaviour rather than features. The Semtech UDP packet forwarder protocol is connectionless, unauthenticated and unencrypted, which is acceptable inside a private network but unwise across the public internet. Basics Station uses a WebSocket (LNS protocol) connection, supports TLS, and can receive configuration and even firmware from the server side. Concentratord plus the MQTT Forwarder skips the intermediate protocol entirely: the gateway publishes straight to the broker, so you secure the gateway with broker TLS and credentials.

A practical rule: if you control the gateway image and want the fewest moving parts, run Concentratord and the MQTT Forwarder on the gateway. If you inherit a vendor gateway that only speaks Semtech UDP, run a Gateway Bridge next to the broker and keep UDP on a private segment. If the gateway supports Basics Station, the bridge terminates its WebSocket and gives you TLS and certificate-based gateway identity.

The MQTT layer and regions

Everything between gateway and server rides on MQTT topics. For a multi-region setup the documentation describes two patterns: a single broker serving several regions with a region prefix in the topic, where for example the US915 prefix us915_0 indicates the first eight channels, or a separate broker per region where the prefix becomes unnecessary (ChirpStack architecture). The prefix is user configurable, and the server’s region configuration must match what the gateways publish under.

The broker is therefore a critical path component even though it holds no state of consequence. Gateways publish uplinks and statistics, the server publishes downlink commands. A broker outage stops both directions, and a broker with retained or queued messages can replay stale downlinks, so use clean sessions and short expiry for gateway traffic. The decoupling also gives you a cheap observability tap: subscribing to the gateway topic with any MQTT client shows raw frames without touching the server.

The server and its API

The server process exposes a gRPC based API and a web interface, and the same API powers integrations. Applications see devices through an application and tenant model: tenants own gateways, applications and device profiles, and device profiles carry the LoRaWAN parameters (MAC version, regional parameters revision, supported class, ADR settings). The separation matters operationally. A device profile is the unit where you decide MAC version and class, while an application is the unit where you choose integrations. Mixing the two concerns is the root of many misconfigured fleets.

Why a Rust server with external state

ChirpStack keeps almost no long-lived state inside the process. Session keys, frame counters and downlink queues are stored outside, which is what lets you restart it, run more than one instance for availability, and keep deployments boring. The price is that Redis and PostgreSQL become the real availability and latency budget, as the next section explains.

State Stores: What PostgreSQL and Redis Each Hold

Separating durable configuration from hot session state is the most important operational idea in a ChirpStack deployment. PostgreSQL answers the question “what exists and how is it configured”, while Redis answers “what is happening right now”. Both roles below are inferred from the architecture and from the LoRaWAN processing the server must do; the ChirpStack pages I could retrieve describe the dependencies but not every key layout, so treat the key-level details as my interpretation, not documentation.

PostgreSQL: the system of record

PostgreSQL holds tenants, users, gateways, applications, device profiles, devices, multicast groups and the device keys. It is the data you would back up and the data a disaster recovery plan must restore. Writes are infrequent compared with traffic: you create devices and edit profiles. Reads happen on cache misses. If you operate at modest scale, a single PostgreSQL instance with streaming replication and regular logical backups is sufficient, and the repository’s note that SQLite is selectable shows how light the durable footprint can be for small deployments.

The one place where PostgreSQL meets the hot path is device metadata lookup at join time, plus any persistence of events or metrics your integrations request. Keep the database on the same low-latency network as the server and tune connection pool size to the number of server instances, not to the number of devices.

Redis: sessions, deduplication and queues

Redis is where per-frame work happens. For every uplink the server must hold three things briefly: the set of gateway copies seen for this frame so it can deduplicate and choose the downlink gateway, the device session (DevAddr, session keys, frame counters, current data rate and ADR state), and the downlink queue for the device. These are small, hot and time sensitive, which is exactly the profile Redis serves well.

Two consequences follow. First, Redis latency adds directly to uplink handling latency, and the Class A receive windows leave no slack: the first window opens one second after the uplink ends by default, so the server has well under a second for deduplication, MIC check, application dispatch and downlink scheduling (the one second RX1 delay is the LoRaWAN default; devices can be configured otherwise). Second, treat Redis loss as a session-recovery event. If Redis restarts without persistence, devices keep sending with valid sessions the server has forgotten. Enable append-only persistence or accept that affected devices must rejoin, and plan for a rejoin storm if the fleet is large.

Capacity intuition (illustrative)

To feel the scale, consider an illustrative fleet of 50,000 devices each sending one frame every 15 minutes. That is 50,000 / 900 s, about 55 uplinks per second on average. With each frame heard by an average of three gateways, the broker carries about 165 gateway messages per second and the server deduplicates down to 55. Each frame costs a handful of Redis operations and a PostgreSQL read only on a cache miss. This is well within what a modest server handles, and the real constraint is radio airtime at the gateways long before CPU on the network server. These are back-of-envelope figures, not benchmarks.

The corollary for sizing: scale Redis memory by device count (session size times devices), and scale CPU and broker throughput by uplink rate times gateway multiplicity. Do not size by device count alone.

Join Procedure, Keys and Frame Security

Every LoRaWAN uplink carries a message integrity code (MIC), and every application payload is encrypted. The network server is the party that validates and enforces this, so understanding the key hierarchy tells you what ChirpStack must store and what an attacker could gain from each compromise.

ChirpStack LoRaWAN join procedure sequence from join request to first ADR downlink

Figure 2: Over-the-air activation. Several gateways forward the same join request, the server deduplicates, validates the MIC and answers in a receive window that opens five seconds after the request.

Over-the-air activation, step by step

In over-the-air activation (OTAA) a device holds a root key and an identifier. It sends a join request containing its identifiers and a random nonce. The network server checks that the nonce has not been used before, verifies the MIC computed with the root key, and replies with a join accept that carries a network address (DevAddr) and parameters from which both sides derive fresh session keys. The join accept is sent in the first receive window five seconds after the request and in the second at six seconds, which are the LoRaWAN join delays defined in the specification. Those longer delays exist so a server has enough time to process a join that may have been heard by many gateways.

The alternative, activation by personalisation (ABP), skips the handshake and burns fixed session keys and a DevAddr into the device. It is simpler and also dangerous: frame counters then reset on device reboot unless stored in non-volatile memory, and the server must either accept counter resets (which opens replay) or reject the device. Use OTAA for everything new. The only honest argument for ABP is a constrained legacy device that cannot do the join.

LoRaWAN 1.0.x versus 1.1 keys

The two protocol generations differ in how many keys exist. In 1.0.x, a single root key (AppKey) yields a network session key (NwkSKey) used for MIC and MAC command encryption, and an application session key (AppSKey) used to encrypt payloads. In 1.1, the root material is split: a NwkKey for the network side and an AppKey for the application side, with separate derived keys for forwarding and serving network integrity (FNwkSIntKey, SNwkSIntKey), network session encryption (NwkSEncKey), and join-related keys (JSIntKey and JSEncKey). Version 1.1 also adds a rejoin mechanism and uses a join-server concept to separate who holds the root keys from who runs the network.

For a self-hosted operator the practical distinctions are these. A 1.0.x device is simpler and widely supported; the same network server both authenticates and can derive the application key, so you trust one box with everything. A 1.1 device lets a separate join server hold application root material so the network operator cannot read application data. If you run both the network and the application, this separation buys little and costs interoperability, since many commodity devices ship only 1.0.x firmware. In ChirpStack the MAC version is a device-profile setting, so a fleet with mixed versions simply uses multiple profiles.

Replay and counter protection

Uplink and downlink frame counters are 32-bit values on the server even though only the low 16 bits travel on the air. The server reconstructs the high bits, rejects any counter at or below the last accepted value, and rejects counters that jump unreasonably far. Counter rollover on devices that reset them is the most common cause of “device joined but no uplinks appear”. If you see a device repeatedly joining and then silently failing, check the frame counter state and the device’s nonce handling before suspecting radio.

Payload confidentiality is end to end between the device and the application, but the network server holds the network keys and can see headers, MAC commands and timing. Metadata such as DevAddr, frame counters, gateway locations and RSSI is visible to whoever runs the server. If you let a third party operate your network server, that metadata is theirs to see. This is one concrete security argument for self-hosting.

Securing the deployment itself

The protocol’s cryptography does not protect your infrastructure. Four controls matter in practice. Put TLS and authentication on the MQTT broker, because an attacker who can publish to gateway topics can inject uplinks or flood the server (they cannot forge a valid MIC without keys, but they can burn CPU and pollute metadata). Keep the Semtech UDP path on a private network if you use it. Protect the PostgreSQL database and backups as key stores, because device root keys live there; field-level encryption or a secrets manager is worth considering. Finally, secure the web interface and API with strong credentials and per-tenant roles, and expose them only through a reverse proxy.

Adaptive Data Rate: The Decision Loop and What It Costs

Adaptive data rate (ADR) is the mechanism by which the network server tunes a device’s spreading factor and transmit power to the radio conditions. According to the ChirpStack documentation, ADR lets the server adjust a device’s data rate and transmit power to reduce airtime and power use, which also eases spectrum congestion. The device controls whether ADR is active: the server only adjusts data rate and TX power when an uplink arrives with the ADR flag set, and the feature is intended for stationary devices only (ChirpStack ADR).

LoRaWAN ADR decision loop in ChirpStack using SNR history and installation margin

Figure 3: The ADR loop. The server derives link margin from recent SNR, subtracts an installation margin, and spends the surplus first on faster data rate and then on lower transmit power.

Why airtime is the currency

LoRa trades data rate for sensitivity: each step up in spreading factor roughly halves the bit rate and extends range. Airtime therefore grows steeply with spreading factor, and airtime is what drains batteries and consumes channel capacity. Using the standard Semtech time-on-air formula, I computed illustrative values for a 23-byte physical payload (13 bytes of LoRaWAN overhead plus 10 bytes of application data) at 125 kHz bandwidth and coding rate 4/5 with an 8-symbol preamble:

Spreading factor Time on air Minimum interval at 1% duty cycle
SF7 about 62 ms about 6 s
SF9 about 206 ms about 20 s
SF10 about 371 ms about 37 s
SF12 about 1,483 ms about 147 s

These figures come from my own calculation with the published formula, not from a ChirpStack measurement. The ratio between SF12 and SF7 is about 24 times. A node that ADR can move from SF12 to SF7 sends the same data using roughly 4% of the airtime, which is both a battery win and a capacity win, because the same gateway can then hear far more nodes. The duty-cycle column reflects the 1% limit that applies in some EU868 sub-bands; regional rules differ, so check your regulator’s current text.

The decision loop

The ChirpStack page deliberately keeps the algorithm abstract. It states that several algorithms are built in, that you may add your own written in JavaScript with file paths set in the configuration file, and that each custom algorithm provides name(), id() and handle(req) functions. The handler receives inputs including the required SNR per data rate, the uplink history (SNR, RSSI and gateway count), the current settings, the allowed minimum and maximum data rate, and the current and maximum TX power index. It returns a new data rate, TX power index and number of transmissions per uplink (ChirpStack ADR). The page does not document the default algorithm’s maths, so what follows is the classic scheme that the LoRa Alliance and Semtech describe and that the community discussions say ChirpStack follows in spirit; verify the details against the source for your version.

The classic loop works like this. Keep a window of recent uplinks, take the best SNR in the window (using the maximum rather than the average protects against fades), and compute the margin as that SNR minus the SNR required to demodulate the current data rate minus the installation margin. Divide the margin by 3 dB to get a number of steps. While steps remain and the data rate is below the maximum, raise the data rate by one. Once at the maximum data rate, lower transmit power in steps while steps remain. If the margin is negative, raise power back toward the maximum.

The installation margin

The documentation describes the installation margin as the link margin kept after choosing data rate and TX power, with higher values making ADR more conservative; its example uses 10 (dB, in the usual convention). It is the single most important tuning knob. A low value squeezes airtime but leaves no headroom for fading, seasonal foliage or a truck parking in front of the gateway. A high value wastes battery. For devices in a changing environment, start high and lower it only after you have weeks of margin data.

A worked example: a node’s best recent SNR is -2 dB at SF9, where the required demodulation SNR is about -12.5 dB. With an installation margin of 10 dB the margin is -2 + 12.5 – 10 = 0.5 dB, so no step is taken. Drop the margin to 5 and you get 5.5 dB, one step of 3 dB, moving the node to SF8. The required SNR values are the commonly published ones for 125 kHz LoRa, and the numbers here are illustrative of the arithmetic, not a recommendation.

The device side: ADR_ACK

If the network is unreachable, an ADR-enabled device cannot wait forever for commands. The specification defines ADR_ACK_LIMIT and ADR_ACK_DELAY (defaults of 64 and 32 frames, as I recall them from the regional parameters) after which the device sets the ADR acknowledgement request bit in uplinks, expecting any downlink within the delay. If none arrives it steps its data rate down, one step at a time, which is how a device recovers after a gateway outage or after moving out of coverage.

Where ADR goes wrong

ADR assumes a static link. Mobile devices should disable it, as the documentation states for stationary-only use. Duty-cycle-limited networks have a subtle failure: ADR commands travel as downlink MAC commands, and a gateway that has exhausted its transmit duty cycle cannot deliver them, so convergence stalls for busy gateways. Devices with infrequent uplinks converge slowly because the history window fills slowly; a node reporting once a day may need weeks to adapt. Finally, the community reports of data rate “stuck” despite good signal usually trace to the device not setting the ADR flag, a device-profile min/max data rate that already caps it, or a region configuration that disallows the target rate.

Everything the network server sends to a device is a downlink, and downlinks are scarce. A gateway is half duplex while it transmits, it is bound by regional duty cycle or dwell-time rules, and the device must be listening at the right moment. The device class defines when it listens.

Class A, B and C

Class A is the mandatory baseline. A device opens two short receive windows after each uplink, and the server can only transmit in those windows. This costs the least energy and gives the worst downlink latency: a queued command waits for the next uplink, which for a device reporting hourly means up to an hour. Class B adds scheduled receive slots synchronised to a gateway beacon, so a device wakes at known ping slots and the server can reach it with bounded latency, at the cost of beacon reception energy and a more complex gateway requirement. Class C keeps the receiver open almost continuously except while transmitting, giving the lowest latency and the highest power consumption, which suits mains-powered actuators.

For the network server this means three different scheduling problems. In Class A it chooses the best gateway for the receive window using signal quality from the uplink. In Class B it has to schedule transmissions into ping slots and keep beacon timing. In Class C it can send at any time but must avoid colliding with gateway duty cycle and with Class A windows it is also serving.

What multicast adds

Unicast downlinks cost one transmission per device. Sending the same payload to 2,000 devices that way is infeasible on a duty-cycled network. Multicast solves this by giving a group of devices a shared multicast address and shared session keys, so one over-the-air frame is received by every member that is listening.

The ChirpStack documentation states that multicast works with Class B and Class C devices. You create multicast groups and assign devices to them, but assignment in ChirpStack does not configure the device; group membership has to be set up on the device separately. When a downlink is queued for a group, ChirpStack works out which gateways must transmit to reach every member, and one payload may be transmitted several times, once per required gateway, with the transmissions spaced apart to reduce collisions (ChirpStack multicast). The page does not state limits on group size, gateway count or timing.

Two design consequences are worth stating. First, multicast has no acknowledgement. The sender does not know who received what, which is why higher layers such as fragmented transport add forward error correction and a status exchange. Second, the cost is proportional to gateways used, not devices. A group whose members are spread over ten gateways costs ten transmissions per payload, each occupying airtime on its gateway.

How group setup reaches the device

Because ChirpStack does not configure the device, something must. The LoRa Alliance defines a remote multicast setup package for this: the server sends a multicast group setup command to each device over ordinary Class A unicast, delivering the address and keys, then a class session command that tells devices when to switch to Class B or Class C for the multicast window. That is the first stage of the FUOTA flow below.

FUOTA: Firmware Updates Over the Air Through Multicast

Updating firmware over LoRaWAN looks absurd on paper: a few hundred kilobytes over a channel that carries tens to a few hundred bytes per frame at low data rates and is duty cycle limited. It works because of three cooperating LoRa Alliance packages and a lot of redundancy. The ChirpStack documentation describes FUOTA as pushing firmware to one or many devices using multicast, based on the LoRaWAN Application Layer Clock Synchronization, Fragmented Data Block Transport and Remote Multicast Setup specifications, each in a version 1 and version 2. It also warns that device support is optional: unless a device’s documentation says it implements FUOTA, assume it does not (ChirpStack FUOTA).

LoRaWAN FUOTA pipeline with multicast setup, fragmented transport and parity recovery

Figure 4: The FUOTA pipeline. Unicast sets the stage, multicast carries fragments and parity, unicast status reports tell the server how many extra fragments to send.

The three packages and what each is for

Clock synchronisation gives the device an accurate network time. Class B and Class C multicast sessions are scheduled at absolute times, so a device whose clock has drifted would miss its own session. The package corrects clock offset using a request-response exchange over unicast.

Remote multicast setup configures the group on the device: multicast address, keys and the start time and class for the session. Fragmented data block transport splits the firmware image into fixed-size fragments, transmitted over multicast, and adds forward error correction. The receiver reassembles the image even when some fragments are lost, because redundant parity fragments computed from the data fragments let it reconstruct missing ones. Once the device has enough fragments it verifies a checksum or integrity value and the application bootloader swaps in the new image.

Worked fragment budget (illustrative)

Suppose a 100 KB image and a device in a region where the usable application payload at the chosen data rate is 48 bytes per fragment. That is 100 x 1024 / 48, about 2,134 uncoded fragments. Allow 20% redundancy for a poor link and you transmit about 2,560. Using my airtime calculation, a fragment with 13 bytes of overhead plus 48 bytes of payload at SF9 lasts about 370 ms. 2,560 fragments at that airtime is roughly 947 seconds of on-air time, about 16 minutes before any duty-cycle spacing, per gateway. If three gateways must each retransmit to cover the group, gateway-side airtime triples, though the devices still hear it in parallel. With a 1% duty cycle limit, a single gateway that is bound by it would need about 100 times that on-air time of wall-clock, around 26 hours, which is why regulation, rather than radio physics, often decides whether FUOTA is realistic in a given band. These are illustrative figures, not measurements, and the duty cycle depends entirely on your region and sub-band.

The same arithmetic explains why a higher data rate for the multicast session is the single biggest lever: halving airtime halves the time, but the group’s worst device must still be able to decode it.

The control loop and failure modes

The server runs a loop: send fragments, ask devices for fragment status over unicast, and send more parity if devices report missing fragments. Failures cluster in a few places.

The device class is wrong: a device profile left at Class A cannot receive the multicast session. A community thread about a v4.12 deployment illustrates this: a user with an STM32 and SX1262 reported that multicast setup started but did not work, and attributed the problem to the Class C device profile; after fixing it, FUOTA showed success but the firmware did not change (ChirpStack forum). Another poster using a V1 package reported all fragments received after about 28 minutes but no completion callback and no apparent image swap. These are anecdotes, not a bug confirmation, yet they point at the part the network cannot fix: the device’s bootloader and completion reporting are the device vendor’s responsibility.

Other common causes: clock drift causing missed sessions, a gateway that cannot transmit because of duty cycle, devices in deep fades that need unicast fallback, and fragment size mismatched to the data rate. Always test on a handful of devices on the bench, then a small pilot group, then the fleet, and keep a rollback image in the bootloader.

Scaling, High Availability and Failure Modes

A single ChirpStack instance with one Redis and one PostgreSQL goes a long way, but production deployments still need a plan for each component’s failure.

Where the limits are

The server is stateless apart from its stores, so you can run several instances against the same Redis, PostgreSQL and broker. Which instance handles a given frame depends on how MQTT subscriptions are shared. I did not find ChirpStack documentation on shared subscriptions in the pages I retrieved, so confirm the supported HA topology in the current documentation or release notes before designing around it. Treat every statement here about multi-instance behaviour as a design question to validate, not as a verified feature.

In practice the order of bottlenecks is: gateway airtime and duty cycle first, Redis latency second, broker throughput third, PostgreSQL last. PostgreSQL matters most when you enable integrations that write every uplink to SQL, so isolate application storage from the network server database.

Failure and recovery matrix

Component down Symptom Recovery
MQTT broker No uplinks, no downlinks Restart; gateways reconnect; queued downlinks may be late
Redis without persistence Sessions forgotten, devices appear to vanish Rejoin devices; plan for a join storm
PostgreSQL Cannot join or edit config; established flows may continue from cache Failover to replica
Gateway Bridge Gateways behind it silent Restart; UDP is connectionless so loss is invisible
Single gateway Coverage hole, higher SF via ADR fallback Add overlapping gateways

Overlapping gateway coverage is both a radio and a reliability measure: with deduplication, adding gateways raises success probability without touching the server.

Observability

Watch four signals: uplink rate per gateway, deduplication ratio (gateways per frame), join success rate and Redis command latency. A sudden drop in the deduplication ratio signals gateway trouble before devices complain. A rising join request rate with a falling success rate signals clock, nonce or key problems, or a Redis flush. Track ADR convergence as the distribution of data rates across the fleet over time; if it does not settle, your installation margin or device behaviour is wrong.

A Self-Hosted Stack: Docker Compose Sketch

The following compose file is a minimal sketch of the dependency set: broker, Redis, PostgreSQL, ChirpStack and a Gateway Bridge. It is untested and illustrative. Image tags, environment variable names and configuration paths change between ChirpStack releases, so base your real stack on the official docker compose example in the documentation for the version you deploy. The structure, not the exact values, is the point.

# UNTESTED sketch - verify image tags, env names and config paths against official docs
services:
  chirpstack:
    image: chirpstack/chirpstack:4
    command: -c /etc/chirpstack
    volumes:
      - ./configuration/chirpstack:/etc/chirpstack
    depends_on: [postgres, redis, mosquitto]
    ports: ["8080:8080"]          # web UI and API
  chirpstack-gateway-bridge:
    image: chirpstack/chirpstack-gateway-bridge:4
    ports: ["1700:1700/udp"]      # Semtech UDP only if needed, keep private
    volumes:
      - ./configuration/gateway-bridge:/etc/chirpstack-gateway-bridge
    depends_on: [mosquitto]
  mosquitto:
    image: eclipse-mosquitto:2
    volumes:
      - ./configuration/mosquitto:/mosquitto/config
  postgres:
    image: postgres:14-alpine
    environment:
      POSTGRES_PASSWORD: change-me
    volumes:
      - pgdata:/var/lib/postgresql/data
  redis:
    image: redis:7-alpine
    command: redis-server --appendonly yes   # persist sessions
    volumes:
      - redisdata:/data
volumes:
  pgdata:
  redisdata:

Three details deserve explanation. Redis is started with append-only persistence so that a restart does not erase device sessions. The UDP port is exposed only because some gateways need it, and in production you would bind it to a private interface. PostgreSQL needs the extensions ChirpStack’s migrations request, which the official guide documents; I did not verify which, so follow the guide rather than this sketch.

Trade-offs, Gotchas, and What Goes Wrong

Self-hosting is a staffing decision. Hosted networks absorb patching, scaling and uptime. Self-hosting hands you key custody, backups, certificate renewal and on-call. The break-even is control over data and policy, not cost. If you only need a hundred sensors on one site, the operational overhead may outweigh the benefits.

ADR is a battery optimisation with a reliability cost. Aggressive ADR pushes devices to the edge of their link budget. Fades then cause lost frames that retries must absorb. Choose the installation margin from your reliability target, not from the airtime saving.

Multicast efficiency depends on topology. One gateway reaching an entire group is cheap. A group spread across many gateways multiplies transmissions. Group devices by coverage area, not by organisational convenience.

Duty cycle quietly dominates. In sub-bands with a 1% limit, downlinks, which gateways must send, are the first casualty of busy periods. Confirmed uplinks, ADR commands and FUOTA all compete for the same scarce gateway transmit time. Avoid confirmed uplinks for high-rate sensors.

Join storms. After a Redis wipe or a regional power cut, thousands of devices rejoin together. Join requests collide, and join accepts need gateway airtime at fixed delays. Jittered retry in device firmware and persistence in Redis are the mitigations.

Mixed vendors, mixed assumptions. Class, MAC version, regional parameters revision and RX delay values live in the device profile; any mismatch with the firmware produces silent downlink failures. Keep a table of verified profiles per device model.

Anti-pattern: exposing everything. The broker, the UDP port and the web interface should not be open to the internet. Use TLS, per-gateway credentials and a reverse proxy.

Practical Recommendations

Start by deciding the failure budget, not the feature list. Pick the gateway path that matches your security posture: Concentratord with the MQTT Forwarder where you control the gateway, a Gateway Bridge for legacy hardware. Put Redis on persistence, keep PostgreSQL backed up as a key store, and treat the broker as critical infrastructure.

For ADR, enable it for stationary devices only, start with a conservative installation margin, and review the data rate distribution after a few weeks. For multicast and FUOTA, validate device support first, test on the bench, run a pilot, and keep a rollback image. Run FUOTA at the highest data rate the weakest device can decode, and schedule it outside peak uplink hours.

Checklist:

  • Device profiles per hardware model with verified MAC version, class and RX settings
  • OTAA only; no ABP in new designs
  • TLS and credentials on MQTT; UDP kept private
  • Redis append-only persistence and tested restore
  • PostgreSQL backups and tested restore, including key material
  • ADR enabled for stationary nodes; margin reviewed quarterly
  • Multicast groups aligned with gateway coverage
  • FUOTA staged: bench, pilot, fleet; bootloader rollback verified
  • Dashboards for deduplication ratio, join success and Redis latency

Frequently Asked Questions

What is ChirpStack in LoRaWAN?

ChirpStack is an open-source, MIT-licensed LoRaWAN network server. It manages gateways, devices and tenants through a web interface and gRPC API, deduplicates uplinks from multiple gateways, handles joins and session keys, schedules downlinks and integrates with databases and cloud services. It relies on PostgreSQL, Redis and an MQTT broker, and pairs with gateway-side components such as Concentratord, the MQTT Forwarder and the Gateway Bridge.

What does the ChirpStack Gateway Bridge do?

The Gateway Bridge converts messages from the Semtech UDP Packet Forwarder or Semtech Basics Station into MQTT messages that the network server consumes. It can run on the gateway or in the cloud. If your gateway runs Concentratord with the MQTT Forwarder, it publishes to the broker directly and you do not need a Gateway Bridge for that gateway.

How does LoRaWAN ADR work in ChirpStack?

When a device sets the ADR flag, ChirpStack evaluates recent uplink SNR against the requirement of the current data rate and an installation margin, then raises data rate and lowers transmit power through MAC commands. Built-in algorithms exist and custom JavaScript algorithms can be added. ADR is intended for stationary devices; a higher installation margin makes it more conservative.

Does ChirpStack support LoRaWAN FUOTA?

Yes. Its FUOTA feature pushes firmware to one or many devices using multicast and builds on the LoRa Alliance clock synchronisation, fragmented data block transport and remote multicast setup packages. It only works if the device firmware implements those packages and a compatible bootloader, so check device documentation first.

Do I need Redis and PostgreSQL for ChirpStack?

PostgreSQL is the default database for durable configuration, and the repository notes SQLite as an option for lighter setups. Redis is used for fast, short-lived state such as sessions and deduplication, and the project’s tests require it. Plan both as availability-critical, with persistence enabled on Redis and regular backups of PostgreSQL.

Can ChirpStack run in high availability?

The server keeps state in external stores, which makes running multiple instances plausible, but I could not verify the supported HA topology from the documentation pages retrieved. Validate multi-instance behaviour, MQTT subscription sharing and Redis failover in the current documentation and in a staging test before committing to a design.

Further Reading

By Riju — about

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *