How to Handle BLE Connections at Scale

Your BLE prototype works beautifully with 10 sensors on your desk. Then you deploy 50 devices in a pilot, and connections start dropping. At 75 devices, the gateway becomes unresponsive. You check the Bluetooth spec and find nothing about connection limits. You check Nordic’s documentation and see 20 connections mentioned as a default. So why is everything falling apart at scale?
The Bluetooth specification doesn’t limit connections, but physics does. Your radio can only be in one place at one time, and every connection demands a slice of that finite resource. Developers who successfully deploy thousands of BLE devices understand something that most documentation glosses over: managing BLE at scale is a scheduling problem, not a protocol problem.
This guide covers the architectural patterns that work at 10, 100, and 10,000+ device deployments, from connection interval orchestration to horizontal gateway scaling.
Why Your Gateway Dies at 20 Connections
The BLE specification defines no maximum connection count. So where does the “20 connection limit” come from?
Every BLE connection requires periodic connection events, moments when the central and peripheral wake up, synchronize, and exchange data. The minimum connection interval defined by the spec is 7.5ms. If your radio must service a connection event for Device A, it physically cannot service Device B during that same window.
Here’s the math that governs your ceiling:
Available radio time per connection interval = Interval / Number of connectionsWith a 7.5ms minimum interval and 20 connections, each device gets 375 microseconds of radio time per interval. That’s barely enough for a single packet exchange. Add connection event overhead, frequency hopping settling time, and scheduling jitter, and you hit a wall.
Nordic’s SoftDevice defaults illustrate this reality. The S140 SoftDevice ships with NRF_SDH_BLE_CENTRAL_LINK_COUNT typically set to 20. This isn’t arbitrary. It’s a practical ceiling based on scheduler bandwidth with default intervals.
// sdk_config.h - Nordic default limits
#define NRF_SDH_BLE_CENTRAL_LINK_COUNT 20
#define NRF_SDH_BLE_PERIPHERAL_LINK_COUNT 1Other stacks face identical physics with different knobs. ESP32’s NimBLE defaults to 9 connections. Silicon Labs’ implementation varies by chip. The numbers differ, but the constraint is universal: every stack has a scheduler with finite bandwidth.
Connection Interval Orchestration: Your Primary Scaling Lever
The single most effective technique for scaling connections is staggering connection intervals based on data freshness requirements. Not every sensor needs millisecond-level updates.
The principle: Assign longer intervals to devices that tolerate latency, reserving short intervals for high-priority or high-throughput devices.
Consider a manufacturing floor with 50 temperature sensors and 10 vibration monitors on a single gateway.
| Device Type | Count | Data Requirement | Interval | Radio Budget Share |
|---|---|---|---|---|
| Temperature sensors | 50 | Update every 30s is fine | 4000ms | Minimal |
| Vibration monitors | 10 | Real-time anomaly detection | 100ms | Dominant |
With 4-second intervals, those 50 temperature sensors only need connection events every 4000ms. Sharing radio time becomes trivial. The 10 vibration monitors consume the bulk of radio bandwidth, but that’s appropriate given their requirements.
Nordic implementation pattern:
// Request a connection parameter update after connection established
ble_gap_conn_params_t conn_params = {
.min_conn_interval = MSEC_TO_UNITS(interval_ms, UNIT_1_25_MS),
.max_conn_interval = MSEC_TO_UNITS(interval_ms, UNIT_1_25_MS),
.slave_latency = 0,
.conn_sup_timeout = MSEC_TO_UNITS(4000, UNIT_10_MS)
};
err_code = sd_ble_gap_conn_param_update(conn_handle, &conn_params);Watch for the thundering herd: When a gateway boots or power cycles, all devices attempt reconnection simultaneously. If they all start with identical intervals, connection events collide. Implement randomized initial intervals or staggered reconnection scheduling to spread the load across time.
// Stagger reconnection attempts at boot
uint32_t reconnect_delay_ms = (device_index * 100) + (rand() % 50);
app_timer_start(reconnect_timer, APP_TIMER_TICKS(reconnect_delay_ms), context);This technique transfers directly to ESP-IDF, Zephyr, and Silicon Labs stacks. Only the API calls change.
Connection Lifecycle State Machine: Preventing Resource Exhaustion
At scale, connections fail. Devices move out of range. Interference causes supervision timeouts. Without disciplined lifecycle management, you accumulate zombie connections that consume resources and eventually exhaust your connection pool.
Recommended state machine:
┌──────────────┐
│ DISCONNECTED │◄───────────────────────────────┐
└──────┬───────┘ │
│ Initiate connection │
▼ │
┌──────────────┐ │
│ CONNECTING │────── Timeout ─────────────────┤
└──────┬───────┘ │
│ Connected event │
▼ │
┌──────────────┐ │
│ CONNECTED │────── Supervision timeout ─────┤
└──────┬───────┘ │
│ Service discovery complete │
▼ │
┌──────────────┐ │
│ ACTIVE │────── Link loss ───────────────┤
└──────┬───────┘ │
│ No traffic for N seconds │
▼ │
┌──────────────┐ │
│ IDLE │────── Supervision timeout ─────┘
└──────────────┘Critical patterns for scale:
- Connection attempt timeouts: Don’t block the connection queue waiting for unreachable devices. Set aggressive timeouts (2-5 seconds) and move on.
// Connection attempt with timeout enforcement
typedef struct {
uint16_t conn_handle;
connection_state_t state;
uint32_t state_entered_tick;
uint8_t retry_count;
} connection_context_t;
void connection_manager_tick(void) {
for (int i = 0; i < MAX_CONNECTIONS; i++) {
connection_context_t *ctx = &connections[i];
uint32_t elapsed = current_tick - ctx->state_entered_tick;
if (ctx->state == STATE_CONNECTING && elapsed > CONNECT_TIMEOUT_TICKS) {
sd_ble_gap_connect_cancel();
ctx->state = STATE_DISCONNECTED;
ctx->retry_count++;
schedule_retry_with_backoff(ctx);
}
}
}Exponential backoff: When a device becomes unreachable, don’t hammer reconnection attempts. Double the delay between attempts up to a maximum.
Resource pooling: Pre-allocate connection contexts at boot. When you’ve allocated all slots, reject new connection requests gracefully rather than crashing.
#define MAX_MANAGED_CONNECTIONS 100 // Includes disconnected devices we track
static connection_context_t connection_pool[MAX_MANAGED_CONNECTIONS];
static uint8_t active_connection_count = 0;- Priority eviction: Under resource pressure, disconnect low-priority idle connections to make room for high-priority devices.
Nordic’s SoftDevice event model maps cleanly to these state transitions. BLE_GAP_EVT_CONNECTED, BLE_GAP_EVT_DISCONNECTED, and BLE_GAP_EVT_TIMEOUT drive your state machine forward.
Horizontal Scaling: Gateway Mesh Architecture
A single radio, no matter how optimized, maxes out around 20-50 active connections in practice. You might reach 100 with aggressive interval tuning and mostly-idle devices. For industrial deployments with thousands of devices, you need horizontal scaling.
Option 1: Multi-radio gateways
A single gateway can host multiple BLE radios, each with its own connection pool. A host MCU or Linux SBC coordinates them:
┌────────────────────────────────────────────┐
│ Gateway Hardware │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ nRF52 │ │ nRF52 │ │ nRF52 │ │
│ │ Radio 1 │ │ Radio 2 │ │ Radio 3 │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │
│ └──────────┼──────────┘ │
│ │ │
│ ┌───────▼────────┐ │
│ │ Host MCU/ │ │
│ │ Coordinator │ │
│ └───────┬────────┘ │
└──────────────────┼─────────────────────────┘
│ Backhaul
▼
Aggregation ServerThis multiplies your per-gateway capacity by the number of radios, but adds hardware cost and coordination complexity.
Option 2: Gateway mesh topology
For true industrial scale (tens of thousands of devices), distribute gateways spatially, each covering a zone:
┌─────────────────┐
│ Aggregation │
│ Server │
└────────┬────────┘
│ Ethernet/LTE
┌────────────────────┼────────────────────┐
│ │ │
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│ Gateway │ │ Gateway │ │ Gateway │
│ Zone A │ │ Zone B │ │ Zone C │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
┌────┴────┐ ┌────┴────┐ ┌────┴────┐
│ 25 BLE │ │ 25 BLE │ │ 25 BLE │
│ devices │ │ devices │ │ devices │
└─────────┘ └─────────┘ └─────────┘Load balancing strategies:
Static zone assignment: Assign devices to gateways during provisioning based on physical location. Simplest to implement, but doesn’t adapt to device movement or gateway failure.
RSSI-based dynamic assignment: Devices connect to the gateway with the strongest signal. Requires coordination to avoid devices bouncing between gateways.
Capacity-aware routing: Gateways advertise their current load; devices or a coordinator routes connections to least-loaded gateways.
Industrial deployment heuristics: Production deployments typically allocate 1 gateway per 20-30 devices to maintain reliability margin. That overhead accounts for device failures, interference, and headroom for connection storms during recovery scenarios.
This gateway mesh topology is distinct from Bluetooth Mesh (the SIG-defined mesh networking protocol). A gateway mesh is an architectural pattern using standard BLE connections with application-layer coordination. No mesh protocol required on devices.
Monitoring and Graceful Degradation
At scale, problems are statistical. You need metrics to detect degradation before users notice.
Essential metrics:
- Connection success rate (should be >95%)
- Average time from connection initiation to GATT ready
- Connection queue depth
- RSSI distribution across devices
- Supervision timeout frequency
Degradation strategies:
- Shed low-priority connections when queue depth exceeds threshold
- Dynamically increase intervals under load (stretch 100ms connections to 200ms temporarily)
- Alert at 80% capacity (don’t wait for failure)
Nordic provides sd_ble_gap_conn_count() to query active connections programmatically. Instrument this in your monitoring loop alongside custom metrics from your connection manager.
Building Your Scaling Strategy
BLE at scale requires a mental shift: connections are a managed resource pool, not fire-and-forget operations.
Start with connection interval orchestration. It’s the highest-leverage change you can make. Match intervals to actual data freshness requirements, and you’ll likely triple your practical connection ceiling.
Add a disciplined lifecycle state machine to prevent resource exhaustion from failed connections and zombie states.
When single-radio limits become the bottleneck, scale horizontally through multi-radio gateways or gateway mesh architecture. Industrial deployments reaching tens of thousands of devices invariably use this pattern.
The techniques here apply regardless of your BLE stack: Nordic, ESP32, Silicon Labs, or others. The physics are the same; only the APIs differ. Start with your most constrained gateway, instrument it thoroughly, and use the data to drive your architecture decisions.
Hubble Network enables direct satellite connectivity for IoT devices, eliminating gateway infrastructure entirely. See how it works →