diff --git a/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt b/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt
index 0e177366c5a88df8c232ee76b00fdf85c73ca083..d2750638fb5354066350c1f7a414ae76ad5c64a4 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt
@@ -35,7 +35,9 @@ import de.kuschku.libquassel.session.ISession
 import de.kuschku.libquassel.session.Session
 import de.kuschku.libquassel.session.SessionManager
 import de.kuschku.libquassel.util.compatibility.LoggingHandler
+import de.kuschku.libquassel.util.compatibility.LoggingHandler.Companion.log
 import de.kuschku.libquassel.util.compatibility.LoggingHandler.LogLevel.INFO
+import de.kuschku.libquassel.util.helpers.clampOf
 import de.kuschku.libquassel.util.helpers.value
 import de.kuschku.malheur.CrashHandler
 import de.kuschku.quasseldroid.BuildConfig
@@ -354,14 +356,17 @@ class QuasselService : DaggerLifecycleService(),
 
     sessionManager.state
       .distinctUntilChanged()
-      .delay(200, TimeUnit.MILLISECONDS)
-      .throttleFirst(1, TimeUnit.SECONDS)
       .toLiveData()
       .observe(
         this, Observer {
         handlerService.backend {
-          LoggingHandler.log(INFO, "QuasselService", "Autoreconnect: State Changed")
-          sessionManager.autoReconnect()
+          if (it == ConnectionState.HANDSHAKE) {
+            backoff = BACKOFF_MIN
+          }
+
+          if (it == ConnectionState.CLOSED) {
+            scheduleReconnect()
+          }
         }
       })
 
@@ -380,6 +385,23 @@ class QuasselService : DaggerLifecycleService(),
     updateNotificationStatus(this.progress)
   }
 
