diff --git a/src/platform/Darwin/BleConnectionDelegateImpl.mm b/src/platform/Darwin/BleConnectionDelegateImpl.mm
index d69623006d84ab..69973669318a62 100644
--- a/src/platform/Darwin/BleConnectionDelegateImpl.mm
+++ b/src/platform/Darwin/BleConnectionDelegateImpl.mm
@@ -32,6 +32,7 @@
 #include <lib/support/logging/CHIPLogging.h>
 #include <platform/CHIPDeviceLayer.h>
 #include <platform/Darwin/BleConnectionDelegate.h>
+#include <platform/LockTracker.h>
 #include <setup_payload/SetupPayload.h>
 
 #import "UUIDHelper.h"
@@ -40,11 +41,12 @@
 
 constexpr uint64_t kScanningWithDiscriminatorTimeoutInSeconds = 60;
 constexpr uint64_t kScanningWithoutDiscriminatorTimeoutInSeconds = 120;
+constexpr const char * kBleWorkQueueName = "org.csa-iot.matter.framework.ble.workqueue";
 
 @interface BleConnection : NSObject <CBCentralManagerDelegate, CBPeripheralDelegate>
 
-@property (strong, nonatomic) dispatch_queue_t workQueue;
 @property (strong, nonatomic) dispatch_queue_t chipWorkQueue;
+@property (strong, nonatomic) dispatch_queue_t workQueue;
 @property (strong, nonatomic) CBCentralManager * centralManager;
 @property (strong, nonatomic) CBPeripheral * peripheral;
 @property (strong, nonatomic) CBUUID * shortServiceUUID;
@@ -58,7 +60,8 @@ @interface BleConnection : NSObject <CBCentralManagerDelegate, CBPeripheralDeleg
 @property (unsafe_unretained, nonatomic) BleConnectionDelegate::OnConnectionErrorFunct onConnectionError;
 @property (unsafe_unretained, nonatomic) chip::Ble::BleLayer * mBleLayer;
 
-- (id)initWithDiscriminator:(const chip::SetupDiscriminator &)deviceDiscriminator;
+- (id)initWithQueue:(dispatch_queue_t)queue;
+- (id)initWithDiscriminator:(const chip::SetupDiscriminator &)deviceDiscriminator queue:(dispatch_queue_t)queue;
 - (void)setBleLayer:(chip::Ble::BleLayer *)bleLayer;
 - (void)start;
 - (void)stop;
