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 e52d5e25767c4b6dc448fe4a64b15357c35e5a8d..5d94053ba090f1db43cd022dedb0236384c94ab5 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
@@ -40,6 +40,7 @@ import org.briarproject.mailbox.android.ui.StartupFailureActivity.Companion.EXTR
 import org.briarproject.mailbox.android.ui.StartupFailureActivity.StartupFailure
 import org.briarproject.mailbox.android.ui.WipeCompleteActivity
 import org.briarproject.mailbox.core.lifecycle.LifecycleManager
+import org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult.CLOCK_ERROR
 import org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult.LIFECYCLE_REUSE
 import org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult.SERVICE_ERROR
 import org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult.SUCCESS
@@ -138,6 +139,7 @@ class MailboxService : Service() {
                 SUCCESS -> started = true
                 SERVICE_ERROR -> showStartupFailure(StartupFailure.SERVICE_ERROR)
                 LIFECYCLE_REUSE -> showStartupFailure(StartupFailure.LIFECYCLE_REUSE)
+                CLOCK_ERROR -> showStartupFailure(StartupFailure.CLOCK_ERROR)
             }
         }
         // Register for device shutdown broadcasts
diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/StartupFailureActivity.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/StartupFailureActivity.kt
index c73032b3cb7bc0222e55f0ef7513c86a4f2be210..866665aa389a75cf929a8cca4e6d83ddaabfc8ad 100644
--- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/StartupFailureActivity.kt
+++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/ui/StartupFailureActivity.kt
@@ -25,6 +25,7 @@ import android.widget.TextView
 import androidx.appcompat.app.AppCompatActivity
 import dagger.hilt.android.AndroidEntryPoint
 import org.briarproject.mailbox.R
+import org.briarproject.mailbox.android.ui.StartupFailureActivity.StartupFailure.CLOCK_ERROR
 import org.briarproject.mailbox.android.ui.StartupFailureActivity.StartupFailure.LIFECYCLE_REUSE
 import org.briarproject.mailbox.android.ui.StartupFailureActivity.StartupFailure.SERVICE_ERROR
 
