diff --git a/daemon/worker.c b/daemon/worker.c
index 7ed015a96db24f43a52b0da1e2c2d20187e4c1da..112ce4eb0cf889b1c9b6f281c8cc67312a7cd912 100644
--- a/daemon/worker.c
+++ b/daemon/worker.c
@@ -493,7 +493,8 @@ static void on_timeout(uv_timer_t *req)
 				inet_ntop(choice->sa_family, kr_inaddr(choice), addr_str, sizeof(addr_str));
 				QRDEBUG(qry, "wrkr", "=> server: '%s' flagged as 'bad'\n", addr_str);
 			}
-			kr_nsrep_update_rtt(&qry->ns, choice, KR_NS_TIMEOUT, worker->engine->resolver.cache_rtt);
+			kr_nsrep_update_rtt(&qry->ns, choice, KR_NS_TIMEOUT,
+					    worker->engine->resolver.cache_rtt, KR_NS_UPDATE);
 		}
 	}
 	/* Release timer handle */
diff --git a/lib/defines.h b/lib/defines.h
index 8d9796c80ca2f3645121c9935b672592d93ecaa0..a5f653e4eddaf3a8437f91677287ce55fa3f0ef3 100644
--- a/lib/defines.h
+++ b/lib/defines.h
@@ -55,6 +55,7 @@ static inline int __attribute__((__cold__)) kr_error(int x) {
 #define KR_ITER_LIMIT 50     /* Built-in iterator limit */
 #define KR_CNAME_CHAIN_LIMIT 40 /* Built-in maximum CNAME chain length */
 #define KR_TIMEOUT_LIMIT 4   /* Maximum number of retries after timeout. */
+#define KR_QUERY_NSRETRY_LIMIT 4 /* Maximum number of retries per query. */
 
 /*
  * Defines.
@@ -80,4 +81,4 @@ void __asan_unpoison_memory_region(void const volatile *addr, size_t size);
 #define kr_asan_poison(addr, size)
 #define kr_asan_unpoison(addr, size)
 #endif
-/* @endcond */
\ No newline at end of file
+/* @endcond */
diff --git a/lib/layer/iterate.c b/lib/layer/iterate.c
index d28184597ca9c9791ab6b1c15b46ae0bb9a4e9d9..99abc54832327fedde9177312aa368b20263c173 100644
--- a/lib/layer/iterate.c
+++ b/lib/layer/iterate.c
@@ -596,8 +596,18 @@ static int resolve(knot_layer_t *ctx, knot_pkt_t *pkt)
 	switch(knot_wire_get_rcode(pkt->wire)) {
 	case KNOT_RCODE_NOERROR:
 	case KNOT_RCODE_NXDOMAIN:
-	case KNOT_RCODE_REFUSED:
 		break; /* OK */
+	case KNOT_RCODE_REFUSED:
+	case KNOT_RCODE_SERVFAIL: {
+		DEBUG_MSG("<= rcode: %s\n", rcode ? rcode->name : "??");
+		query->fails += 1;
+		if (query->fails >= KR_QUERY_NSRETRY_LIMIT) {
+			query->fails = 0; /* Reset per-query counter. */
+			return resolve_error(pkt, req);
+		} else {
+			return KNOT_STATE_CONSUME;
+		}
+	}
 	case KNOT_RCODE_FORMERR:
 	case KNOT_RCODE_NOTIMPL:
 		DEBUG_MSG("<= rcode: %s\n", rcode ? rcode->name : "??");
diff --git a/lib/nsrep.c b/lib/nsrep.c
index 3fd91d7d0b4543d66f0d0bc14bfec15a94060160..e5c918443724cfdfc398ff82664fda0c874c9c1c 100644
--- a/lib/nsrep.c
+++ b/lib/nsrep.c
@@ -24,6 +24,7 @@
 #include "lib/resolve.h"
 #include "lib/defines.h"
 #include "lib/generic/pack.h"
+#include "contrib/ucw/lib.h"
 
 /** Some built-in unfairness ... */
 #define FAVOUR_IPV6 20 /* 20ms bonus for v6 */
@@ -221,7 +222,8 @@ int kr_nsrep_elect_addr(struct kr_query *qry, struct kr_context *ctx)
 
 #undef ELECT_INIT
 
-int kr_nsrep_update_rtt(struct kr_nsrep *ns, const struct sockaddr *addr, unsigned score, kr_nsrep_lru_t *cache)
+int kr_nsrep_update_rtt(struct kr_nsrep *ns, const struct sockaddr *addr,
+			unsigned score, kr_nsrep_lru_t *cache, int umode)
 {
 	if (!ns || !cache || ns->addr[0].ip.sa_family == AF_UNSPEC) {
 		return kr_error(EINVAL);
@@ -249,12 +251,16 @@ int kr_nsrep_update_rtt(struct kr_nsrep *ns, const struct sockaddr *addr, unsign
 	if (score <= KR_NS_GLUED) {
 		score = KR_NS_GLUED + 1;
 	}
-	/* Set initial value or smooth over last two measurements */
-	if (*cur != 0) {
-		*cur = (*cur + score) / 2;
-	} else {
-	/* First measurement, reset */
-		*cur = score;
+	/* First update is always set. */
+	if (*cur == 0) {
+		umode = KR_NS_RESET;
+	}
+	/* Update score, by default smooth over last two measurements. */
+	switch (umode) {
+	case KR_NS_UPDATE: *cur = (*cur + score) / 2; break;
+	case KR_NS_RESET:  *cur = score; break;
+	case KR_NS_ADD:    *cur = MIN(KR_NS_MAX_SCORE - 1, *cur + score); break;
+	default: break;
 	}
 	return kr_ok();
 }
@@ -274,4 +280,4 @@ int kr_nsrep_update_rep(struct kr_nsrep *ns, unsigned reputation, kr_nsrep_lru_t
 	}
 	*cur = reputation;
 	return kr_ok();
-}
\ No newline at end of file
+}
diff --git a/lib/nsrep.h b/lib/nsrep.h
index 848b28f0b542be9886ddd3c62be7f773a6997776..9f2e0b784174c328ca4b24690ac3fa7b74e57ab0 100644
--- a/lib/nsrep.h
+++ b/lib/nsrep.h
@@ -35,6 +35,7 @@ enum kr_ns_score {
 	KR_NS_TIMEOUT   = (95 * KR_NS_MAX_SCORE) / 100,
 	KR_NS_LONG      = (3 * KR_NS_TIMEOUT) / 4,
 	KR_NS_UNKNOWN   = KR_NS_TIMEOUT / 2,
+	KR_NS_PENALTY   = 100,
 	KR_NS_GLUED     = 10
 };
 
@@ -47,6 +48,15 @@ enum kr_ns_rep {
 	KR_NS_NOEDNS = 1 << 2  /**< NS has no EDNS support */
 };
 
+/**
+ * NS RTT update modes.
+ */
+enum kr_ns_update_mode {
+	KR_NS_UPDATE = 0, /**< Update as smooth over last two measurements */
+	KR_NS_RESET,      /**< Set to given value */
+	KR_NS_ADD         /**< Increment current value */
+};
+
 /**
  * NS reputation/QoS tracking.
  */
@@ -111,16 +121,18 @@ int kr_nsrep_elect_addr(struct kr_query *qry, struct kr_context *ctx);
 /**
  * Update NS address RTT information.
  *
- * @brief Reputation is smoothed over last N measurements.
+ * @brief In KR_NS_UPDATE mode reputation is smoothed over last N measurements.
  * 
  * @param  ns           updated NS representation
  * @param  addr         chosen address (NULL for first)
  * @param  score        new score (i.e. RTT), see enum kr_ns_score
  * @param  cache        LRU cache
+ * @param  umode        update mode (KR_NS_UPDATE or KR_NS_RESET or KR_NS_ADD)
  * @return              0 on success, error code on failure
  */
 KR_EXPORT
-int kr_nsrep_update_rtt(struct kr_nsrep *ns, const struct sockaddr *addr, unsigned score, kr_nsrep_lru_t *cache);
+int kr_nsrep_update_rtt(struct kr_nsrep *ns, const struct sockaddr *addr,
+			unsigned score, kr_nsrep_lru_t *cache, int umode);
 
 /**
  * Update NSSET reputation information.
diff --git a/lib/resolve.c b/lib/resolve.c
index 9b7416d2e5f68348fc77696d92c37430f8ece17d..3782fcd842d290c03ba863720c120434fe7be55c 100644
--- a/lib/resolve.c
+++ b/lib/resolve.c
@@ -457,17 +457,23 @@ int kr_resolve_consume(struct kr_request *request, const struct sockaddr *src, k
 			if (!(qry->flags & QUERY_SAFEMODE)) {
 				struct timeval now;
 				gettimeofday(&now, NULL);
-				kr_nsrep_update_rtt(&qry->ns, src, time_diff(&qry->timestamp, &now), ctx->cache_rtt);
+				kr_nsrep_update_rtt(&qry->ns, src, time_diff(&qry->timestamp, &now), ctx->cache_rtt, KR_NS_UPDATE);
 				WITH_DEBUG {
 					char addr_str[INET6_ADDRSTRLEN];
 					inet_ntop(src->sa_family, kr_inaddr(src), addr_str, sizeof(addr_str));
 					DEBUG_MSG(qry, "<= server: '%s' rtt: %ld ms\n", addr_str, time_diff(&qry->timestamp, &now));
 				}
 			}
-			qry->flags &= ~(QUERY_AWAIT_IPV6|QUERY_AWAIT_IPV4);
+			/* Do not complete NS address resolution on soft-fail. */
+			const int rcode = knot_wire_get_rcode(packet->wire);
+			if (rcode != KNOT_RCODE_SERVFAIL && rcode != KNOT_RCODE_REFUSED) {
+				qry->flags &= ~(QUERY_AWAIT_IPV6|QUERY_AWAIT_IPV4);
+			} else { /* Penalize SERVFAILs. */
+				kr_nsrep_update_rtt(&qry->ns, src, KR_NS_PENALTY, ctx->cache_rtt, KR_NS_ADD);
+			}
 		/* Do not penalize validation timeouts. */
 		} else if (!(qry->flags & QUERY_DNSSEC_BOGUS)) {
-			kr_nsrep_update_rtt(&qry->ns, src, KR_NS_TIMEOUT, ctx->cache_rtt);
+			kr_nsrep_update_rtt(&qry->ns, src, KR_NS_TIMEOUT, ctx->cache_rtt, KR_NS_RESET);
 			WITH_DEBUG {
 				char addr_str[INET6_ADDRSTRLEN];
 				inet_ntop(src->sa_family, kr_inaddr(src), addr_str, sizeof(addr_str));
@@ -697,13 +703,15 @@ int kr_resolve_produce(struct kr_request *request, struct sockaddr **dst, int *t
 
 ns_election:
 
-	/* If the query has already selected a NS and is waiting for IPv4/IPv6 record,
+	/* If the query has got REFUSED & SERVFAIL, retry with current src up to KR_QUERY_NSRETRY_LIMIT.
+	 * If the query has already selected a NS and is waiting for IPv4/IPv6 record,
 	 * elect best address only, otherwise elect a completely new NS.
 	 */
 	if(++ns_election_iter >= KR_ITER_LIMIT) {
 		DEBUG_MSG(qry, "=> couldn't converge NS selection, bail out\n");
 		return KNOT_STATE_FAIL;
 	}
+
 	if (qry->flags & (QUERY_AWAIT_IPV4|QUERY_AWAIT_IPV6)) {
 		kr_nsrep_elect_addr(qry, request->ctx);
 	} else if (!qry->ns.name || !(qry->flags & (QUERY_TCP|QUERY_STUB))) { /* Keep NS when requerying/stub. */
diff --git a/lib/rplan.h b/lib/rplan.h
index a804658b18720b3ffbf56541f259c210a15ee9b3..d0e8e2f3fcb2c553c1641a44ec4ddb91e17e9603 100644
--- a/lib/rplan.h
+++ b/lib/rplan.h
@@ -46,7 +46,7 @@
 	X(ALWAYS_CUT,      1 << 18) /**< Always recover zone cut (even if cached). */ \
 	X(DNSSEC_WEXPAND,  1 << 19) /**< Query response has wildcard expansion. */ \
 	X(PERMISSIVE,      1 << 20) /**< Permissive resolver mode. */ \
-	X(STRICT,          1 << 21) /**< Strict resolver mode. */
+	X(STRICT,          1 << 21) /**< Strict resolver mode. */ \
 
 /** Query flags */
 enum kr_query_flag {
@@ -70,6 +70,7 @@ struct kr_query {
 	uint16_t id;
 	uint32_t flags;
 	uint32_t secret;
+	uint16_t fails;
 	struct timeval timestamp;
 	struct kr_zonecut zone_cut;
 	struct kr_nsrep ns;
diff --git a/tests/deckard b/tests/deckard
index 5cb5935b3c139938689132c7199b14f3421bdffe..85cbf82244059113ff3149da04f99089dddc1c5b 160000
--- a/tests/deckard
+++ b/tests/deckard
@@ -1 +1 @@
-Subproject commit 5cb5935b3c139938689132c7199b14f3421bdffe
+Subproject commit 85cbf82244059113ff3149da04f99089dddc1c5b
diff --git a/tests/tests.mk b/tests/tests.mk
index c971de628f3d92d2442d65b5d4afbdf4969df828..1a82d4fcba4792df01092f4cf728f4d6e3da5787 100644
--- a/tests/tests.mk
+++ b/tests/tests.mk
@@ -12,7 +12,7 @@ TEMPLATE := template/kresd.j2
 $(deckard_DIR)/Makefile:
 	@git submodule update --init --recursive
 check-integration: $(deckard_DIR)/Makefile
-	@[ ! -d $(deckard_DIR)/contrib/libswrap/obj ] && mkdir $(deckard_DIR)/contrib/libswrap/obj
+	@mkdir -p $(deckard_DIR)/contrib/libswrap/obj
 	@$(MAKE) -s -C $(deckard_DIR) TESTS=$(TESTS) DAEMON=$(abspath daemon/kresd) TEMPLATE=$(TEMPLATE) DYLD_LIBRARY_PATH=$(DYLD_LIBRARY_PATH)
 deckard: check-integration