diff --git a/man/knot.conf.5.in b/man/knot.conf.5.in
index 3138e6d1989f8a50f616b650ceac1306437593a5..c1bb55bf2d33364e0007831d04b6399a68a3e763 100644
--- a/man/knot.conf.5.in
+++ b/man/knot.conf.5.in
@@ -48,6 +48,12 @@ serves as an example of the configuration for knotc(8) and knotd(8).
   # Default: unset (auto-estimates optimal value from the number of online CPUs)
   # workers 3;
 
+  # Number of background workers
+  # This option is used to set number of threads used to execute background
+  # operations (e.g., zone loading, zone signing, XFR zone updates, ...)
+  # Default: unset (auto-estimates optimal value from the number of online CPUs)
+  # background-workers 4;
+
   # User for running server
   # May also specify user.group (e.g. knot.users)
   # user knot.users;
diff --git a/src/knot/conf/cf-lex.l b/src/knot/conf/cf-lex.l
index 0bb85d771da48f03c632cb6527a59dd058a26dd7..555c9aa1f4a5ac1305b554fffa26d0946a6623af 100644
--- a/src/knot/conf/cf-lex.l
+++ b/src/knot/conf/cf-lex.l
@@ -117,6 +117,7 @@ update-in       { lval.t = yytext; return UPDATE_IN; }
 notify-in       { lval.t = yytext; return NOTIFY_IN; }
 notify-out      { lval.t = yytext; return NOTIFY_OUT; }
 workers         { lval.t = yytext; return WORKERS; }
+background-workers { lval.t = yytext; return BACKGROUND_WORKERS; }
 user            { lval.t = yytext; return USER; }
 pidfile         { lval.t = yytext; return PIDFILE; }
 rundir          { lval.t = yytext; return RUNDIR; }
diff --git a/src/knot/conf/cf-parse.y b/src/knot/conf/cf-parse.y
index f15b705560e7dbb436bf011b625a365015b07f9e..a80e317ed3b15bdb33fb5d5feadbdcef2c60a50d 100644
--- a/src/knot/conf/cf-parse.y
+++ b/src/knot/conf/cf-parse.y
@@ -461,6 +461,7 @@ static void ident_auto(int tok, conf_t *conf, bool val)
 %token <tok> MAX_UDP_PAYLOAD
 %token <tok> TSIG_ALGO_NAME
 %token <tok> WORKERS
+%token <tok> BACKGROUND_WORKERS
 %token <tok> USER
 %token <tok> RUNDIR
 %token <tok> PIDFILE
@@ -590,6 +591,9 @@ system:
  | system WORKERS NUM ';' {
      SET_NUM(new_config->workers, $3.i, 1, 255, "workers");
  }