+  // Set default backoff to 5ms
+  private var backoff = BACKOFF_MIN
+  private var scheduled = false
+  private fun scheduleReconnect() {
+    if (!scheduled) {
+      log(INFO, "QuasselService", "Reconnect: Scheduling backoff in ${backoff / 1_000} seconds")
+      scheduled = true
+      handlerService.backendDelayed(backoff) {
+        log(INFO, "QuasselService", "Reconnect: Scheduled backoff happened")
+        scheduled = false
+
+        backoff = clampOf(backoff * 2, BACKOFF_MIN, BACKOFF_MAX)
+        sessionManager.autoReconnect(true)
+      }
+    }
+  }
+
   override fun onDestroy() {
     sharedPreferences(Keys.Status.NAME, Context.MODE_PRIVATE) {
       unregisterOnSharedPreferenceChangeListener(this@QuasselService)
@@ -412,6 +434,11 @@ class QuasselService : DaggerLifecycleService(),
   }
 
   companion object {
+    // default backoff is 5 seconds
+    const val BACKOFF_MIN = 5_000L
+    // max is 30 minutes
+    const val BACKOFF_MAX = 1_800_000L
+
     fun launch(
       context: Context,
       disconnect: Boolean? = null,
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/helper/IntRangeHelper.kt b/app/src/main/java/de/kuschku/quasseldroid/util/helper/IntRangeHelper.kt
index 8ea855a1869c5878b0fcaf3c841c3c0cbb93bfa2..15040669dbe1d9b5ee8c544df4196c85b78c28a2 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/util/helper/IntRangeHelper.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/helper/IntRangeHelper.kt
@@ -19,11 +19,11 @@
 
 package de.kuschku.quasseldroid.util.helper
 
-import de.kuschku.libquassel.util.helpers.clamp
+import de.kuschku.libquassel.util.helpers.clampOf
 
 infix fun IntRange.without(other: IntRange): Iterable<IntRange> {
-  val otherStart = minOf(other.start, other.last + 1).clamp(this.start, this.last + 1)
-  val otherLast = maxOf(other.start, other.last + 1).clamp(this.start, this.last + 1)
+  val otherStart = clampOf(minOf(other.start, other.last + 1), this.start, this.last + 1)
+  val otherLast = clampOf(maxOf(other.start, other.last + 1), this.start, this.last + 1)
 
   val startingFragment: IntRange = this.start until otherStart
   val endingFragment: IntRange = otherLast + 1 until this.last + 1
diff --git a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferViewConfig.kt b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferViewConfig.kt
index 85f8df480ddfcd2a9eec2d535ee53b2fec2faef7..7168fff078ac30586b5753e4fa6cf15de70b6403 100644
--- a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferViewConfig.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferViewConfig.kt
@@ -25,7 +25,7 @@ import de.kuschku.libquassel.quassel.BufferInfo
 import de.kuschku.libquassel.quassel.syncables.interfaces.IBufferViewConfig
 import de.kuschku.libquassel.session.SignalProxy
 import de.kuschku.libquassel.util.flag.hasFlag
-import de.kuschku.libquassel.util.helpers.clamp
+import de.kuschku.libquassel.util.helpers.clampOf
 import io.reactivex.Observable
 import io.reactivex.subjects.BehaviorSubject
 
@@ -126,7 +126,7 @@ class BufferViewConfig constructor(
       return
 
     val currentPos = _buffers.indexOf(bufferId)
-    val targetPos = pos.clamp(0, _buffers.size - 1)
+    val targetPos = clampOf(pos, 0, _buffers.size - 1)
 
     if (currentPos > targetPos) {
       _buffers.removeAt(currentPos)
diff --git a/lib/src/main/java/de/kuschku/libquassel/session/HeartBeatThread.kt b/lib/src/main/java/de/kuschku/libquassel/session/HeartBeatThread.kt
new file mode 100644
index 0000000000000000000000000000000000000000..13436adaddd0fb57a094b8de4bd945760a3e521c
--- /dev/null
+++ b/lib/src/main/java/de/kuschku/libquassel/session/HeartBeatThread.kt
@@ -0,0 +1,34 @@
+package de.kuschku.libquassel.session
+
+import de.kuschku.libquassel.protocol.message.SignalProxyMessage
+import org.threeten.bp.Duration
+import org.threeten.bp.Instant
+
+class HeartBeatThread(private val session: Session) : Thread() {
+  private var running = true
+  private var lastHeartBeatReply: Instant = Instant.now()
+  override fun run() {
+    while (running) {
+      Thread.sleep(30_000)
+      val now = Instant.now()
+      if (Duration.between(lastHeartBeatReply, now).toMillis() > TIMEOUT) {
+        session.close()
+      } else {
+        session.dispatch(SignalProxyMessage.HeartBeat(now))
+      }
+    }
+  }
+
+  fun end() {
+    running = false
+  }
+
+  fun setLastHeartBeatReply(time: Instant) {
+    this.lastHeartBeatReply = time
+  }
+
+  companion object {
+    // Timeout, set to 2 minutes
+    const val TIMEOUT = 120_000L
+  }
+}
diff --git a/lib/src/main/java/de/kuschku/libquassel/session/ProtocolHandler.kt b/lib/src/main/java/de/kuschku/libquassel/session/ProtocolHandler.kt
index 8a0a55988296b36ac876055bbc865538619c224a..7255cf2fb810be9f5241c2410599627e734b4f7d 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/ProtocolHandler.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/session/ProtocolHandler.kt
@@ -29,7 +29,6 @@ import de.kuschku.libquassel.quassel.syncables.interfaces.ISyncableObject
 import de.kuschku.libquassel.quassel.syncables.interfaces.invokers.Invokers
 import de.kuschku.libquassel.util.compatibility.LoggingHandler.Companion.log
 import de.kuschku.libquassel.util.compatibility.LoggingHandler.LogLevel.DEBUG
-import org.threeten.bp.Instant
 import java.io.Closeable
 
 @Suppress("LeakingThis")
@@ -148,7 +147,6 @@ abstract class ProtocolHandler(
 
   override fun handle(f: SignalProxyMessage.HeartBeat): Boolean {
     dispatch(SignalProxyMessage.HeartBeatReply(f.timestamp))
-    dispatch(SignalProxyMessage.HeartBeat(Instant.now()))
     return true
   }
 
diff --git a/lib/src/main/java/de/kuschku/libquassel/session/Session.kt b/lib/src/main/java/de/kuschku/libquassel/session/Session.kt
index def9966bfc1d19782c33bcff6ce3ed617a92c44f..361c206705f9025a7206bd875659e354d6159e33 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/Session.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/session/Session.kt
@@ -90,6 +90,8 @@ class Session(
 
   override val lag = BehaviorSubject.createDefault(0L)
 
+  private val heartBeatThread = HeartBeatThread(this)
+
   init {
     coreConnection.start()
   }
@@ -211,10 +213,10 @@ class Session(
       synchronize(networkConfig, true)
 
       synchronize(backlogManager)
-
-      dispatch(SignalProxyMessage.HeartBeat(Instant.now()))
     }
 
+    heartBeatThread.start()
+
     return true
   }
 
@@ -231,11 +233,11 @@ class Session(
     }
     notificationManager?.init(this)
     coreConnection.setState(ConnectionState.CONNECTED)
-    dispatch(SignalProxyMessage.HeartBeat(Instant.now()))
   }
 
   override fun handle(f: SignalProxyMessage.HeartBeatReply): Boolean {
     val now = Instant.now()
+    heartBeatThread.setLastHeartBeatReply(f.timestamp)
     val latency = now.toEpochMilli() - f.timestamp.toEpochMilli()
     lag.onNext(latency)
     return true
@@ -257,6 +259,8 @@ class Session(
   override fun close() {
     super.close()
 
+    heartBeatThread.end()
+
     coreConnection.close()
 
     certManagers.clear()
diff --git a/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt b/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt
index a0b87ceaf92620cb6df1195664ef168e861bc02d..a56e736cd83bca37daefb6859ba5edb96811f284 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt
@@ -138,19 +138,23 @@ class SessionManager(
     )
   }
 
-  fun autoReconnect(forceReconnect: Boolean = false) {
-    if (!hasErrored) {
-      state.or(ConnectionState.DISCONNECTED).let {
-        if (it == ConnectionState.CLOSED) {
-          log(INFO, "SessionManager", "Autoreconnect triggered")
-          reconnect(forceReconnect)
-        } else {
-          log(INFO, "SessionManager", "Autoreconnect failed: state is $it")
-        }
+  /**
+   * @return if an autoreconnect has been necessary
+   */
+  fun autoReconnect(forceReconnect: Boolean = false) = if (!hasErrored) {
+    state.or(ConnectionState.DISCONNECTED).let {
+      if (it == ConnectionState.CLOSED) {
+        log(INFO, "SessionManager", "Autoreconnect triggered")
+        reconnect(forceReconnect)
+        true
+      } else {
+        log(INFO, "SessionManager", "Autoreconnect failed: state is $it")
+        false
       }
-    } else {
-      log(INFO, "SessionManager", "Autoreconnect failed: hasErrored")
     }
+  } else {
+    log(INFO, "SessionManager", "Autoreconnect failed: hasErrored")
+    false
   }
 
   fun reconnect(forceReconnect: Boolean = false) {
diff --git a/lib/src/main/java/de/kuschku/libquassel/util/helpers/MathHelper.kt b/lib/src/main/java/de/kuschku/libquassel/util/helpers/MathHelper.kt
index 6e6f989552d6ffe1c9aa1e98f64845d9fe208fdc..20e01930ad7b7a9fc8c5ced507f13cc295953e51 100644
--- a/lib/src/main/java/de/kuschku/libquassel/util/helpers/MathHelper.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/util/helpers/MathHelper.kt
@@ -19,5 +19,8 @@
 
 package de.kuschku.libquassel.util.helpers
 
-inline fun Int.clamp(lowerBound: Int, upperBound: Int): Int =
-  maxOf(lowerBound, minOf(this, upperBound))
+inline fun clampOf(value: Int, lowerBound: Int, upperBound: Int): Int =
+  maxOf(lowerBound, minOf(value, upperBound))
+
+inline fun clampOf(value: Long, lowerBound: Long, upperBound: Long): Long =
+  maxOf(lowerBound, minOf(value, upperBound))