diff --git a/src/libknot/nameserver/internet.c b/src/libknot/nameserver/internet.c
index a4739a4b40815bf389f11197cea976443ef27772..e604d1c38677e2d4cd7ebeb185a38f5fb84fbee8 100644
--- a/src/libknot/nameserver/internet.c
+++ b/src/libknot/nameserver/internet.c
@@ -15,6 +15,13 @@
  */
 #include "knot/server/zones.h"
 
+/* Visited wildcard node list. */
+struct wildcard_hit {
+	node_t n;
+	const knot_node_t *node;
+	const knot_dname_t *sname;
+};
+
 /*! \brief Query processing states. */
 enum {
 	BEGIN,   /* Begin name resolution. */
@@ -26,6 +33,53 @@ enum {
 	ERROR    /* Resolution failed. */
 };
 
+/*! \brief Check if given node was already visited. */
+static int wildcard_has_visited(struct query_data *qdata, const knot_node_t *node)
+{
+	struct wildcard_hit *item = NULL;
+	WALK_LIST(item, qdata->wildcards) {
+		if (item->node == node) {
+			return true;
+		}
+	}
+	return false;
+}
+
+/*! \brief Mark given node as visited. */
+static int wildcard_visit(struct query_data *qdata, const knot_node_t *node, const knot_dname_t *sname)
+{
+	assert(qdata);
+	assert(node);
+
+	mm_ctx_t *mm = qdata->mm;
+	struct wildcard_hit *item = mm->alloc(mm->ctx, sizeof(struct wildcard_hit));
+	item->node = node;
+	item->sname = sname;
+	add_tail(&qdata->wildcards, (node_t *)item);
+	return KNOT_EOK;
+}
+
+/*! \brief Put all covering records for wildcard list. */
+static int wildcard_list_cover(knot_pkt_t *pkt, struct query_data *qdata)
+{
+	int ret = KNOT_EOK;
+	struct wildcard_hit *item = NULL;
+
+	WALK_LIST(item, qdata->wildcards) {
+		ret = ns_put_nsec_nsec3_wildcard_answer(
+					item->node,
+					knot_node_parent(item->node),
+					NULL, qdata->zone->contents,
+					item->sname,
+					pkt);
+		if (ret != KNOT_EOK) {
+			break;
+		}
+	}
+
+	return ret;
+}
+
 static int follow_cname(knot_pkt_t *pkt, const knot_dname_t **name, struct query_data *qdata)
 {
 	dbg_ns("%s(%p, %p, %p)\n", __func__, name, pkt, qdata);
@@ -43,14 +97,14 @@ static int follow_cname(knot_pkt_t *pkt, const knot_dname_t **name, struct query
 
 		/* Check if is not in wildcard nodes (loop). */
 		dbg_ns("%s: CNAME node %p is wildcard\n", __func__, cname_node);
-		if (ptrlist_contains(&qdata->wildcards, cname_node)) {
+		if (wildcard_has_visited(qdata, cname_node)) {
 			dbg_ns("%s: node %p already visited => CNAME loop\n",
 			       __func__, cname_node);
 			return HIT;
 		}
 
 		/* Put to wildcard node list. */
-		if (ptrlist_add(&qdata->wildcards, cname_node, qdata->mm) == NULL) {
+		if (wildcard_visit(qdata, cname_node, *name) != KNOT_EOK) {
 			return ERROR;
 		}
 
@@ -169,8 +223,14 @@ static int name_not_found(knot_pkt_t *pkt, const knot_dname_t **name,
 	if (wildcard_node) {
 		dbg_ns("%s: name %p covered by wildcard\n", __func__, *name);
 		qdata->node = wildcard_node;
-		qdata->encloser = wildcard_node;
+		/* keep encloser */
 		qdata->previous = NULL;
+
+		/* Put to wildcard node list. */
+		if (wildcard_visit(qdata, qdata->encloser, *name) != KNOT_EOK) {
+			return ERROR;
+		}
+
 		return name_found(pkt, name, qdata);
 	}
 
@@ -187,6 +247,13 @@ static int name_not_found(knot_pkt_t *pkt, const knot_dname_t **name,
 		return FOLLOW;
 	}
 
+	/* Name is below delegation. */
+	if (knot_node_is_deleg_point(qdata->encloser)) {
+		dbg_ns("%s: name below delegation point %p\n", __func__, *name);
+		qdata->node = qdata->encloser;
+		return DELEG;
+	}
+
 	dbg_ns("%s: name not found in zone %p\n", __func__, *name);
 	return MISS;
 }
@@ -245,18 +312,43 @@ static int solve_authority(int state, const knot_dname_t **qname,
 
 	switch (state) {
 	case HIT:    /* Positive response, add (optional) AUTHORITY NS. */
+		dbg_ns("%s: answer is POSITIVE\n", __func__);
 		ret = ns_put_authority_ns(zone_contents, pkt);
-		dbg_ns("%s: putting authority NS = %d\n", __func__, ret);
 		if (ret == KNOT_ESPACE) { /* Optional. */
 			ret = KNOT_EOK;
 		}
+		/* Put NSEC/NSEC3 Wildcard proof if answered from wildcard. */
+		if (ret == KNOT_EOK && knot_pkt_have_dnssec(qdata->pkt)) {
+			ret = wildcard_list_cover(pkt, qdata);
+		}
 		break;
 	case MISS:   /* MISS, set NXDOMAIN RCODE. */
-		qdata->rcode = KNOT_RCODE_NXDOMAIN;
 		dbg_ns("%s: answer is NXDOMAIN\n", __func__);
-	case NODATA: /* NODATA or NXDOMAIN, append AUTHORITY SOA. */
+		qdata->rcode = KNOT_RCODE_NXDOMAIN;
 		ret = ns_put_authority_soa(zone_contents, pkt);
-		dbg_ns("%s: putting authority SOA = %d\n", __func__, ret);
+		if (ret == KNOT_EOK && knot_pkt_have_dnssec(qdata->pkt)) {
+			ret = ns_put_nsec_nsec3_nxdomain(zone_contents,
+							 qdata->previous,
+							 qdata->encloser,
+							 *qname, pkt);
+		}
+		break;
+	case NODATA: /* NODATA append AUTHORITY SOA + NSEC/NSEC3. */
+		dbg_ns("%s: answer is NODATA\n", __func__);
+		ret = ns_put_authority_soa(zone_contents, pkt);
+		if (ret == KNOT_EOK && knot_pkt_have_dnssec(qdata->pkt)) {
+			if (knot_dname_is_wildcard(qdata->node->owner)) {
+				ret = ns_put_nsec_nsec3_wildcard_nodata(qdata->node,
+									qdata->encloser,
+									qdata->previous,
+									qdata->zone->contents,
+									*qname, pkt);
+			} else {
+				ret = ns_put_nsec_nsec3_nodata(zone_contents,
+							       qdata->node,
+							       pkt);
+			}
+		}
 		break;
 	case DELEG:  /* Referral response. */ /*! \todo DS + NS */
 		ret = ns_referral(qdata->node, zone_contents, *qname, pkt, knot_pkt_qtype(pkt));
diff --git a/src/libknot/nameserver/name-server.c b/src/libknot/nameserver/name-server.c
index 3851c8d6112311cc6a34ece52bbee364d2695957..75f0cbcc2f0f991ad9008c7f3bcf46766070d128 100644
--- a/src/libknot/nameserver/name-server.c
+++ b/src/libknot/nameserver/name-server.c
@@ -829,7 +829,7 @@ int ns_put_additional(const knot_zone_t *zone, knot_pkt_t *resp)
 int ns_put_authority_ns(const knot_zone_contents_t *zone,
                         knot_pkt_t *resp)
 {
-	dbg_ns_verb("PUTTING AUTHORITY NS\n");
+	dbg_ns("%s: putting authority NS\n", __func__);
 	assert(KNOT_PKT_IN_NS(resp));
 
 	knot_rrset_t *ns_rrset = knot_node_get_rrset(
@@ -869,7 +869,7 @@ int ns_put_authority_soa(const knot_zone_contents_t *zone,
                                  knot_pkt_t *resp)
 {
 	assert(KNOT_PKT_IN_NS(resp));
-	dbg_ns_verb("PUTTING AUTHORITY SOA\n");
+	dbg_ns("%s: putting authority SOA\n", __func__);
 
 	int ret;
 
@@ -1219,9 +1219,9 @@ static int ns_put_nsec3_no_wildcard_child(const knot_zone_contents_t *zone,
  *             RRSets of the requested type).
  * \param resp Response where to add the NSECs or NSEC3s.
  */
-static int ns_put_nsec_nsec3_nodata(const knot_zone_contents_t *zone,
-                                    const knot_node_t *node,
-                                    knot_pkt_t *resp)
+int ns_put_nsec_nsec3_nodata(const knot_zone_contents_t *zone,
+				    const knot_node_t *node,
+				    knot_pkt_t *resp)
 {
 	if (!DNSSEC_ENABLED ||
 	    !knot_pkt_have_dnssec(resp->query)) {
@@ -1235,8 +1235,7 @@ static int ns_put_nsec_nsec3_nodata(const knot_zone_contents_t *zone,
 
 	if (knot_zone_contents_nsec3_enabled(zone)) {
 		knot_node_t *nsec3_node = knot_node_get_nsec3_node(node);
-		dbg_ns_verb("Adding NSEC3 for NODATA, NSEC3 node: %p\n",
-		            nsec3_node);
+		dbg_ns("%s: adding NSEC3 NODATA\n", __func__);
 
 		if (nsec3_node != NULL
 		    && (rrset = knot_node_get_rrset(nsec3_node,
@@ -1249,7 +1248,7 @@ static int ns_put_nsec_nsec3_nodata(const knot_zone_contents_t *zone,
 			return KNOT_ENONODE;
 		}
 	} else {
-		dbg_ns_verb("Adding NSEC for NODATA\n");
+		dbg_ns("%s: adding NSEC NODATA\n", __func__);
 		if ((rrset = knot_node_get_rrset(node, KNOT_RRTYPE_NSEC))
 		    != NULL
 		    && knot_rrset_rdata_rr_count(rrset)) {
@@ -1464,7 +1463,7 @@ static int ns_put_nsec3_nxdomain(const knot_zone_contents_t *zone,
  * \retval KNOT_EOK
  * \retval NS_ERR_SERVFAIL
  */
-static int ns_put_nsec_nsec3_nxdomain(const knot_zone_contents_t *zone,
+int ns_put_nsec_nsec3_nxdomain(const knot_zone_contents_t *zone,
                                       const knot_node_t *previous,
                                       const knot_node_t *closest_encloser,
                                       const knot_dname_t *qname,
@@ -1612,25 +1611,25 @@ static int ns_put_nsec_wildcard(const knot_zone_contents_t *zone,
  * \retval KNOT_EOK
  * \retval NS_ERR_SERVFAIL
  */
-static int ns_put_nsec_nsec3_wildcard_nodata(const knot_node_t *node,
-                                          const knot_node_t *closest_encloser,
-                                          const knot_node_t *previous,
-                                          const knot_zone_contents_t *zone,
-                                          const knot_dname_t *qname,
-                                          knot_pkt_t *resp)
+int ns_put_nsec_nsec3_wildcard_nodata(const knot_node_t *node,
+					  const knot_node_t *closest_encloser,
+					  const knot_node_t *previous,
+					  const knot_zone_contents_t *zone,
+					  const knot_dname_t *qname,
+					  knot_pkt_t *resp)
 {
 	int ret = KNOT_EOK;
 	if (DNSSEC_ENABLED
 	    && knot_pkt_have_dnssec(resp->query)) {
 		if (knot_zone_contents_nsec3_enabled(zone)) {
 			ret = ns_put_nsec3_closest_encloser_proof(zone,
-			                                      &closest_encloser,
-			                                      qname, resp);
+							      &closest_encloser,
+							      qname, resp);
 
 			const knot_node_t *nsec3_node;
 			if (ret == KNOT_EOK
 			    && (nsec3_node = knot_node_nsec3_node(node))
-			        != NULL) {
+				!= NULL) {
 				ret = ns_put_nsec3_from_node(nsec3_node, resp);
 			}
 		} else {
@@ -1684,29 +1683,7 @@ int ns_put_nsec_nsec3_wildcard_answer(const knot_node_t *node,
 
 /*----------------------------------------------------------------------------*/
 
-static int ns_put_nsec_nsec3_wildcard_nodes(knot_pkt_t *response,
-                                            const knot_zone_contents_t *zone)
-{
-	assert(response != NULL);
-	assert(zone != NULL);
 
-	int ret = 0;
-
-	for (int i = 0; i < response->wildcard_nodes.count; ++i) {
-		ret = ns_put_nsec_nsec3_wildcard_answer(
-		                        response->wildcard_nodes.nodes[i],
-		                        knot_node_parent(
-		                            response->wildcard_nodes.nodes[i]),
-		                        NULL, zone,
-		                        response->wildcard_nodes.snames[i],
-		                        response);
-		if (ret != KNOT_EOK) {
-			return ret;
-		}
-	}
-
-	return KNOT_EOK;
-}
 
 /*----------------------------------------------------------------------------*/
 /*!
@@ -2172,11 +2149,6 @@ static int ns_xfr_send_and_clear(knot_ns_xfr_t *xfr, int add_tsig)
 		knot_pkt_tsig_set(xfr->response, NULL);
 	}
 
-dbg_ns_exec_verb(
-	dbg_ns_verb("Response structure after clearing:\n");
-	knot_pkt_dump(xfr->response);
-);
-
 	return KNOT_EOK;
 }
 
@@ -2966,11 +2938,6 @@ int knot_ns_init_xfr(knot_nameserver_t *nameserver, knot_ns_xfr_t *xfr)
 		return ret;
 	}
 
-dbg_ns_exec_verb(
-	dbg_ns_verb("Parsed XFR query:\n");
-	knot_pkt_dump(xfr->query);
-);
-
 	knot_zonedb_t *zonedb = rcu_dereference(nameserver->zone_db);
 	const knot_dname_t *qname = knot_pkt_qname(xfr->query);
 
@@ -3558,6 +3525,16 @@ int ns_proc_begin(ns_proc_context_t *ctx, const ns_proc_module_t *module)
 		return NS_PROC_NOOP;
 	}
 
+#ifdef KNOT_NS_DEBUG
+	/* Check module API. */
+	assert(module->begin);
+	assert(module->in);
+	assert(module->out);
+	assert(module->err);
+	assert(module->reset);
+	assert(module->finish);
+#endif /* KNOT_NS_DEBUG */
+
 	ctx->module = module;
 	ctx->state = module->begin(ctx);
 
diff --git a/src/libknot/nameserver/name-server.h b/src/libknot/nameserver/name-server.h
index fcbfac1fb1f31e37b91f5d4fd0568518c44d2b05..e0d70e3acc2d81aabff4888063e216c9523317b8 100644
--- a/src/libknot/nameserver/name-server.h
+++ b/src/libknot/nameserver/name-server.h
@@ -397,12 +397,6 @@ const knot_zone_t *ns_get_zone_for_qname(knot_zonedb_t *zdb,
                                                   uint16_t qtype);
 
 int ns_put_additional(const knot_zone_t *zone, knot_pkt_t *resp);
-int ns_put_nsec_nsec3_wildcard_answer(const knot_node_t *node,
-                                          const knot_node_t *closest_encloser,
-                                          const knot_node_t *previous,
-                                          const knot_zone_contents_t *zone,
-                                          const knot_dname_t *qname,
-                                          knot_pkt_t *resp);
 
 int ns_put_authority_soa(const knot_zone_contents_t *zone,
                                  knot_pkt_t *resp);
@@ -431,6 +425,30 @@ int ns_process_dname(knot_rrset_t *dname_rrset,
 
 int ns_add_dnskey(const knot_node_t *apex, knot_pkt_t *resp);
 
+int ns_put_nsec_nsec3_nodata(const knot_zone_contents_t *zone,
+				    const knot_node_t *node,
+				    knot_pkt_t *resp);
+
+int ns_put_nsec_nsec3_nxdomain(const knot_zone_contents_t *zone,
+				      const knot_node_t *previous,
+				      const knot_node_t *closest_encloser,
+				      const knot_dname_t *qname,
+				      knot_pkt_t *resp);
+
+int ns_put_nsec_nsec3_wildcard_answer(const knot_node_t *node,
+				      const knot_node_t *closest_encloser,
+				      const knot_node_t *previous,
+				      const knot_zone_contents_t *zone,
+				      const knot_dname_t *qname,
+				      knot_pkt_t *resp);
+
+int ns_put_nsec_nsec3_wildcard_nodata(const knot_node_t *node,
+				      const knot_node_t *closest_encloser,
+				      const knot_node_t *previous,
+				      const knot_zone_contents_t *zone,
+				      const knot_dname_t *qname,
+				      knot_pkt_t *resp);
+
 /* #10 >>> Exposed API. */
 
 /* #10 <<< Next-gen API. */