+ | system BACKGROUND_WORKERS NUM ';' {
+     SET_NUM(new_config->bg_workers, $3.i, 1, 255, "background-workers");
+ }
  | system USER TEXT ';' {
      new_config->uid = new_config->gid = -1; // Invalidate
      char* dpos = strchr($3.t, '.'); // Find uid.gid format
diff --git a/src/knot/conf/conf.h b/src/knot/conf/conf.h
index 217fa1a6a195acae60cca5d15474c0c99288d6cf..d7d3ed657b46cb0f4bcf1a90b9514f38b86622e9 100644
--- a/src/knot/conf/conf.h
+++ b/src/knot/conf/conf.h
@@ -215,6 +215,7 @@ typedef struct conf_t {
 	size_t nsid_len;/*!< Server's NSID length. */
 	size_t max_udp_payload; /*!< Maximal UDP payload size. */
 	int   workers;  /*!< Number of workers per interface. */
+	int   bg_workers; /*!< Number of background workers. */
 	int   uid;      /*!< Specified user id. */
 	int   gid;      /*!< Specified group id. */
 	int   max_conn_idle; /*!< TCP idle timeout. */
diff --git a/src/knot/main.c b/src/knot/main.c
index b18241b1d28436349c624c2f8480da72d4bc3035..ce9823805b09d4f3808c3f5794183cfd7c023d0c 100644
--- a/src/knot/main.c
+++ b/src/knot/main.c
@@ -280,7 +280,7 @@ int main(int argc, char **argv)
 
 	/* Initialize server. */
 	server_t server;
-	res = server_init(&server);
+	res = server_init(&server, config->bg_workers);
 	if (res != KNOT_EOK) {
 		log_server_fatal("Could not initialize server: %s\n",
 		                 knot_strerror(res));
diff --git a/src/knot/nameserver/axfr.c b/src/knot/nameserver/axfr.c
index 30f6bebf16209f18bc6e4c5ddd10442b8312211e..6bcc36ebdf294e56304ea26f64680ecb09b8783e 100644
--- a/src/knot/nameserver/axfr.c
+++ b/src/knot/nameserver/axfr.c
@@ -317,7 +317,7 @@ static int axfr_answer_finalize(struct answer_data *data)
 static int process_axfr_packet(knot_pkt_t *pkt, struct xfr_proc *proc)
 {
 	if (pkt == NULL) {
-		return KNOT_EINVAL;
+		return NS_PROC_FAIL;
 	}
 
 	++proc->npkts;
@@ -345,7 +345,6 @@ static int process_axfr_packet(knot_pkt_t *pkt, struct xfr_proc *proc)
 int axfr_process_answer(knot_pkt_t *pkt, struct answer_data *data)
 {
 	/* Initialize processing with first packet. */
-	int ret = KNOT_EOK;
 	if (data->ext == NULL) {
 		NS_NEED_TSIG_SIGNED(&data->param->tsig_ctx, 0);
 		if (!zone_transfer_needed(data->param->zone, pkt)) {
@@ -354,7 +353,7 @@ int axfr_process_answer(knot_pkt_t *pkt, struct answer_data *data)
 		}
 		AXFRIN_LOG(LOG_INFO, "Starting.");
 
-		ret = axfr_answer_init(data);
+		int ret = axfr_answer_init(data);
 		if (ret != KNOT_EOK) {
 			AXFRIN_LOG(LOG_ERR, "Failed.\n");
 			return NS_PROC_FAIL;
@@ -364,7 +363,7 @@ int axfr_process_answer(knot_pkt_t *pkt, struct answer_data *data)
 	}
 
 	/* Process answer packet. */
-	ret = process_axfr_packet(pkt, (struct xfr_proc *)data->ext);
+	int ret = process_axfr_packet(pkt, (struct xfr_proc *)data->ext);
 	if (ret == NS_PROC_DONE) {
 		NS_NEED_TSIG_SIGNED(&data->param->tsig_ctx, 0);
 		/* This was the last packet, finalize zone and publish it. */
diff --git a/src/knot/nameserver/ixfr.c b/src/knot/nameserver/ixfr.c
index 75bdaab7524f317a51e952e291b04aa8d32c3a1a..738da1f87ed9835c81af992c60beb3a6e811a8e5 100644
--- a/src/knot/nameserver/ixfr.c
+++ b/src/knot/nameserver/ixfr.c
@@ -656,7 +656,6 @@ int ixfr_process_answer(knot_pkt_t *pkt, struct answer_data *adata)
 
 	if (ret == NS_PROC_FAIL) {
 		ixfrin_cleanup(adata);
-		IXFRIN_LOG(LOG_ERR, "Failed.");
 	}
 
 	return ret;
diff --git a/src/knot/nameserver/process_query.c b/src/knot/nameserver/process_query.c
index 5e5c313101dfe185dd9feb4f480b769a0f3ed453..3263526902be404654c020641f8e9f5621c3cd30 100644
--- a/src/knot/nameserver/process_query.c
+++ b/src/knot/nameserver/process_query.c
@@ -302,6 +302,11 @@ int process_query_verify(struct query_data *qdata)
 
 int process_query_sign_response(knot_pkt_t *pkt, struct query_data *qdata)
 {
+	if (pkt->size == 0) {
+		// Nothing to sign.
+		return KNOT_EOK;
+	}
+
 	int ret = KNOT_EOK;
 	knot_pkt_t *query = qdata->query;
 	knot_sign_context_t *ctx = &qdata->sign;
diff --git a/src/knot/nameserver/update.c b/src/knot/nameserver/update.c
index 1d9134db6d51029a20bb8df0240d70cc7233f55a..2ccc501a9d2c222d8909a7989241a71324bbfca1 100644
--- a/src/knot/nameserver/update.c
+++ b/src/knot/nameserver/update.c
@@ -14,8 +14,6 @@
 #define UPDATE_LOG(severity, msg...) \
 	QUERY_LOG(severity, qdata, "UPDATE", msg)
 
-#warning merge file with ddns.c
-
 static int update_forward(knot_pkt_t *pkt, struct query_data *qdata)
 {
 	/*! \todo This will be reimplemented when RESPONSE and REQUEST processors
diff --git a/src/knot/server/journal.c b/src/knot/server/journal.c
index eb629f6bfccec9cfd5dcf39ea35aa521bbb18a8b..567acb2fcd5969615469b64b0dd9f574810d67da 100644
--- a/src/knot/server/journal.c
+++ b/src/knot/server/journal.c
@@ -1050,7 +1050,7 @@ static int zones_serialize_and_store_chgset(const changeset_t *chs,
 	int ret = zones_rrset_write_to_mem(chs->soa_from, &entry, &max_size);
 	if (ret != KNOT_EOK) {
 		dbg_zones("%s:%d ret = %s\n", __func__, __LINE__, knot_strerror(ret));
-		return KNOT_ERROR;  /*! \todo Other code? */
+		return ret;
 	}
 
 	/* Serialize RRSets from the 'remove' section. */
@@ -1060,7 +1060,7 @@ static int zones_serialize_and_store_chgset(const changeset_t *chs,
 		ret = zones_rrset_write_to_mem(rrset, &entry, &max_size);
 		if (ret != KNOT_EOK) {
 			dbg_zones("%s:%d ret = %s\n", __func__, __LINE__, knot_strerror(ret));
-			return KNOT_ERROR;  /*! \todo Other code? */
+			return ret;
 		}
 	}
 
@@ -1068,7 +1068,7 @@ static int zones_serialize_and_store_chgset(const changeset_t *chs,
 	ret = zones_rrset_write_to_mem(chs->soa_to, &entry, &max_size);
 	if (ret != KNOT_EOK) {
 		dbg_zones("%s:%d ret = %s\n", __func__, __LINE__, knot_strerror(ret));
-		return KNOT_ERROR;  /*! \todo Other code? */
+		return ret;
 	}
 
 	/* Serialize RRSets from the 'add' section. */
@@ -1077,7 +1077,7 @@ static int zones_serialize_and_store_chgset(const changeset_t *chs,
 		ret = zones_rrset_write_to_mem(rrset, &entry, &max_size);
 		if (ret != KNOT_EOK) {
 			dbg_zones("%s:%d ret = %s\n", __func__, __LINE__, knot_strerror(ret));
-			return KNOT_ERROR;  /*! \todo Other code? */
+			return ret;
 		}
 
 	}
diff --git a/src/knot/server/serialization.c b/src/knot/server/serialization.c
index 21c0d9d68c9fb7b951704abdaf4c02af6d4cde4e..8d708f87aac5b17b23dd420823abb133f85030a2 100644
--- a/src/knot/server/serialization.c
+++ b/src/knot/server/serialization.c
@@ -92,8 +92,6 @@ int changeset_binary_size(const changeset_t *chgset, size_t *size)
 	}
 
 	*size = soa_from_size + soa_to_size + remove_size + add_size;
-	/* + Changeset flags. */
-	*size += sizeof(uint32_t);
 
 	return KNOT_EOK;
 }
diff --git a/src/knot/server/server.c b/src/knot/server/server.c
index 3f32db93caa6275bbb7dc3264469561bf4413678..3ea58658cf3d98d5eb009293e465866a42c876cd 100644
--- a/src/knot/server/server.c
+++ b/src/knot/server/server.c
@@ -116,12 +116,16 @@ static int server_init_iface(iface_t *new_if, conf_iface_t *cfg_if)
 	char addr_str[SOCKADDR_STRLEN] = {0};
 	sockaddr_tostr(&cfg_if->addr, addr_str, sizeof(addr_str));
 
+#if defined(SO_REUSEPORT)
+	/* Each thread binds own socket. */
+	int sock = -1;
+#else
 	/* Create bound UDP socket. */
 	int sock = net_bound_socket(SOCK_DGRAM, &cfg_if->addr);
 	if (sock < 0) {
 		return sock;
 	}
-
+#endif
 	new_if->fd[IO_UDP] = sock;
 
 	/* Create bound TCP socket. */
@@ -257,7 +261,7 @@ static int reconfigure_sockets(const struct conf_t *conf, server_t *s)
 	return bound;
 }
 
-int server_init(server_t *server)
+int server_init(server_t *server, int bg_workers)
 {
 	/* Clear the structure. */
 	dbg_server("%s(%p)\n", __func__, server);
@@ -278,8 +282,12 @@ int server_init(server_t *server)
 	}
 
 	/* Create zone events threads. */
-#warning TODO: config option
-	server->workers = worker_pool_create(4); //! \todo config option
+	if (bg_workers < 1) {
+		bg_workers = dt_optimal_size();
+	}
+	assert(bg_workers > 0);
+
+	server->workers = worker_pool_create(bg_workers);
 	if (server->workers == NULL) {
 		dt_delete(&server->iosched);
 		evsched_deinit(&server->sched);
diff --git a/src/knot/server/server.h b/src/knot/server/server.h
index f6aedb21b1899e9ba199d827d8a7eb007bf7c2da..b371bcc00892e4b439af3e0beac2b6e8a503a37f 100644
--- a/src/knot/server/server.h
+++ b/src/knot/server/server.h
@@ -133,7 +133,7 @@ typedef struct server_t {
  * \retval KNOT_EOK on success.
  * \retval KNOT_EINVAL on invalid parameters.
  */
-int server_init(server_t *server);
+int server_init(server_t *server, int bg_workers);
 
 /*!
  * \brief Properly destroys the server structure.
diff --git a/src/knot/server/xfr-handler.c b/src/knot/server/xfr-handler.c
index 0d5fa1eee2e783ed64823de479d8342458226bf5..6d4bddc7cd87850f196a379a3bafe3019d04e827 100644
--- a/src/knot/server/xfr-handler.c
+++ b/src/knot/server/xfr-handler.c
@@ -669,7 +669,7 @@ static int xfr_task_resp_process(server_t *server,
 		}
 
 		/* Check SOA SERIAL. */
-		int ret = xfrin_transfer_needed(zone->contents, packet);
+//		int ret = xfrin_transfer_needed(zone->contents, packet);
 		dbg_zones_verb("xfrin_transfer_needed() returned %s\n",
 		               knot_strerror(ret));
 		if (ret < 0) {
diff --git a/src/knot/updates/apply.c b/src/knot/updates/apply.c
index 77d3d1c138cbcd508d136f3c9898360c0609b1ea..510d7e664c0e7f8206a6e06a37603baf9bed7ea5 100644
--- a/src/knot/updates/apply.c
+++ b/src/knot/updates/apply.c
@@ -31,139 +31,6 @@
 #include "libknot/util/utils.h"
 #include "libknot/rdata/soa.h"
 
-/* ------------------------ legacy, to be removed --------------------------- */
-
-#define KNOT_NS_TSIG_FREQ 100
-
-static int knot_ns_tsig_required(int packet_nr)
-{
-	/*! \bug This can overflow to negative numbers. Proper solution is to
-	 *       count exactly at one place for each incoming/outgoing packet
-	 *       with packet_nr = (packet_nr + 1) % FREQ and require TSIG on 0.
-	 */
-	dbg_ns_verb("ns_tsig_required(%d): %d\n", packet_nr,
-	            (packet_nr % KNOT_NS_TSIG_FREQ == 0));
-	return (packet_nr % KNOT_NS_TSIG_FREQ == 0);
-}
-
-int xfrin_transfer_needed(const zone_contents_t *zone,
-                          knot_pkt_t *soa_response)
-{
-#warning Reimplement or remove
-	/*
-	 * Retrieve the local Serial
-	 */
-	const knot_rdataset_t *soa_rrs =
-		node_rdataset(zone->apex, KNOT_RRTYPE_SOA);
-	if (soa_rrs == NULL) {
-		char *name = knot_dname_to_str(zone->apex->owner);
-		dbg_xfrin("SOA RRSet missing in the zone %s!\n", name);
-		free(name);
-		return KNOT_ERROR;
-	}
-
-	uint32_t local_serial = knot_soa_serial(soa_rrs);
-	/*
-	 * Retrieve the remote Serial
-	 */
-	// the SOA should be the first (and only) RRSet in the response
-	const knot_pktsection_t *answer = knot_pkt_section(soa_response, KNOT_ANSWER);
-	if (answer->count < 1) {
-		return KNOT_EMALF;
-	}
-	knot_rrset_t soa_rr = answer->rr[0];
-	if (soa_rr.type != KNOT_RRTYPE_SOA) {
-		return KNOT_EMALF;
-	}
-
-	uint32_t remote_serial = knot_soa_serial(&soa_rr.rrs);
-	return (knot_serial_compare(local_serial, remote_serial) < 0);
-}
-
-static int xfrin_check_tsig(knot_pkt_t *packet, knot_ns_xfr_t *xfr,
-                            int tsig_req)
-{
-#warning reimplement, but not inside this file
-	assert(packet != NULL);
-	assert(xfr != NULL);
-
-	dbg_xfrin_verb("xfrin_check_tsig(): packet nr: %d, required: %d\n",
-		       xfr->packet_nr, tsig_req);
-
-	/*
-	 * If we are expecting it (i.e. xfr->prev_digest_size > 0)
-	 *   a) it should be there (first, last or each 100th packet) and it
-	 *      is not
-	 *        Then we should discard the changes and close the connection.
-	 *   b) it should be there and it is or it may not be there (other
-	 *      packets) and it is
-	 *        We validate the TSIG and reset packet number counting and
-	 *        data aggregation.
-	 *
-	 * If we are not expecting it (i.e. xfr->prev_digest_size <= 0) and
-	 * it is there => it should probably be considered an error
-	 */
-
-	int ret = KNOT_EOK;
-	if (xfr->tsig_key) {
-		// just append the wireformat to the TSIG data
-		uint8_t *wire_buf = xfr->tsig_data + xfr->tsig_data_size;
-		memcpy(wire_buf, packet->wire, packet->size);
-		xfr->tsig_data_size += packet->size;
-	}
-
-	if (xfr->tsig_key) {
-		if (tsig_req && packet->tsig_rr == NULL) {
-			// TSIG missing!!
-			return KNOT_ENOTSIG;
-		} else if (packet->tsig_rr != NULL) {
-			// TSIG there, either required or not, process
-			if (xfr->packet_nr == 0) {
-				ret = knot_tsig_client_check(packet->tsig_rr,
-					xfr->tsig_data, xfr->tsig_data_size,
-					xfr->digest, xfr->digest_size,
-					xfr->tsig_key,
-					xfr->tsig_prev_time_signed);
-			} else {
-				ret = knot_tsig_client_check_next(packet->tsig_rr,
-					xfr->tsig_data, xfr->tsig_data_size,
-					xfr->digest, xfr->digest_size,
-					xfr->tsig_key,
-					xfr->tsig_prev_time_signed);
-			}
-
-			if (ret != KNOT_EOK) {
-				/* No need to check TSIG error
-				 * here, propagate and check elsewhere.*/
-				return ret;
-			}
-
-			// and reset the data storage
-			//xfr->packet_nr = 1;
-			xfr->tsig_data_size = 0;
-
-			// Extract the digest from the TSIG RDATA and store it.
-			if (xfr->digest_max_size < tsig_rdata_mac_length(packet->tsig_rr)) {
-				return KNOT_ESPACE;
-			}
-			memcpy(xfr->digest, tsig_rdata_mac(packet->tsig_rr),
-			       tsig_rdata_mac_length(packet->tsig_rr));
-			xfr->digest_size = tsig_rdata_mac_length(packet->tsig_rr);
-
-			// Extract the time signed from the TSIG and store it
-			// We may rewrite the tsig_req_time_signed field
-			xfr->tsig_prev_time_signed =
-					tsig_rdata_time_signed(packet->tsig_rr);
-
-		}
-	} else if (packet->tsig_rr != NULL) {
-		// TSIG where it should not be
-		return KNOT_EMALF;
-	}
-
-	return KNOT_EOK;
-}
-
 /* --------------------------- Update cleanup ------------------------------- */
 
 /*!
@@ -185,7 +52,6 @@ static void rrs_list_clear(list_t *l, mm_ctx_t *mm)
 /*! \brief Frees additional data from single node */
 static int free_additional(zone_node_t **node, void *data)
 {
-#warning - TODO: still needed?
 	UNUSED(data);
 	if ((*node)->flags & NODE_FLAGS_NONAUTH) {
 		// non-auth nodes have no additionals.
diff --git a/src/knot/updates/apply.h b/src/knot/updates/apply.h
index 3e9ec88f8ef88604b342c89604236ed25a5f45da..16fa27b1cb319e5a20e892691680d80b232c91c7 100644
--- a/src/knot/updates/apply.h
+++ b/src/knot/updates/apply.h
@@ -34,20 +34,6 @@
 #include "knot/server/xfr-handler.h"
 #include "knot/updates/changesets.h"
 
-/*!
- * \brief Checks if a zone transfer is required by comparing the zone's SOA with
- *        the one received from master server.
- *
- * \param zone Zone to check.
- * \param soa_response Response to SOA query received from master server.
- *
- * \retval < 0 if an error occured.
- * \retval 1 if the transfer is needed.
- * \retval 0 if the transfer is not needed.
- */
-int xfrin_transfer_needed(const zone_contents_t *zone,
-                          knot_pkt_t *soa_response);
-
 /*!
  * \brief Applies changesets *with* zone shallow copy.
  *
diff --git a/src/knot/updates/changesets.c b/src/knot/updates/changesets.c
index bd6669398a3f437f2d24acb205b813fc4b71532e..6fee48a5625aaa72f8f0cb281c287b9a1839e2bb 100644
--- a/src/knot/updates/changesets.c
+++ b/src/knot/updates/changesets.c
@@ -238,7 +238,7 @@ int changeset_merge(changeset_t *ch1, changeset_t *ch2)
 	return KNOT_EOK;
 }
 
-static void knot_free_changeset(changeset_t *changeset)
+static void knot_free_changeset(changeset_t *changeset, mm_ctx_t *rr_mm)
 {
 	if (changeset == NULL) {
 		return;
@@ -247,14 +247,14 @@ static void knot_free_changeset(changeset_t *changeset)
 	// Delete RRSets in lists, in case there are any left
 	knot_rr_ln_t *rr_node;
 	WALK_LIST(rr_node, changeset->add) {
-		knot_rrset_free(&rr_node->rr, NULL);
+		knot_rrset_free(&rr_node->rr, rr_mm);
 	}
 	WALK_LIST(rr_node, changeset->remove) {
-		knot_rrset_free(&rr_node->rr, NULL);
+		knot_rrset_free(&rr_node->rr, rr_mm);
 	}
 
-	knot_rrset_free(&changeset->soa_from, NULL);
-	knot_rrset_free(&changeset->soa_to, NULL);
+	knot_rrset_free(&changeset->soa_from, rr_mm);
+	knot_rrset_free(&changeset->soa_to, rr_mm);
 
 	// Delete binary data
 	free(changeset->data);
@@ -265,7 +265,7 @@ static void knot_changesets_deinit(changesets_t *changesets, mm_ctx_t *rr_mm)
 	if (!EMPTY_LIST(changesets->sets)) {
 		changeset_t *chg = NULL;
 		WALK_LIST(chg, changesets->sets) {
-			knot_free_changeset(chg);
+			knot_free_changeset(chg, rr_mm);
 		}
 	}
 
diff --git a/src/knot/updates/ddns.c b/src/knot/updates/ddns.c
index c2a8843732895ad453b0bbb8431c4e7cc56f8d2e..a94b2b08b5c95ab6cc809233998ba7aeaca8642a 100644
--- a/src/knot/updates/ddns.c
+++ b/src/knot/updates/ddns.c
@@ -31,8 +31,6 @@
 #include "common/descriptor.h"
 #include "common/lists.h"
 
-#warning merge with update.c
-
 /* ----------------------------- prereq check ------------------------------- */
 
 /*!< \brief Clears prereq RRSet list. */
@@ -1018,8 +1016,9 @@ int ddns_process_update(const zone_t *zone, const knot_pkt_t *query,
 			return KNOT_ENOMEM;
 		}
 
-		int new_serial = zone_contents_next_serial(zone->contents,
-		                                           zone->conf->serial_policy);
+		uint32_t new_serial =
+			zone_contents_next_serial(zone->contents,
+		                                  zone->conf->serial_policy);
 		knot_soa_serial_set(&soa_cpy->rrs, new_serial);
 		changeset_add_soa(changeset, soa_cpy, CHANGESET_ADD);
 	}
diff --git a/src/knot/worker/pool.c b/src/knot/worker/pool.c
index 68971d97c309d469dd8fe3cd74f5c0d2c6646527..d6d0cf046c1df9510943b4682ffe9bd7c9b6e2e9 100644
--- a/src/knot/worker/pool.c
+++ b/src/knot/worker/pool.c
@@ -25,6 +25,7 @@
 #include "knot/server/dthreads.h"
 #include "knot/worker/pool.h"
 #include "knot/worker/queue.h"
+#include "libknot/dnssec/crypto.h"
 
 /*!
  * \brief Worker pool state.
@@ -86,6 +87,13 @@ static int worker_main(dthread_t *thread)
 	return KNOT_EOK;
 }
 
+static int worker_cleanup(dthread_t *thread)
+{
+	knot_crypto_cleanup_thread();
+
+	return KNOT_EOK;
+}
+
 /* -- public API ------------------------------------------------------------ */
 
 worker_pool_t *worker_pool_create(unsigned threads)
@@ -96,7 +104,7 @@ worker_pool_t *worker_pool_create(unsigned threads)
 	}
 
 	memset(pool, 0, sizeof(worker_pool_t));
-	pool->threads = dt_create(threads, worker_main, NULL, pool);
+	pool->threads = dt_create(threads, worker_main, worker_cleanup, pool);
 	if (pool->threads == NULL) {
 		free(pool);
 		return NULL;
diff --git a/src/knot/zone/events.c b/src/knot/zone/events.c
index a504268a3fb2b063043c6eaccf3eb0382e11df21..91b63f0079af0704c78a11a554c07c1e7b18d208 100644
--- a/src/knot/zone/events.c
+++ b/src/knot/zone/events.c
@@ -368,8 +368,8 @@ static int event_update(zone_t *zone)
 	qdata.query = update->query;
 	qdata.zone  = zone;
 
-	/* Process the update query. */
-	int ret = update_process_query(resp, &qdata);
+	/* Process the update query. If processing fails, resp will be set. */
+	update_process_query(resp, &qdata);
 
 	/* Send response. */
 	if (net_is_connected(update->fd)) {
diff --git a/src/knot/zone/zone.c b/src/knot/zone/zone.c
index 53422275165096a5c2706f6f491a3714f65564bd..cb0eead3d5e4869badf810560f587b555215f182 100644
--- a/src/knot/zone/zone.c
+++ b/src/knot/zone/zone.c
@@ -67,7 +67,7 @@ static int set_acl(acl_t **acl, list_t* acl_list)
 	return KNOT_EOK;
 }
 
-static void free_ddns_q(zone_t *z)
+static void free_ddns_queue(zone_t *z)
 {
 	struct request_data *n = NULL;
 	node_t *nxt = NULL;
@@ -133,7 +133,7 @@ void zone_free(zone_t **zone_ptr)
 	acl_delete(&zone->xfr_out);
 	acl_delete(&zone->notify_in);
 	acl_delete(&zone->update_in);
-	free_ddns_q(zone);
+	free_ddns_queue(zone);
 	pthread_mutex_destroy(&zone->ddns_lock);
 
 	/* Free assigned config. */
diff --git a/src/knot/zone/zonedb-load.c b/src/knot/zone/zonedb-load.c
index 77ce66101c266a8ba641d3e20ff6daad49ddeaed..64b61d839130a4e820a676c6ccfc798337519a5c 100644
--- a/src/knot/zone/zonedb-load.c
+++ b/src/knot/zone/zonedb-load.c
@@ -137,7 +137,11 @@ static zone_t *create_zone(conf_zone_t *conf, server_t *server, zone_t *old_zone
 		zone_events_schedule(zone, ZONE_EVENT_REFRESH, ZONE_EVENT_NOW);
 		break;
 	case ZONE_STATUS_NOT_FOUND:
+		break;
 	case ZONE_STATUS_FOUND_CURRENT:
+		if (zone->conf->dnssec_enable) {
+			zone_events_schedule(zone, ZONE_EVENT_DNSSEC, ZONE_EVENT_NOW);
+		}
 		break;
 	default:
 		assert(0);
@@ -249,8 +253,8 @@ int zonedb_reload(const conf_t *conf, struct server_t *server)
 	}
 
 	/* Freeze zone timers. */
+#warning "Workers have to be suspended and unsuspended outside this function."
 	if (server->zone_db) {
-		// TODO: ne, tohle nechceme
 		//knot_zonedb_foreach(server->zone_db, zone_events_freeze);
 	}
 
diff --git a/src/knot/zone/zonefile.c b/src/knot/zone/zonefile.c
index f6413ffb7556d2af01ad9179c1c56484bbb08008..be8182fd7cc1f668e5c1a9ba1c3d38e5d59bae7b 100644
--- a/src/knot/zone/zonefile.c
+++ b/src/knot/zone/zonefile.c
@@ -199,7 +199,7 @@ static zone_contents_t *create_zone_from_name(const char *origin)
 int zonefile_open(zloader_t *loader, const char *source, const char *origin,
 		  bool semantic_checks)
 {
-	if (!loader || !conf) {
+	if (!loader) {
 		return KNOT_EINVAL;
 	}
 
diff --git a/src/libknot/rrset.c b/src/libknot/rrset.c
index d1766f4dad302780ab021133184aad11a702d0d4..04d9d8d8fe5a2cc918fbed978a11c2b7a7b205c0 100644
--- a/src/libknot/rrset.c
+++ b/src/libknot/rrset.c
@@ -571,18 +571,14 @@ bool knot_rrset_equal(const knot_rrset_t *r1,
 		return r1 == r2;
 	}
 
-	if (knot_rrset_empty(r1)) {
-		return knot_rrset_empty(r2);
-	} else if (knot_rrset_empty(r2)) {
-		return knot_rrset_empty(r1);
-	}
-
 	if (r1->type != r2->type) {
 		return false;
 	}
 
-	if (!knot_dname_is_equal(r1->owner, r2->owner)) {
-		return false;
+	if (r1->owner && r2->owner) {
+		if (!knot_dname_is_equal(r1->owner, r2->owner)) {
+			return false;
+		}
 	}
 
 	if (cmp == KNOT_RRSET_COMPARE_WHOLE) {
diff --git a/tests-extra/tools/dnstest/server.py b/tests-extra/tools/dnstest/server.py
index 951abb62707f99b15b90a215087e29b1083d4383..b52d6d14dad77dc5a6f71f692060375a800bb980 100644
--- a/tests-extra/tools/dnstest/server.py
+++ b/tests-extra/tools/dnstest/server.py
@@ -879,7 +879,7 @@ class Knot(Server):
 
         s.begin("zones")
         s.item_str("storage", self.dir)
-        s.item("zonefile-sync", "0")
+        s.item("zonefile-sync", "1d")
         s.item("notify-timeout", "5")
         s.item("notify-retries", "5")
         s.item("semantic-checks", "on")