diff --git a/samples/knot.full.conf b/samples/knot.full.conf
index 5d66be9b6378848efd7206c68ea0360ad6f06a67..609b23187f7c6fb1260b9bbcba2e6d14c417183f 100644
--- a/samples/knot.full.conf
+++ b/samples/knot.full.conf
@@ -132,6 +132,21 @@ remotes {
   }
 }
 
+# Section 'control' specifies on which interface to listen for RC commands
+control {
+
+  # Specifies interface, syntax is exactly the same as in 'interfaces' section
+  # Note: as of now, it is possible replay commands in a short time frame
+  #       with MitM type attacks, so you should keep the interface on localnet.
+  # Default port is: 5553
+  listen-on { address 127.0.0.1@5553; }
+  
+  # Specifies ACL list for remote control
+  # Same syntax as for ACLs in zones
+  # List of remotes delimited by comma
+  allow server0;
+}
+
 # Section 'zones' contains information about zones to be served.
 zones {
 
diff --git a/src/knot/conf/cf-parse.y b/src/knot/conf/cf-parse.y
index 082dde37466448da9990e3659ffebc844507713a..8833073986ae6d1f17e53d32babbcc8925ad6a71 100644
--- a/src/knot/conf/cf-parse.y
+++ b/src/knot/conf/cf-parse.y
@@ -15,7 +15,6 @@
 #include "common/sockaddr.h"
 #include "libknot/dname.h"
 #include "knot/conf/conf.h"
-#include "knot/ctl/remote.h"
 #include "libknotd_la-cf-parse.h" /* Automake generated header. */
 
 extern int cf_lex (YYSTYPE *lvalp, void *scanner);
@@ -43,7 +42,7 @@ static void conf_init_iface(void *scanner, char* ifname, int port)
 
 static void conf_start_iface(void *scanner, char* ifname)
 {
-   conf_init_iface(scanner, ifname, CONFIG_DEFAULT_PORT);
+   conf_init_iface(scanner, ifname, -1);
    add_tail(&new_config->ifaces, &this_iface->n);
    ++new_config->ifaces_count;
 }
@@ -313,7 +312,7 @@ interface_start:
 
 interface:
  | interface PORT NUM ';' {
-     if (this_iface->port != CONFIG_DEFAULT_PORT) {
+     if (this_iface->port > 0) {
        cf_error(scanner, "only one port definition is allowed in interface section\n");
      } else {
        this_iface->port = $3.i;
@@ -333,7 +332,7 @@ interface:
      } else {
        this_iface->address = $3.t;
        this_iface->family = AF_INET;
-       if (this_iface->port != CONFIG_DEFAULT_PORT) {
+       if (this_iface->port > 0) {
 	 cf_error(scanner, "only one port definition is allowed in interface section\n");
        } else {
 	 this_iface->port = $5.i;
@@ -354,7 +353,7 @@ interface:
      } else {
        this_iface->address = $3.t;
        this_iface->family = AF_INET6;
-       if (this_iface->port != CONFIG_DEFAULT_PORT) {
+       if (this_iface->port > 0) {
           cf_error(scanner, "only one port definition is allowed in interface section\n");
        } else {
           this_iface->port = $5.i;
@@ -822,7 +821,7 @@ log_start:
 log: LOG '{' log_start log_end;
 
 ctl_listen_start:
-  LISTEN_ON { conf_init_iface(scanner, NULL, REMOTE_DPORT); }
+  LISTEN_ON { conf_init_iface(scanner, NULL, -1); }
   ;
 
 ctl_allow_start:
diff --git a/src/knot/conf/conf.c b/src/knot/conf/conf.c
index 5ed187c4ef7e3fd33aeb9681c39ec545472c0e05..c99c11f36086ff6567a2dbff53c928440f10ad71 100644
--- a/src/knot/conf/conf.c
+++ b/src/knot/conf/conf.c
@@ -26,6 +26,7 @@
 #include <urcu.h>
 #include "knot/conf/conf.h"
 #include "knot/common.h"
+#include "knot/ctl/remote.h"
 
 /*
  * Defaults.
@@ -145,6 +146,17 @@ static int conf_process(conf_t *conf)
 	if (conf->max_conn_reply < 1) {
 		conf->max_conn_reply = CONFIG_REPLY_WD;
 	}
+	
+	// Postprocess interfaces
+	conf_iface_t *cfg_if = NULL;
+	WALK_LIST(cfg_if, conf->ifaces) {
+		if (cfg_if->port <= 0) {
+			cfg_if->port = CONFIG_DEFAULT_PORT;
+		}
+	}
+	if (conf->ctl.iface && conf->ctl.iface->port <= 0) {
+		conf->ctl.iface->port = REMOTE_DPORT;
+	}
 
 	// Postprocess zones
 	int ret = KNOT_EOK;
diff --git a/src/knot/ctl/knotc_main.c b/src/knot/ctl/knotc_main.c
index d61489288c99b8633e08949b46a3c979c46b5f13..ec251f936a839e1b1f2fe7b3b21256943babd621 100644
--- a/src/knot/ctl/knotc_main.c
+++ b/src/knot/ctl/knotc_main.c
@@ -620,9 +620,11 @@ int main(int argc, char **argv)
 		if (strchr(r_addr, ':')) { /* Dumb way to check for v6 addr. */
 			ctl_if->family = AF_INET6;
 		}
-		ctl_if->key = malloc(sizeof(knot_key_t));
-		if (ctl_if->key) {
-			memcpy(ctl_if->key, &r_key, sizeof(knot_key_t));
+		if (r_key.name) {
+			ctl_if->key = malloc(sizeof(knot_key_t));
+			if (ctl_if->key) {
+				memcpy(ctl_if->key, &r_key, sizeof(knot_key_t));
+			}
 		}
 	}
 	conf()->ctl.iface = ctl_if;
diff --git a/src/knot/ctl/remote.c b/src/knot/ctl/remote.c
index a9e494c5788d46aa028b7cbc042b818e3344b717..ed86b9973412dbea2f9a9890ce01989882cf9e3c 100644
--- a/src/knot/ctl/remote.c
+++ b/src/knot/ctl/remote.c
@@ -459,17 +459,41 @@ int remote_process(server_t *s, int r, uint8_t* buf, size_t buflen)
 		
 		/* Check ACL list. */
 		rcu_read_lock();
+		knot_key_t *k = NULL;
 		acl_key_t *m = NULL;
+		knot_rcode_t ts_rc = 0;
+		uint16_t ts_trc = 0;
+		uint64_t ts_tmsigned = 0;
+		const knot_rrset_t *tsig_rr = knot_packet_tsig(pkt);
 		if (acl_match(conf()->ctl.acl, &a, &m) == ACL_DENY) {
 			knot_packet_free(&pkt);
 			socket_close(c);
 			rcu_read_unlock();
 			return KNOT_EACCES;
 		}
+		if (m && m->val) {
+			k = ((conf_iface_t *)m->val)->key;
+		}
 		rcu_read_unlock();
 		
 		/* Check TSIG. */
-		/*! \todo #2035 */
+		if (k) {
+			if (!tsig_rr) {
+				knot_packet_free(&pkt);
+				socket_close(c);
+				return KNOT_EACCES;
+			}
+			ret = zones_verify_tsig_query(pkt, k, &ts_rc,
+			                              &ts_trc, &ts_tmsigned);
+			if (ret != KNOT_EOK) {
+				dbg_server("remote: failed to verify TSIG, "
+				           "RC: %u TSIG_RC: %u\n",
+				           ts_rc, ts_trc);
+				knot_packet_free(&pkt);
+				socket_close(c);
+				return KNOT_EACCES;
+			}
+		}
 		
 		/* Answer packet. */
 		wire_len = buflen;
diff --git a/src/knot/server/zones.c b/src/knot/server/zones.c
index ffc5347c0716cf8ce83813c974f372b1a07ab77d..a93bbc95c6189a244b6db5b82d7811d43cbf7db3 100644
--- a/src/knot/server/zones.c
+++ b/src/knot/server/zones.c
@@ -1855,117 +1855,6 @@ dbg_zones_exec(
 
 /*----------------------------------------------------------------------------*/
 
-static int zones_verify_tsig_query(const knot_packet_t *query,
-                                   const knot_rrset_t *tsig_rr,
-                                   const knot_key_t *key,
-                                   knot_rcode_t *rcode, uint16_t *tsig_rcode,
-                                   uint64_t *tsig_prev_time_signed)
-{
-	assert(tsig_rr != NULL);
-	assert(key != NULL);
-	assert(rcode != NULL);
-	assert(tsig_rcode != NULL);
-
-	/*
-	 * 1) Check if we support the requested algorithm.
-	 */
-	tsig_algorithm_t alg = tsig_rdata_alg(tsig_rr);
-	if (tsig_alg_digest_length(alg) == 0) {
-		log_answer_info("Unsupported digest algorithm "
-		                "requested, treating as bad key\n");
-		/*! \todo [TSIG] It is unclear from RFC if I
-		 *               should treat is as a bad key
-		 *               or some other error.
-		 */
-		*rcode = KNOT_RCODE_NOTAUTH;
-		*tsig_rcode = KNOT_TSIG_RCODE_BADKEY;
-		return KNOT_TSIG_EBADKEY;
-	}
-
-	const knot_dname_t *kname = knot_rrset_owner(tsig_rr);
-	assert(kname != NULL);
-
-	/*
-	 * 2) Find the particular key used by the TSIG.
-	 *    Check not only name, but also the algorithm.
-	 */
-	if (key && kname && knot_dname_compare(key->name, kname) == 0
-	    && key->algorithm == alg) {
-		dbg_zones_verb("Found claimed TSIG key for comparison\n");
-	} else {
-		*rcode = KNOT_RCODE_NOTAUTH;
-		*tsig_rcode = KNOT_TSIG_RCODE_BADKEY;
-		return KNOT_TSIG_EBADKEY;
-	}
-
-	/*
-	 * 3) Validate the query with TSIG.
-	 */
-	/* Prepare variables for TSIG */
-	/*! \todo These need to be saved to the response somehow. */
-	//size_t tsig_size = tsig_wire_maxsize(key);
-	size_t digest_max_size = tsig_alg_digest_length(key->algorithm);
-	//size_t digest_size = 0;
-	//uint64_t tsig_prev_time_signed = 0;
-	//uint8_t *digest = (uint8_t *)malloc(digest_max_size);
-	//memset(digest, 0 , digest_max_size);
-
-	/* Copy MAC from query. */
-	dbg_zones_verb("Validating TSIG from query\n");
-
-	//const uint8_t* mac = tsig_rdata_mac(tsig_rr);
-	size_t mac_len = tsig_rdata_mac_length(tsig_rr);
-
-	int ret = KNOT_EOK;
-
-	if (mac_len > digest_max_size) {
-		*rcode = KNOT_RCODE_FORMERR;
-		dbg_zones("MAC length %zu exceeds digest "
-		       "maximum size %zu\n", mac_len, digest_max_size);
-		return KNOT_EMALF;
-	} else {
-		//memcpy(digest, mac, mac_len);
-		//digest_size = mac_len;
-
-		/* Check query TSIG. */
-		ret = knot_tsig_server_check(tsig_rr,
-		                             knot_packet_wireformat(query),
-		                             knot_packet_size(query), key);
-		dbg_zones_verb("knot_tsig_server_check() returned %s\n",
-		               knot_strerror(ret));
-
-		/* Evaluate TSIG check results. */
-		switch(ret) {
-		case KNOT_EOK:
-			*rcode = KNOT_RCODE_NOERROR;
-			break;
-		case KNOT_TSIG_EBADKEY:
-			*tsig_rcode = KNOT_TSIG_RCODE_BADKEY;
-			*rcode = KNOT_RCODE_NOTAUTH;
-			break;
-		case KNOT_TSIG_EBADSIG:
-			*tsig_rcode = KNOT_TSIG_RCODE_BADSIG;
-			*rcode = KNOT_RCODE_NOTAUTH;
-			break;
-		case KNOT_TSIG_EBADTIME:
-			*tsig_rcode = KNOT_TSIG_RCODE_BADTIME;
-			// store the time signed from the query
-			*tsig_prev_time_signed = tsig_rdata_time_signed(tsig_rr);
-			*rcode = KNOT_RCODE_NOTAUTH;
-			break;
-		case KNOT_EMALF:
-			*rcode = KNOT_RCODE_FORMERR;
-			break;
-		default:
-			*rcode = KNOT_RCODE_SERVFAIL;
-		}
-	}
-
-	return ret;
-}
-
-/*----------------------------------------------------------------------------*/
-
 static int zones_check_tsig_query(const knot_zone_t *zone,
                                   knot_packet_t *query,
                                   const sockaddr_t *addr,
@@ -2014,7 +1903,7 @@ static int zones_check_tsig_query(const knot_zone_t *zone,
 		if (*tsig_key_zone != NULL) {
 			// everything OK, so check TSIG
 			dbg_zones_verb("Verifying TSIG.\n");
-			ret = zones_verify_tsig_query(query, tsig, *tsig_key_zone,
+			ret = zones_verify_tsig_query(query, *tsig_key_zone,
 			                              rcode, tsig_rcode,
 			                              tsig_prev_time_signed);
 		} else {
@@ -4128,3 +4017,114 @@ int zones_process_update_response(knot_nameserver_t *ns,
 	*rsize = 0;
 	return ret;
 }
+
+
+int zones_verify_tsig_query(const knot_packet_t *query,
+                            const knot_key_t *key,
+                            knot_rcode_t *rcode, uint16_t *tsig_rcode,
+                            uint64_t *tsig_prev_time_signed)
+{
+	assert(key != NULL);
+	assert(rcode != NULL);
+	assert(tsig_rcode != NULL);
+	
+	const knot_rrset_t *tsig_rr = knot_packet_tsig(query);
+	assert(tsig_rr != NULL);
+
+	/*
+	 * 1) Check if we support the requested algorithm.
+	 */
+	tsig_algorithm_t alg = tsig_rdata_alg(tsig_rr);
+	if (tsig_alg_digest_length(alg) == 0) {
+		log_answer_info("Unsupported digest algorithm "
+		                "requested, treating as bad key\n");
+		/*! \todo [TSIG] It is unclear from RFC if I
+		 *               should treat is as a bad key
+		 *               or some other error.
+		 */
+		*rcode = KNOT_RCODE_NOTAUTH;
+		*tsig_rcode = KNOT_TSIG_RCODE_BADKEY;
+		return KNOT_TSIG_EBADKEY;
+	}
+
+	const knot_dname_t *kname = knot_rrset_owner(tsig_rr);
+	assert(kname != NULL);
+
+	/*
+	 * 2) Find the particular key used by the TSIG.
+	 *    Check not only name, but also the algorithm.
+	 */
+	if (key && kname && knot_dname_compare(key->name, kname) == 0
+	    && key->algorithm == alg) {
+		dbg_zones_verb("Found claimed TSIG key for comparison\n");
+	} else {
+		*rcode = KNOT_RCODE_NOTAUTH;
+		*tsig_rcode = KNOT_TSIG_RCODE_BADKEY;
+		return KNOT_TSIG_EBADKEY;
+	}
+
+	/*
+	 * 3) Validate the query with TSIG.
+	 */
+	/* Prepare variables for TSIG */
+	/*! \todo These need to be saved to the response somehow. */
+	//size_t tsig_size = tsig_wire_maxsize(key);
+	size_t digest_max_size = tsig_alg_digest_length(key->algorithm);
+	//size_t digest_size = 0;
+	//uint64_t tsig_prev_time_signed = 0;
+	//uint8_t *digest = (uint8_t *)malloc(digest_max_size);
+	//memset(digest, 0 , digest_max_size);
+
+	/* Copy MAC from query. */
+	dbg_zones_verb("Validating TSIG from query\n");
+
+	//const uint8_t* mac = tsig_rdata_mac(tsig_rr);
+	size_t mac_len = tsig_rdata_mac_length(tsig_rr);
+
+	int ret = KNOT_EOK;
+
+	if (mac_len > digest_max_size) {
+		*rcode = KNOT_RCODE_FORMERR;
+		dbg_zones("MAC length %zu exceeds digest "
+		       "maximum size %zu\n", mac_len, digest_max_size);
+		return KNOT_EMALF;
+	} else {
+		//memcpy(digest, mac, mac_len);
+		//digest_size = mac_len;
+
+		/* Check query TSIG. */
+		ret = knot_tsig_server_check(tsig_rr,
+		                             knot_packet_wireformat(query),
+		                             knot_packet_size(query), key);
+		dbg_zones_verb("knot_tsig_server_check() returned %s\n",
+		               knot_strerror(ret));
+
+		/* Evaluate TSIG check results. */
+		switch(ret) {
+		case KNOT_EOK:
+			*rcode = KNOT_RCODE_NOERROR;
+			break;
+		case KNOT_TSIG_EBADKEY:
+			*tsig_rcode = KNOT_TSIG_RCODE_BADKEY;
+			*rcode = KNOT_RCODE_NOTAUTH;
+			break;
+		case KNOT_TSIG_EBADSIG:
+			*tsig_rcode = KNOT_TSIG_RCODE_BADSIG;
+			*rcode = KNOT_RCODE_NOTAUTH;
+			break;
+		case KNOT_TSIG_EBADTIME:
+			*tsig_rcode = KNOT_TSIG_RCODE_BADTIME;
+			// store the time signed from the query
+			*tsig_prev_time_signed = tsig_rdata_time_signed(tsig_rr);
+			*rcode = KNOT_RCODE_NOTAUTH;
+			break;
+		case KNOT_EMALF:
+			*rcode = KNOT_RCODE_FORMERR;
+			break;
+		default:
+			*rcode = KNOT_RCODE_SERVFAIL;
+		}
+	}
+
+	return ret;
+}
diff --git a/src/knot/server/zones.h b/src/knot/server/zones.h
index 9797dd8b8a8ad4390a2e2b028828905258df2c90..04d5363350571597381cf6e2c26a6a64e9d079b8 100644
--- a/src/knot/server/zones.h
+++ b/src/knot/server/zones.h
@@ -340,6 +340,22 @@ int zones_process_update_response(knot_nameserver_t *ns,
                                   knot_ns_xfr_t *data,
                                   knot_packet_t *packet, uint8_t *rwire,
                                   size_t *rsize, size_t maxlen);
+
+/*!
+ * \brief Verify TSIG in query.
+ *
+ * \param query Query packet.
+ * \param key TSIG key used for this query.
+ * \param rcode Dst for resulting RCODE.
+ * \param tsig_rcode Dst for resulting TSIG RCODE.
+ * \param tsig_prev_time_signed Dst for previout time signed.
+ *
+ * \return KNOT_EOK if verified or error if not.
+ */
+int zones_verify_tsig_query(const knot_packet_t *query,
+                            const knot_key_t *key,
+                            knot_rcode_t *rcode, uint16_t *tsig_rcode,
+                            uint64_t *tsig_prev_time_signed);
 #endif // _KNOTD_ZONES_H_
 
 /*! @} */