@@ -36,7 +37,7 @@ class StartupFailureActivity : AppCompatActivity() {
     }
 
     enum class StartupFailure {
-        SERVICE_ERROR, LIFECYCLE_REUSE
+        SERVICE_ERROR, LIFECYCLE_REUSE, CLOCK_ERROR
     }
 
     override fun onCreate(savedInstanceState: Bundle?) {
@@ -51,6 +52,7 @@ class StartupFailureActivity : AppCompatActivity() {
         val errorRes = when (i.getSerializableExtra(EXTRA_START_RESULT) as StartupFailure) {
             SERVICE_ERROR -> R.string.startup_failed_service_error
             LIFECYCLE_REUSE -> R.string.startup_failed_lifecycle_reuse
+            CLOCK_ERROR -> R.string.startup_failed_clock_error
         }
         val msg: TextView = findViewById(R.id.errorMessage)
         msg.text = getString(errorRes)
diff --git a/mailbox-android/src/main/res/values/strings.xml b/mailbox-android/src/main/res/values/strings.xml
index bcffbb7d70fa972c7d0f97604d18c57d15b7ef3f..d6cdf41f190048688dd6d2ebfa62f90e0b0854fb 100644
--- a/mailbox-android/src/main/res/values/strings.xml
+++ b/mailbox-android/src/main/res/values/strings.xml
@@ -77,4 +77,6 @@
     <string name="startup_failed_activity_title">Mailbox Startup Failure</string>
     <string name="startup_failed_service_error">Mailbox was unable to start a required component.\n\nPlease upgrade to the latest version of the app and try again.</string>
     <string name="startup_failed_lifecycle_reuse">Mailbox has already stopped.\n\nPlease close the app and try again.</string>
+    <string name="startup_failed_clock_error">Mailbox was unable to start because your device\'s clock is wrong.\n\nPlease set your device\'s clock to the right time and try again.</string>
+
 </resources>
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 dfc769bf70a7a5f901efec26148093c49622a35d..4f148a988b5b921b14a65af4b23bbfd74d35b952 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
@@ -34,7 +34,7 @@ interface LifecycleManager {
      * The result of calling [startServices].
      */
     enum class StartResult {
-        SERVICE_ERROR, LIFECYCLE_REUSE, SUCCESS
+        SERVICE_ERROR, LIFECYCLE_REUSE, CLOCK_ERROR, SUCCESS
     }
 
     /**
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 60cd7b27d3671f9fe38b84fa3265c87e010921c2..826921b92bc8945743b4e0491ddb3c895ee1876f 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
@@ -32,10 +32,14 @@ import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState.S
 import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState.WIPING
 import org.briarproject.mailbox.core.lifecycle.LifecycleManager.OpenDatabaseHook
 import org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult
+import org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult.CLOCK_ERROR
 import org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult.LIFECYCLE_REUSE
 import org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult.SERVICE_ERROR
 import org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult.SUCCESS
 import org.briarproject.mailbox.core.setup.WipeManager
+import org.briarproject.mailbox.core.system.Clock
+import org.briarproject.mailbox.core.system.Clock.MAX_REASONABLE_TIME_MS
+import org.briarproject.mailbox.core.system.Clock.MIN_REASONABLE_TIME_MS
 import org.briarproject.mailbox.core.system.System
 import org.briarproject.mailbox.core.util.LogUtils.info
 import org.briarproject.mailbox.core.util.LogUtils.logDuration
@@ -56,6 +60,7 @@ internal class LifecycleManagerImpl @Inject constructor(
     private val db: Database,
     private val wipeManager: WipeManager,
     private val system: System,
+    private val clock: Clock,
 ) :
     LifecycleManager, MigrationListener {
 
@@ -105,6 +110,11 @@ internal class LifecycleManagerImpl @Inject constructor(
             LOG.warn { "Invalid state: ${state.value}" }
             return LIFECYCLE_REUSE
         }
+        val now = clock.currentTimeMillis()
+        if (now < MIN_REASONABLE_TIME_MS || now > MAX_REASONABLE_TIME_MS) {
+            LOG.warn { "System clock is unreasonable: $now" }
+            return CLOCK_ERROR
+        }
         this.wipeHook = wipeHook
         return try {
             LOG.info("Opening database")
diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/system/Clock.java b/mailbox-core/src/main/java/org/briarproject/mailbox/core/system/Clock.java
index 43aacdcbf4700173a6b41db51ca04ce9d86c91b7..eabfdad4987455340fe10216571ddb53d9cc4d6c 100644
--- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/system/Clock.java
+++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/system/Clock.java
@@ -21,4 +21,20 @@ package org.briarproject.mailbox.core.system;
 
 public interface Clock {
 	long currentTimeMillis();
+
+	/**
+	 * The minimum reasonable value for the system clock, in milliseconds
+	 * since the Unix epoch.
+	 * <p/>
+	 * 1 Jan 2023, 00:00:00 UTC
+	 */
+	long MIN_REASONABLE_TIME_MS = 1_672_531_200_000L;
+
+	/**
+	 * The maximum reasonable value for the system clock, in milliseconds
+	 * since the Unix epoch.
+	 * <p/>
+	 * 1 Jan 2121, 00:00:00 UTC
+	 */
+	long MAX_REASONABLE_TIME_MS = 4_765_132_800_000L;
 }
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 dee124dc9797296b3d0d5e78f1228da9a60c7e5f..fe93dc739d78741daa404b20f2727e96d6339d78 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
@@ -15,6 +15,7 @@ import org.briarproject.mailbox.core.TestModule
 import org.briarproject.mailbox.core.TestUtils.getNewRandomContact
 import org.briarproject.mailbox.core.TestUtils.getNewRandomId
 import org.briarproject.mailbox.core.contacts.Contact
+import org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult.SUCCESS
 import org.junit.jupiter.api.AfterAll
 import org.junit.jupiter.api.AfterEach
 import org.junit.jupiter.api.BeforeAll
@@ -26,6 +27,7 @@ import org.junit.jupiter.api.io.TempDir
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 import java.io.File
+import kotlin.test.assertEquals
 import kotlin.test.assertFalse
 import kotlin.test.assertTrue
 
@@ -80,7 +82,7 @@ abstract class IntegrationTest(private val installJsonFeature: Boolean = true) {
         testComponent = DaggerTestComponent.builder().testModule(TestModule(tempDir)).build()
         testComponent.injectCoreEagerSingletons()
         assertFalse(setupManager.hasDb)
-        lifecycleManager.startServices()
+        assertEquals(SUCCESS, lifecycleManager.startServices())
         lifecycleManager.waitForStartup()
         baseUrl = "http://127.0.0.1:${testComponent.getWebServerManager().port}"
     }
diff --git a/mailbox-lib/src/main/java/org/briarproject/mailbox/lib/AbstractMailbox.kt b/mailbox-lib/src/main/java/org/briarproject/mailbox/lib/AbstractMailbox.kt
index 8b22df0c9d1686afae4edd917ff2da0955ade85a..983e9bc37b20def223529d1067644bfeafbbe4a2 100644
--- a/mailbox-lib/src/main/java/org/briarproject/mailbox/lib/AbstractMailbox.kt
+++ b/mailbox-lib/src/main/java/org/briarproject/mailbox/lib/AbstractMailbox.kt
@@ -26,6 +26,7 @@ import org.briarproject.mailbox.core.CoreEagerSingletons
 import org.briarproject.mailbox.core.MailboxLibEagerSingletons
 import org.briarproject.mailbox.core.db.TransactionManager
 import org.briarproject.mailbox.core.lifecycle.LifecycleManager
+import org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult.SUCCESS
 import org.briarproject.mailbox.core.server.WebServerManager
 import org.briarproject.mailbox.core.setup.QrCodeEncoder
 import org.briarproject.mailbox.core.setup.SetupManager
@@ -82,7 +83,8 @@ abstract class AbstractMailbox(protected val customDataDir: File? = null) {
 
     fun startLifecycle() {
         LOG.info { "Starting lifecycle" }
-        lifecycleManager.startServices()
+        val startResult = lifecycleManager.startServices()
+        if (startResult != SUCCESS) error(startResult.name)
         LOG.info { "Waiting for startup" }
         lifecycleManager.waitForStartup()
         LOG.info { "Startup finished" }