diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxService.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxService.kt
index f284e88b59593cee19bde93e4752c212c2d8276a..e03afbf92355bf008dca8be3141fa1291101f98e 100644
--- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxService.kt
+++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxService.kt
@@ -158,7 +158,7 @@ class MailboxService : Service() {
         if (started) {
             androidExecutor.runOnBackgroundThread {
                 try {
-                    lifecycleManager.stopServices()
+                    lifecycleManager.stopServices(true)
                     lifecycleManager.waitForShutdown()
                 } catch (e: InterruptedException) {
                     LOG.info("Interrupted while waiting for shutdown")
diff --git a/mailbox-cli/src/main/java/org/briarproject/mailbox/cli/Main.kt b/mailbox-cli/src/main/java/org/briarproject/mailbox/cli/Main.kt
index 2f790acbd0730d666e33647b4f9bf9c8e3fae29d..a2292d4b0b66303b48957d712b91430fcd6e7009 100644
--- a/mailbox-cli/src/main/java/org/briarproject/mailbox/cli/Main.kt
+++ b/mailbox-cli/src/main/java/org/briarproject/mailbox/cli/Main.kt
@@ -25,7 +25,6 @@ import com.github.ajalt.clikt.core.CliktCommand
 import com.github.ajalt.clikt.parameters.options.counted
 import com.github.ajalt.clikt.parameters.options.flag
 import com.github.ajalt.clikt.parameters.options.option
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.takeWhile
 import kotlinx.coroutines.runBlocking
 import org.briarproject.mailbox.core.CoreEagerSingletons
@@ -122,7 +121,7 @@ class Main : CliktCommand(
     private fun startLifecycle() {
         Runtime.getRuntime().addShutdownHook(
             Thread {
-                lifecycleManager.stopServices()
+                lifecycleManager.stopServices(false)
                 lifecycleManager.waitForShutdown()
             }
         )
diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManager.kt b/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManager.kt
index a15d3b78ce61fe1a4a748f5c30ee95a27a5247e9..73f9012304f692a37ece138e49a06ce33004eaec 100644
--- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManager.kt
+++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManager.kt
@@ -82,7 +82,7 @@ interface LifecycleManager {
      * [Database].
      */
     @Wakeful
-    fun stopServices()
+    fun stopServices(exitAfterStopping: Boolean)
 
     /**
      * Wipes entire database as well as stored files. Also stops all services
diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManagerImpl.kt b/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManagerImpl.kt
index c598732ba9a82b8ccf239479574864bfd2529e89..1a76a857ec0ac89251afd7ea148309f8f52c569c 100644
--- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManagerImpl.kt
+++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManagerImpl.kt
@@ -167,7 +167,7 @@ internal class LifecycleManagerImpl @Inject constructor(
     }
 
     @GuardedBy("startStopWipeSemaphore")
-    override fun stopServices() {
+    override fun stopServices(exitAfterStopping: Boolean) {
         LOG.info("stopServices()")
         try {
             LOG.info("acquiring start stop semaphore")
@@ -214,8 +214,10 @@ internal class LifecycleManagerImpl @Inject constructor(
             startStopWipeSemaphore.release()
             // This is for the CLI where we might call stopServices() twice due to the shutdown
             // hook. In order to avoid a deadlock with calling exitProcess() from two threads, make
-            // sure here that it gets called only once.
-            if (stopped) {
+            // sure here that it gets called only once. Also, only exit if exitAfterStopping is true
+            // because we need to avoid calling exitProcess() on a shutdown hook, which causes a
+            // deadlock by itself.
+            if (stopped && exitAfterStopping) {
                 LOG.info("Exiting")
                 exitProcess(0)
             }
@@ -261,7 +263,7 @@ internal class LifecycleManagerImpl @Inject constructor(
             // If we were not do this, the webserver would wait for the request to finish and the
             // request would wait for the webserver to finish.
             thread {
-                stopServices()
+                stopServices(true)
             }
             return true
         } finally {
diff --git a/mailbox-core/src/test/java/org/briarproject/mailbox/core/server/IntegrationTest.kt b/mailbox-core/src/test/java/org/briarproject/mailbox/core/server/IntegrationTest.kt
index 8598918c369ac4597032ecc037d1bf1ddc9ec4d0..d1365003964f9e05559f9524104d48f661541a72 100644
--- a/mailbox-core/src/test/java/org/briarproject/mailbox/core/server/IntegrationTest.kt
+++ b/mailbox-core/src/test/java/org/briarproject/mailbox/core/server/IntegrationTest.kt
@@ -87,7 +87,7 @@ abstract class IntegrationTest(private val installJsonFeature: Boolean = true) {
 
     @AfterAll
     fun tearDown() {
-        lifecycleManager.stopServices()
+        lifecycleManager.stopServices(false)
         lifecycleManager.waitForShutdown()
     }