@@ -72,57 +75,80 @@ - (void)update;
 namespace DeviceLayer {
     namespace Internal {
         BleConnection * ble;
+        dispatch_queue_t bleWorkQueue;
 
         void BleConnectionDelegateImpl::NewConnection(
             Ble::BleLayer * bleLayer, void * appState, const SetupDiscriminator & deviceDiscriminator)
         {
+            assertChipStackLockedByCurrentThread();
+
             ChipLogProgress(Ble, "%s", __FUNCTION__);
+            if (!bleWorkQueue) {
+                bleWorkQueue = dispatch_queue_create(kBleWorkQueueName, DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
+            }
+
+            dispatch_async(bleWorkQueue, ^{
+                // If the previous connection delegate was a scan without a discriminator, just reuse it instead of
+                // creating a brand new connection but update the discriminator and the ble layer members.
+                if (ble and ![ble hasDiscriminator]) {
+                    [ble setBleLayer:bleLayer];
+                    ble.appState = appState;
+                    ble.onConnectionComplete = OnConnectionComplete;
+                    ble.onConnectionError = OnConnectionError;
+                    [ble updateWithDiscriminator:deviceDiscriminator];
+                    return;
+                }
 
-            // If the previous connection delegate was a scan without a discriminator, just reuse it instead of
-            // creating a brand new connection but update the discriminator and the ble layer members.
-            if (ble and ![ble hasDiscriminator]) {
+                [ble stop];
+                ble = [[BleConnection alloc] initWithDiscriminator:deviceDiscriminator queue:bleWorkQueue];
                 [ble setBleLayer:bleLayer];
                 ble.appState = appState;
                 ble.onConnectionComplete = OnConnectionComplete;
                 ble.onConnectionError = OnConnectionError;
-                [ble updateWithDiscriminator:deviceDiscriminator];
-                return;
-            }
-
-            CancelConnection();
-            ble = [[BleConnection alloc] initWithDiscriminator:deviceDiscriminator];
-            [ble setBleLayer:bleLayer];
-            ble.appState = appState;
-            ble.onConnectionComplete = OnConnectionComplete;
-            ble.onConnectionError = OnConnectionError;
-            ble.centralManager = [ble.centralManager initWithDelegate:ble queue:ble.workQueue];
+                ble.centralManager = [ble.centralManager initWithDelegate:ble queue:bleWorkQueue];
+            });
         }
 
         void BleConnectionDelegateImpl::PrepareConnection()
         {
-            ChipLogProgress(Ble, "%s", __FUNCTION__);
+            assertChipStackLockedByCurrentThread();
 
-            // If the previous connection delegate was a scan without a discriminator, just reuse it instead of
-            // creating a brand new connection but clear the cache and reset the timer.
-            if (ble and ![ble hasDiscriminator]) {
-                [ble update];
-                return;
+            ChipLogProgress(Ble, "%s", __FUNCTION__);
+            if (!bleWorkQueue) {
+                bleWorkQueue = dispatch_queue_create(kBleWorkQueueName, DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
             }
 
-            CancelConnection();
-            ble = [[BleConnection alloc] init];
-            ble.onConnectionComplete = OnConnectionComplete;
-            ble.onConnectionError = OnConnectionError;
-            ble.centralManager = [ble.centralManager initWithDelegate:ble queue:ble.workQueue];
+            dispatch_async(bleWorkQueue, ^{
+                // If the previous connection delegate was a scan without a discriminator, just reuse it instead of
+                // creating a brand new connection but clear the cache and reset the timer.
+                if (ble and ![ble hasDiscriminator]) {
+                    [ble update];
+                    return;
+                }
+
+                [ble stop];
+                ble = [[BleConnection alloc] initWithQueue:bleWorkQueue];
+                ble.onConnectionComplete = OnConnectionComplete;
+                ble.onConnectionError = OnConnectionError;
+                ble.centralManager = [ble.centralManager initWithDelegate:ble queue:bleWorkQueue];
+            });
         }
 
         CHIP_ERROR BleConnectionDelegateImpl::CancelConnection()
         {
+            assertChipStackLockedByCurrentThread();
+
             ChipLogProgress(Ble, "%s", __FUNCTION__);
-            if (ble) {
+            if (bleWorkQueue == nil) {
+                return CHIP_NO_ERROR;
+            }
+
+            dispatch_async(bleWorkQueue, ^{
                 [ble stop];
                 ble = nil;
-            }
+            });
+
+            bleWorkQueue = nil;
             return CHIP_NO_ERROR;
         }
     } // namespace Internal
@@ -134,15 +160,14 @@ @interface BleConnection ()
 
 @implementation BleConnection
 
-- (id)init
+- (id)initWithQueue:(dispatch_queue_t)queue
 {
     self = [super init];
     if (self) {
         self.shortServiceUUID = [UUIDHelper GetShortestServiceUUID:&chip::Ble::CHIP_BLE_SVC_ID];
-        _workQueue
-            = dispatch_queue_create("org.csa-iot.matter.framework.ble.workqueue", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
         _chipWorkQueue = chip::DeviceLayer::PlatformMgrImpl().GetWorkQueue();
-        _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _workQueue);
+        _workQueue = queue;
+        _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
         _centralManager = [CBCentralManager alloc];
         _found = false;
         _cachedPeripherals = [[NSMutableDictionary alloc] init];
@@ -159,9 +184,9 @@ - (id)init
     return self;
 }
 
-- (id)initWithDiscriminator:(const chip::SetupDiscriminator &)deviceDiscriminator
+- (id)initWithDiscriminator:(const chip::SetupDiscriminator &)deviceDiscriminator queue:(dispatch_queue_t)queue
 {
-    self = [self init];
+    self = [self initWithQueue:queue];
     if (self) {
         _deviceDiscriminator = deviceDiscriminator;
         _hasDeviceDiscriminator = true;
@@ -409,12 +434,28 @@ - (void)start
 - (void)stop
 {
     [self stopScanning];
-    [self disconnect];
     [_cachedPeripherals removeAllObjects];
     _cachedPeripherals = nil;
-    _centralManager.delegate = nil;
-    _centralManager = nil;
-    _peripheral = nil;
+
+    if (!_centralManager || !_peripheral) {
+        return;
+    }
+
+    // Properly closing the underlying ble connections needs to happens
+    // on the chip work queue. At the same time the SDK is trying to
+    // properly unsubscribe and shutdown the connection, so if we nullify
+    // the centralManager and the peripheral members too early it won't be
+    // able to reach those.
+    // This is why closing connections happens as 2 async steps.
+    dispatch_async(_chipWorkQueue, ^{
+        _mBleLayer->CloseAllBleConnections();
+
+        dispatch_async(_workQueue, ^{
+            _centralManager.delegate = nil;
+            _centralManager = nil;
+            _peripheral = nil;
+        });
+    });
 }
 
 - (void)startScanning
@@ -445,16 +486,6 @@ - (void)connect:(CBPeripheral *)peripheral
     [_centralManager connectPeripheral:peripheral options:nil];
 }
 
-- (void)disconnect
-{
-    if (!_centralManager || !_peripheral) {
-        return;
-    }
-
-    _mBleLayer->CloseAllBleConnections();
-    _peripheral = nil;
-}
-
 - (void)update
 {
     [_cachedPeripherals removeAllObjects];