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 300e235dac507a32d253b64e884fd672b0520d41..7c50e19a02e550320111a27fcd1771c9085bd4f5 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
@@ -26,6 +26,8 @@ import android.content.Intent
 import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
 import android.content.IntentFilter
 import android.os.IBinder
+import androidx.core.app.ServiceCompat
+import androidx.core.app.ServiceCompat.stopForeground
 import androidx.core.content.ContextCompat
 import dagger.hilt.android.AndroidEntryPoint
 import org.briarproject.mailbox.R
@@ -103,7 +105,10 @@ class MailboxService : Service() {
         startForeground(
             NOTIFICATION_MAIN_ID,
             notificationManager.getServiceNotification(
-                Starting(getString(R.string.startup_headline))
+                Starting(
+                    status = getString(R.string.startup_headline),
+                    isCancelable = false,
+                )
             )
         )
 
@@ -156,7 +161,7 @@ class MailboxService : Service() {
     override fun onDestroy() {
         super.onDestroy()
         LOG.info("Destroyed")
-        stopForeground(true)
+        stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
         if (receiver != null) unregisterReceiver(receiver)
         if (started) {
             androidExecutor.runOnBackgroundThread {
diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/StatusManager.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/StatusManager.kt
index 455321245797ae82a040439dc1bfff4157056ba5..6d2bce89ca6c22484d27ccc892fa2298dabaa80d 100644
--- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/StatusManager.kt
+++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/StatusManager.kt
@@ -25,6 +25,7 @@ import android.graphics.Bitmap
 import androidx.annotation.StringRes
 import androidx.annotation.UiThread
 import dagger.hilt.android.qualifiers.ApplicationContext
+import kotlinx.coroutines.DelicateCoroutinesApi
 import kotlinx.coroutines.Dispatchers.IO
 import kotlinx.coroutines.Dispatchers.Main
 import kotlinx.coroutines.GlobalScope
@@ -60,6 +61,7 @@ import javax.inject.Singleton
 import kotlin.math.min
 
 @Singleton
+@OptIn(DelicateCoroutinesApi::class)
 class StatusManager @Inject constructor(
     @ApplicationContext private val context: Context,
     lifecycleManager: LifecycleManager,
@@ -123,7 +125,7 @@ class StatusManager @Inject constructor(
     object NeedOnboarding : MailboxAppState(false)
     object NeedsDozeExemption : MailboxAppState(false)
     object NotStarted : MailboxAppState(false)
-    data class Starting(val status: String) : MailboxAppState(true)
+    data class Starting(val status: String, val isCancelable: Boolean) : MailboxAppState(true)
     data class StartedSettingUp(val qrCode: Bitmap, val link: String) : MailboxAppState(true)
     object StartedSetupComplete : MailboxAppState(true)
     object ErrorClockSkew : MailboxAppState(true)
@@ -192,16 +194,26 @@ class StatusManager @Inject constructor(
             // Keep this check below WIPING, STOPPING and STOPPED so that the online check
             // does not interfere with these states - no point in showing a network error then.
             online != null && !online -> ErrorNoNetwork
-            ls != LifecycleState.RUNNING -> Starting(getString(R.string.startup_init_app))
+            ls != LifecycleState.RUNNING -> Starting(
+                status = getString(R.string.startup_init_app),
+                isCancelable = false,
+            )
             // RUNNING
             tor != TorState.Published -> when (tor) {
-                TorState.StartingStopping -> Starting(getString(R.string.startup_init_app))
+                TorState.StartingStopping -> Starting(
+                    status = getString(R.string.startup_init_app),
+                    isCancelable = true,
+                )
                 is TorState.Enabling -> Starting(
-                    getString(R.string.startup_bootstrapping_tor, tor.percent)
+                    status = getString(R.string.startup_bootstrapping_tor, tor.percent),
+                    isCancelable = true,
                 )
                 TorState.ClockSkewed -> ErrorClockSkew
                 TorState.Inactive -> ErrorNoNetwork
-                else -> Starting(getString(R.string.startup_publishing_onion_service))
+                else -> Starting(
+                    status = getString(R.string.startup_publishing_onion_service),
+                    isCancelable = true,
+                )
             }
             setup == SetupComplete.FALSE -> {
                 // FIXME we shouldn't do expensive calls on the UiThread
diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/StartupFragment.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/StartupFragment.kt
index 792e7f6f9eb261871bed2cca0a82ede2f18c9877..542cae88421cb8f8db81e377f57f68e5e51ae79e 100644
--- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/StartupFragment.kt
+++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/StartupFragment.kt
@@ -22,7 +22,10 @@ package org.briarproject.mailbox.android.ui
 import android.os.Bundle
 import android.view.LayoutInflater
 import android.view.View
+import android.view.View.GONE
+import android.view.View.VISIBLE
 import android.view.ViewGroup
+import android.widget.Button
 import android.widget.TextView
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
@@ -36,6 +39,7 @@ class StartupFragment : Fragment() {
 
     private val viewModel: MailboxViewModel by activityViewModels()
     private lateinit var statusDetail: TextView
+    private lateinit var cancelButton: Button
 
     override fun onCreateView(
         inflater: LayoutInflater,
@@ -47,6 +51,7 @@ class StartupFragment : Fragment() {
 
     override fun onViewCreated(v: View, savedInstanceState: Bundle?) {
         statusDetail = v.findViewById(R.id.statusDetail)
+        cancelButton = v.findViewById(R.id.button)
 
         launchAndRepeatWhileStarted {
             viewModel.appState.collect { onAppStateChanged(it) }
@@ -58,6 +63,15 @@ class StartupFragment : Fragment() {
     private fun onAppStateChanged(state: MailboxAppState) {
         if (state is Starting) {
             statusDetail.text = state.status
+            if (state.isCancelable) {
+                cancelButton.visibility = VISIBLE
+                cancelButton.setOnClickListener {
+                    viewModel.stopLifecycle()
+                    requireActivity().finishAffinity()
+                }
+            } else {
+                cancelButton.visibility = GONE
+            }
         }
     }
 
diff --git a/mailbox-android/src/main/res/layout/fragment_startup.xml b/mailbox-android/src/main/res/layout/fragment_startup.xml
index 1ce5cd181a571784f2e5548dbb385d7e3296035b..1680ce47098a801bd3f746e576123238e627c302 100644
--- a/mailbox-android/src/main/res/layout/fragment_startup.xml
+++ b/mailbox-android/src/main/res/layout/fragment_startup.xml
@@ -46,4 +46,18 @@
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toBottomOf="@id/statusHeadline" />
 
+    <Button
+        android:id="@+id/button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="16dp"
+        android:text="@string/cancel"
+        android:visibility="gone"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/statusDetail"
+        app:layout_constraintVertical_bias="1.0"
+        tools:visibility="visible" />
+
 </androidx.constraintlayout.widget.ConstraintLayout>