diff --git a/binary-compatibility-validator/api/binary-compatibility-validator.api b/binary-compatibility-validator/api/binary-compatibility-validator.api
index 2f4bc8b5f..c64cba596 100644
--- a/binary-compatibility-validator/api/binary-compatibility-validator.api
+++ b/binary-compatibility-validator/api/binary-compatibility-validator.api
@@ -5365,6 +5365,7 @@ public class net/mamoe/mirai/utils/BotConfiguration {
 	public static synthetic fun fileBasedDeviceInfo$default (Lnet/mamoe/mirai/utils/BotConfiguration;Ljava/lang/String;ILjava/lang/Object;)V
 	public final fun getAutoReconnectOnForceOffline ()Z
 	public final fun getBotLoggerSupplier ()Lkotlin/jvm/functions/Function1;
+	public final fun getCacheDirSupplier ()Lkotlin/jvm/functions/Function0;
 	public static final fun getDefault ()Lnet/mamoe/mirai/utils/BotConfiguration;
 	public final fun getDeviceInfo ()Lkotlin/jvm/functions/Function1;
 	public final fun getFirstReconnectDelayMillis ()J
@@ -5406,6 +5407,7 @@ public class net/mamoe/mirai/utils/BotConfiguration {
 	public static synthetic fun redirectNetworkLogToFile$default (Lnet/mamoe/mirai/utils/BotConfiguration;Ljava/io/File;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
 	public final fun setAutoReconnectOnForceOffline (Z)V
 	public final fun setBotLoggerSupplier (Lkotlin/jvm/functions/Function1;)V
+	public final fun setCacheDirSupplier (Lkotlin/jvm/functions/Function0;)V
 	public final fun setDeviceInfo (Lkotlin/jvm/functions/Function1;)V
 	public final fun setFirstReconnectDelayMillis (J)V
 	public final fun setFriendListCache (Lnet/mamoe/mirai/utils/BotConfiguration$FriendListCache;)V
diff --git a/mirai-core-api/src/commonMain/kotlin/utils/BotConfiguration.kt b/mirai-core-api/src/commonMain/kotlin/utils/BotConfiguration.kt
index f9eebb97b..23bf9213a 100644
--- a/mirai-core-api/src/commonMain/kotlin/utils/BotConfiguration.kt
+++ b/mirai-core-api/src/commonMain/kotlin/utils/BotConfiguration.kt
@@ -56,6 +56,11 @@ public open class BotConfiguration { // open for Java
      */
     public var workingDir: File = File(".")
 
+    /**
+     * 缓存数据目录
+     */
+    public var cacheDirSupplier: (() -> File) = { workingDir.resolve("cache") }
+
     /**
      * Json 序列化器, 使用 'kotlinx.serialization'
      */
@@ -396,11 +401,11 @@ public open class BotConfiguration { // open for Java
      */
     public class FriendListCache @JvmOverloads constructor(
         /**
-         * 缓存文件位置, 相对于 [workingDir] 的路径.
+         * 缓存文件位置, 相对于 [cacheDirSupplier] 的路径.
          *
          * 注意: 保存的文件仅供内部使用, 将来可能会变化.
          */
-        public val cacheFile: File = File("cache/friends.json"),
+        public val cacheFile: File = File("friends.json"),
         /**
          * 在有好友列表修改时自动保存间隔
          */
@@ -432,11 +437,11 @@ public open class BotConfiguration { // open for Java
      */
     public class GroupMemberListCache @JvmOverloads constructor(
         /**
-         * 缓存目录位置, 相对于 [workingDir] 的路径.
+         * 缓存目录位置, 相对于 [cacheDirSupplier] 的路径.
          *
          * 注意: 保存的文件仅供内部使用, 将来可能会变化.
          */
-        public val cacheDir: File = File("cache/groups"),
+        public val cacheDir: File = File("groups"),
         /**
          * 在有成员列表修改时自动保存间隔
          */
@@ -470,6 +475,7 @@ public open class BotConfiguration { // open for Java
         return BotConfiguration().also { new ->
             // To structural order
             new.workingDir = workingDir
+            new.cacheDirSupplier = cacheDirSupplier
             new.json = json
             new.parentCoroutineContext = parentCoroutineContext
             new.heartbeatPeriodMillis = heartbeatPeriodMillis
diff --git a/mirai-core/src/commonMain/kotlin/AbstractBot.kt b/mirai-core/src/commonMain/kotlin/AbstractBot.kt
index d2ae64c2e..c818a7a45 100644
--- a/mirai-core/src/commonMain/kotlin/AbstractBot.kt
+++ b/mirai-core/src/commonMain/kotlin/AbstractBot.kt
@@ -68,7 +68,7 @@ internal abstract class AbstractBot<N : BotNetworkHandler> constructor(
     }
 
     // region network
-    internal val serverList: MutableList<Pair<String, Int>> = DefaultServerList.toMutableList()
+    internal val serverList: MutableList<Pair<String, Int>> = mutableListOf()
 
     val network: N get() = _network
 
@@ -149,7 +149,10 @@ internal abstract class AbstractBot<N : BotNetworkHandler> constructor(
 
                 bot.asQQAndroidBot().client.run {
                     if (serverList.isEmpty()) {
-                        serverList.addAll(DefaultServerList)
+                        bot.asQQAndroidBot().bdhSyncer.loadServerListFromCache()
+                        if (serverList.isEmpty()) {
+                            serverList.addAll(DefaultServerList)
+                        } else Unit
                     } else serverList.removeAt(0)
                 }
 
diff --git a/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt b/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt
index fd1ed3cee..bdf0f15e0 100644
--- a/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt
+++ b/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt
@@ -25,6 +25,7 @@ import net.mamoe.mirai.internal.contact.info.StrangerInfoImpl
 import net.mamoe.mirai.internal.contact.uin
 import net.mamoe.mirai.internal.message.*
 import net.mamoe.mirai.internal.network.*
+import net.mamoe.mirai.internal.network.handler.BdhSessionSyncer
 import net.mamoe.mirai.internal.network.handler.QQAndroidBotNetworkHandler
 import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket
 import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType
@@ -57,6 +58,8 @@ internal class QQAndroidBot constructor(
     private val account: BotAccount,
     configuration: BotConfiguration
 ) : AbstractBot<QQAndroidBotNetworkHandler>(configuration, account.id) {
+    val bdhSyncer: BdhSessionSyncer = BdhSessionSyncer(this)
+
     var client: QQAndroidClient = initClient()
         private set
 
@@ -79,7 +82,7 @@ internal class QQAndroidBot constructor(
 
     val friendListCache: FriendListCache? by lazy {
         configuration.friendListCache?.cacheFile?.let { cacheFile ->
-            val ret = configuration.workingDir.resolve(cacheFile).loadAs(FriendListCache.serializer(), JsonForCache) ?: FriendListCache()
+            val ret = configuration.cacheDirSupplier().resolve(cacheFile).loadAs(FriendListCache.serializer(), JsonForCache) ?: FriendListCache()
 
             @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
             bot.eventChannel.parentScope(this@QQAndroidBot)
@@ -130,6 +133,7 @@ internal class QQAndroidBot constructor(
     @ThisApiMustBeUsedInWithConnectionLockBlock
     @Throws(LoginFailedException::class) // only
     override suspend fun relogin(cause: Throwable?) {
+        bdhSyncer.loadFromCache()
         client.useNextServers { host, port ->
             network.closeEverythingAndRelogin(host, port, cause, 0)
         }
diff --git a/mirai-core/src/commonMain/kotlin/network/ContactListCache.kt b/mirai-core/src/commonMain/kotlin/network/ContactListCache.kt
index f5fde74c8..5d79c4920 100644
--- a/mirai-core/src/commonMain/kotlin/network/ContactListCache.kt
+++ b/mirai-core/src/commonMain/kotlin/network/ContactListCache.kt
@@ -84,7 +84,7 @@ internal class GroupMemberListCaches(
     }
 
     private val cacheDir by lazy {
-        bot.configuration.groupMemberListCache!!.cacheDir.let { bot.configuration.workingDir.resolve(it) }
+        bot.configuration.groupMemberListCache!!.cacheDir.let { bot.configuration.cacheDirSupplier().resolve(it) }
     }
 
     private fun resolveCacheFile(groupCode: Long): File {
diff --git a/mirai-core/src/commonMain/kotlin/network/QQAndroidClient.kt b/mirai-core/src/commonMain/kotlin/network/QQAndroidClient.kt
index bce6b8407..b7473f040 100644
--- a/mirai-core/src/commonMain/kotlin/network/QQAndroidClient.kt
+++ b/mirai-core/src/commonMain/kotlin/network/QQAndroidClient.kt
@@ -19,6 +19,7 @@ import kotlinx.io.core.BytePacketBuilder
 import kotlinx.io.core.String
 import kotlinx.io.core.toByteArray
 import kotlinx.io.core.writeFully
+import kotlinx.serialization.Serializable
 import net.mamoe.mirai.data.OnlineStatus
 import net.mamoe.mirai.internal.BotAccount
 import net.mamoe.mirai.internal.QQAndroidBot
@@ -281,11 +282,6 @@ internal open class QQAndroidClient(
 
     lateinit var t104: ByteArray
 
-    /**
-     * from ConfigPush.PushReq
-     */
-    @JvmField
-    val bdhSession: CompletableDeferred<BdhSession> = CompletableDeferred()
 }
 
 internal fun BytePacketBuilder.writeLoginExtraData(loginExtraData: LoginExtraData) {
@@ -298,6 +294,7 @@ internal fun BytePacketBuilder.writeLoginExtraData(loginExtraData: LoginExtraDat
     }
 }
 
+@Serializable
 internal class BdhSession(
     val sigSession: ByteArray,
     val sessionKey: ByteArray,
diff --git a/mirai-core/src/commonMain/kotlin/network/handler/BdhSessionSyncer.kt b/mirai-core/src/commonMain/kotlin/network/handler/BdhSessionSyncer.kt
new file mode 100644
index 000000000..28c22b4f3
--- /dev/null
+++ b/mirai-core/src/commonMain/kotlin/network/handler/BdhSessionSyncer.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2019-2021 Mamoe Technologies and contributors.
+ *
+ *  此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ *  Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
+ *
+ *  https://github.com/mamoe/mirai/blob/master/LICENSE
+ */
+
+package net.mamoe.mirai.internal.network.handler
+
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.builtins.ListSerializer
+import kotlinx.serialization.builtins.PairSerializer
+import kotlinx.serialization.builtins.serializer
+import net.mamoe.mirai.internal.QQAndroidBot
+import net.mamoe.mirai.internal.network.BdhSession
+import net.mamoe.mirai.internal.network.JsonForCache
+import java.io.File
+
+private val ServerListSerializer: KSerializer<List<Pair<String, Int>>> =
+    ListSerializer(PairSerializer(String.serializer(), Int.serializer()))
+
+@OptIn(ExperimentalCoroutinesApi::class)
+internal class BdhSessionSyncer(
+    private val bot: QQAndroidBot
+) {
+    var bdhSession: CompletableDeferred<BdhSession> = CompletableDeferred()
+    val hasSession: Boolean get() = bdhSession.isCompleted
+
+    fun overrideSession(
+        session: BdhSession,
+        doSave: Boolean = true
+    ) {
+        bdhSession.complete(session)
+        bdhSession = CompletableDeferred(session)
+        if (doSave) {
+            saveToCache()
+        }
+    }
+
+    private val sessionCacheFile: File
+        get() = bot.configuration.cacheDirSupplier().resolve("session.json")
+    private val serverListCacheFile: File
+        get() = bot.configuration.cacheDirSupplier().resolve("serverlist.json")
+
+    fun loadServerListFromCache() {
+        val serverListCacheFile = this.serverListCacheFile
+        if (serverListCacheFile.isFile) {
+            bot.network.logger.verbose("Loading server list from cache.")
+            kotlin.runCatching {
+                val list = JsonForCache.decodeFromString(ServerListSerializer, serverListCacheFile.readText())
+                bot.serverList.clear()
+                bot.serverList.addAll(list)
+            }.onFailure {
+                bot.network.logger.warning("Error in loading server list from cache", it)
+            }
+        } else {
+            bot.network.logger.verbose("No server list cached.")
+        }
+    }
+
+    fun loadFromCache() {
+        val sessionCacheFile = this.sessionCacheFile
+        if (sessionCacheFile.isFile) {
+            bot.network.logger.verbose("Loading BdhSession from cache file")
+            kotlin.runCatching {
+                overrideSession(
+                    JsonForCache.decodeFromString(BdhSession.serializer(), sessionCacheFile.readText()),
+                    doSave = false
+                )
+            }.onFailure {
+                bot.network.logger.warning("Error in loading BdhSession from cache", it)
+            }
+        } else {
+            bot.network.logger.verbose("No BdhSession cache")
+        }
+    }
+
+    fun saveServerListToCache() {
+        val serverListCacheFile = this.serverListCacheFile
+        serverListCacheFile.parentFile?.mkdirs()
+
+        bot.network.logger.verbose("Saving server list to cache")
+        kotlin.runCatching {
+            serverListCacheFile.writeText(
+                JsonForCache.encodeToString(
+                    ServerListSerializer,
+                    bot.serverList
+                )
+            )
+        }.onFailure {
+            bot.network.logger.warning("Error in saving ServerList to cache.", it)
+        }
+    }
+
+    fun saveToCache() {
+        val sessionCacheFile = this.sessionCacheFile
+        sessionCacheFile.parentFile?.mkdirs()
+        if (bdhSession.isCompleted) {
+            bot.network.logger.verbose("Saving bdh session to cache")
+            kotlin.runCatching {
+                sessionCacheFile.writeText(
+                    JsonForCache.encodeToString(
+                        BdhSession.serializer(),
+                        bdhSession.getCompleted()
+                    )
+                )
+            }.onFailure {
+                bot.network.logger.warning("Error in saving BdhSession to cache.", it)
+            }
+        } else {
+            sessionCacheFile.delete()
+            bot.network.logger.verbose("No BdhSession to save to cache")
+        }
+
+    }
+}
diff --git a/mirai-core/src/commonMain/kotlin/network/handler/QQAndroidBotNetworkHandler.kt b/mirai-core/src/commonMain/kotlin/network/handler/QQAndroidBotNetworkHandler.kt
index fee835efa..8d40ffd75 100644
--- a/mirai-core/src/commonMain/kotlin/network/handler/QQAndroidBotNetworkHandler.kt
+++ b/mirai-core/src/commonMain/kotlin/network/handler/QQAndroidBotNetworkHandler.kt
@@ -359,24 +359,20 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
         logger.info { "Awaiting ConfigPushSvc.PushReq." }
         when (val resp: ConfigPushSvc.PushReq.PushReqResponse? = nextEventOrNull(20_000)) {
             null -> {
-                kotlin.runCatching { bot.client.bdhSession.completeExceptionally(CancellationException("Timeout waiting for ConfigPushSvc.PushReq")) }
-                logger.warning { "Missing ConfigPushSvc.PushReq. File uploading may be affected." }
+                val hasSession = bot.bdhSyncer.hasSession
+                kotlin.runCatching { bot.bdhSyncer.bdhSession.completeExceptionally(CancellationException("Timeout waiting for ConfigPushSvc.PushReq")) }
+                if (!hasSession) {
+                    logger.warning { "Missing ConfigPushSvc.PushReq. File uploading may be affected." }
+                } else {
+                    logger.warning { "Missing ConfigPushSvc.PushReq. Using latest response. File uploading may be affected." }
+                }
             }
             is ConfigPushSvc.PushReq.PushReqResponse.Success -> {
                 logger.info { "ConfigPushSvc.PushReq: Success." }
             }
             is ConfigPushSvc.PushReq.PushReqResponse.ChangeServer -> {
-                bot.logger.info { "Server requires reconnect." }
-                bot.logger.info { "Server list: ${resp.serverList.joinToString()}." }
-
-                if (resp.serverList.isNotEmpty()) {
-                    bot.serverList.clear()
-                    resp.serverList.shuffled().forEach {
-                        bot.serverList.add(it.host to it.port)
-                    }
-                }
-
-                bot.launch { BotOfflineEvent.RequireReconnect(bot).broadcast() }
+                logger.info { "ConfigPushSvc.PushReq: Require reconnect" }
+                // handled in ConfigPushSvc
                 return@launch
             }
         }
diff --git a/mirai-core/src/commonMain/kotlin/network/highway/Highway.kt b/mirai-core/src/commonMain/kotlin/network/highway/Highway.kt
index a7499c6ca..8b972759b 100644
--- a/mirai-core/src/commonMain/kotlin/network/highway/Highway.kt
+++ b/mirai-core/src/commonMain/kotlin/network/highway/Highway.kt
@@ -59,7 +59,7 @@ internal object Highway {
         fallbackSession: (Throwable) -> BdhSession = { throw IllegalStateException("Failed to get bdh session", it) }
     ): BdhUploadResponse {
         val bdhSession = kotlin.runCatching {
-            val deferred = bot.client.bdhSession
+            val deferred = bot.bdhSyncer.bdhSession
             // no need to care about timeout. proceed by bot init
             @OptIn(ExperimentalCoroutinesApi::class)
             if (noBdhAwait) deferred.getCompleted() else deferred.await()
diff --git a/mirai-core/src/commonMain/kotlin/network/keys.kt b/mirai-core/src/commonMain/kotlin/network/keys.kt
index 415d1092a..76159b262 100644
--- a/mirai-core/src/commonMain/kotlin/network/keys.kt
+++ b/mirai-core/src/commonMain/kotlin/network/keys.kt
@@ -200,7 +200,10 @@ internal open class KeyWithCreationTime(
 
 internal suspend inline fun QQAndroidClient.useNextServers(crossinline block: suspend (host: String, port: Int) -> Unit) {
     if (bot.serverList.isEmpty()) {
-        bot.serverList.addAll(DefaultServerList)
+        bot.bdhSyncer.loadServerListFromCache()
+        if (bot.serverList.isEmpty()) {
+            bot.serverList.addAll(DefaultServerList)
+        }
     }
     retryCatchingExceptions(bot.serverList.size, except = LoginFailedException::class) l@{
         val pair = bot.serverList[0]
diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/login/ConfigPushSvc.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/login/ConfigPushSvc.kt
index 591c42bce..fe9aaeb90 100644
--- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/login/ConfigPushSvc.kt
+++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/login/ConfigPushSvc.kt
@@ -9,10 +9,14 @@
 
 package net.mamoe.mirai.internal.network.protocol.packet.login
 
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
 import kotlinx.io.core.ByteReadPacket
 import kotlinx.serialization.Serializable
 import net.mamoe.mirai.event.AbstractEvent
 import net.mamoe.mirai.event.Event
+import net.mamoe.mirai.event.broadcast
+import net.mamoe.mirai.event.events.BotOfflineEvent
 import net.mamoe.mirai.internal.QQAndroidBot
 import net.mamoe.mirai.internal.message.contextualBugReportException
 import net.mamoe.mirai.internal.network.BdhSession
@@ -30,6 +34,7 @@ import net.mamoe.mirai.internal.utils.io.serialization.loadAs
 import net.mamoe.mirai.internal.utils.io.serialization.readUniPacket
 import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId
 import net.mamoe.mirai.internal.utils.io.serialization.writeJceStruct
+import net.mamoe.mirai.utils.info
 import net.mamoe.mirai.utils.toUHexString
 import java.lang.IllegalStateException
 import net.mamoe.mirai.internal.network.protocol.data.jce.PushReq as PushReqJceStruct
@@ -110,7 +115,7 @@ internal class ConfigPushSvc {
 
                 val bigDataChannel = fileStoragePushFSSvcList.bigDataChannel
                 if (bigDataChannel?.vBigdataPbBuf == null) {
-                    client.bdhSession.completeExceptionally(IllegalStateException("BdhSession not received."))
+                    bot.bdhSyncer.bdhSession.completeExceptionally(IllegalStateException("BdhSession not received."))
                     return
                 }
 
@@ -134,16 +139,35 @@ internal class ConfigPushSvc {
                     session
                 }.fold(
                     onSuccess = {
-                        client.bdhSession.complete(it)
+                        bdhSyncer.overrideSession(it)
                     },
                     onFailure = { cause ->
                         val e = IllegalStateException("Failed to decode BdhSession", cause)
-                        client.bdhSession.completeExceptionally(e)
+                        bdhSyncer.bdhSession.completeExceptionally(e)
                         logger.error(e)
                     }
                 )
             }
 
+            fun handleRequireReconnect(resp: PushReqResponse.ChangeServer) {
+                bot.logger.info { "Server requires reconnect." }
+                bot.logger.info { "Server list: ${resp.serverList.joinToString()}." }
+
+                if (resp.serverList.isNotEmpty()) {
+                    bot.serverList.clear()
+                    resp.serverList.shuffled().forEach {
+                        bot.serverList.add(it.host to it.port)
+                    }
+                }
+                bot.bdhSyncer.saveToCache()
+                bot.bdhSyncer.saveServerListToCache()
+
+                bot.launch {
+                    delay(1000)
+                    BotOfflineEvent.RequireReconnect(bot).broadcast()
+                }
+            }
+
             when (packet) {
                 is PushReqResponse.Success -> {
                     handleSuccess(packet)
@@ -172,6 +196,10 @@ internal class ConfigPushSvc {
                         // writePacket(this.build().debugPrintThis())
                     }
                 }
+                is PushReqResponse.ChangeServer -> {
+                    handleRequireReconnect(packet)
+                    return null
+                }
                 else -> {
                     // handled in QQABot
                     return null