diff --git a/Knot.files b/Knot.files
index 39842935ad156f80ee351a0185318c75d6b3abc3..04b32171b993c95250e9802b714d4a187bab0cf6 100644
--- a/Knot.files
+++ b/Knot.files
@@ -77,7 +77,9 @@ src/contrib/sockaddr.c
 src/contrib/sockaddr.h
 src/contrib/string.c
 src/contrib/string.h
+src/contrib/time.h
 src/contrib/tolower.h
+src/contrib/trim.h
 src/contrib/ucw/array-sort.h
 src/contrib/ucw/binsearch.h
 src/contrib/ucw/heap.c
@@ -241,8 +243,6 @@ src/knot/common/process.c
 src/knot/common/process.h
 src/knot/common/ref.c
 src/knot/common/ref.h
-src/knot/common/time.h
-src/knot/common/trim.h
 src/knot/conf/base.c
 src/knot/conf/base.h
 src/knot/conf/conf.c
@@ -255,6 +255,8 @@ src/knot/conf/scheme.c
 src/knot/conf/scheme.h
 src/knot/conf/tools.c
 src/knot/conf/tools.h
+src/knot/ctl/commands.c
+src/knot/ctl/commands.h
 src/knot/ctl/remote.c
 src/knot/ctl/remote.h
 src/knot/dnssec/context.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 78875fe869a185aeb83e513f46482fc0d274885e..8865614c97a85f29ce3b2a3365ff1f4e18116492 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -239,6 +239,8 @@ libknotd_la_SOURCES =				\
 	knot/conf/scheme.h			\
 	knot/conf/tools.c			\
 	knot/conf/tools.h			\
+	knot/ctl/commands.c			\
+	knot/ctl/commands.h			\
 	knot/ctl/remote.c			\
 	knot/ctl/remote.h			\
 	knot/dnssec/context.c			\
diff --git a/src/knot/common/log.c b/src/knot/common/log.c
index eb40e72de32d46330872e1b2c8daba51551cf660..0773aa96915e8f13120cc1b355e176652f052dc3 100644
--- a/src/knot/common/log.c
+++ b/src/knot/common/log.c
@@ -45,8 +45,9 @@ struct log_sink
 {
 	uint8_t *facility;     /* Log sinks. */
 	size_t facility_count; /* Sink count. */
-	FILE** file;           /* Open files. */
+	FILE **file;           /* Open files. */
 	ssize_t file_count;    /* Nr of open files. */
+	logflag_t flags;       /* Formatting flags. */
 };
 
 /*! Log sink singleton. */
@@ -208,6 +209,11 @@ bool log_isopen()
 	return s_log != NULL;
 }
 
+void log_flag_set(logflag_t flag)
+{
+	s_log->flags |= flag;
+}
+
 /*! \brief Open file as a logging facility. */
 static int log_open_file(struct log_sink *log, const char* filename)
 {
@@ -278,13 +284,15 @@ static int emit_log_msg(int level, const char *zone, size_t zone_len, const char
 	level = LOG_MASK(level);
 
 	/* Prefix date and time. */
-	char tstr[LOG_BUFLEN] = {0};
-	struct tm lt;
-	struct timeval tv;
-	gettimeofday(&tv, NULL);
-	time_t sec = tv.tv_sec;
-	if (localtime_r(&sec, &lt) != NULL) {
-		strftime(tstr, sizeof(tstr), KNOT_LOG_TIME_FORMAT " ", &lt);
+	char tstr[LOG_BUFLEN] = { 0 };
+	if (!log_isopen() || !(s_log->flags & LOG_FNO_TIMESTAMP)) {
+		struct tm lt;
+		struct timeval tv;
+		gettimeofday(&tv, NULL);
+		time_t sec = tv.tv_sec;
+		if (localtime_r(&sec, &lt) != NULL) {
+			strftime(tstr, sizeof(tstr), KNOT_LOG_TIME_FORMAT " ", &lt);
+		}
 	}
 
 	// Log streams
@@ -364,10 +372,12 @@ static int log_msg_text(int level, const char *zone, const char *fmt, va_list ar
 	size_t capacity = sizeof(sbuf) - 1;
 
 	/* Prefix error level. */
-	const char *prefix = level_prefix(level);
-	ret = log_msg_add(&write, &capacity, "%s: ", prefix);
-	if (ret != KNOT_EOK) {
-		return ret;
+	if (level != LOG_INFO || !log_isopen() || !(s_log->flags & LOG_FNO_INFO)) {
+		const char *prefix = level_prefix(level);
+		ret = log_msg_add(&write, &capacity, "%s: ", prefix);
+		if (ret != KNOT_EOK) {
+			return ret;
+		}
 	}
 
 	/* Prefix zone name. */
diff --git a/src/knot/common/log.h b/src/knot/common/log.h
index 5cdf3708faacd4ed418d22f83cb4dc3b9790a581..be7e251aaa781813f7ae8bc509fd429c44ce3c9a 100644
--- a/src/knot/common/log.h
+++ b/src/knot/common/log.h
@@ -60,6 +60,12 @@ typedef enum {
 	LOG_ANY    = 7  /*!< Any module. */
 } logsrc_t;
 
+/*! \brief Logging format flags. */
+typedef enum {
+	LOG_FNO_TIMESTAMP = 1 << 0, /*!< Don't print timestamp prefix. */
+	LOG_FNO_INFO      = 1 << 1  /*!< Don't print info level prefix. */
+} logflag_t;
+
 /*! \brief Format for timestamps in log files. */
 #define KNOT_LOG_TIME_FORMAT "%Y-%m-%dT%H:%M:%S"
 
@@ -83,6 +89,11 @@ void log_close();
  */
 bool log_isopen();
 
+/*!
+ * \brief Set logging format flag.
+ */
+void log_flag_set(logflag_t flag);
+
 /*!
  * \brief Return log levels for a given facility.
  *
diff --git a/src/knot/ctl/commands.c b/src/knot/ctl/commands.c
new file mode 100644
index 0000000000000000000000000000000000000000..7c22edb61faa9468ac560f819c0c0f4658c2743a
--- /dev/null
+++ b/src/knot/ctl/commands.c
@@ -0,0 +1,694 @@
+/*  Copyright (C) 2015 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <urcu.h>
+
+#include "knot/common/log.h"
+#include "knot/conf/confio.h"
+#include "knot/ctl/commands.h"
+#include "knot/ctl/remote.h"
+#include "libknot/libknot.h"
+#include "contrib/macros.h"
+#include "contrib/string.h"
+
+/*! \brief Callback prototype for per-zone operations. */
+typedef int (remote_zonef_t)(zone_t *, remote_cmdargs_t *);
+
+/*! \brief Append data to the output buffer. */
+static int cmdargs_append(remote_cmdargs_t *args, const char *data, size_t size)
+{
+	assert(args);
+	assert(size <= CMDARGS_ALLOC_BLOCK);
+
+	if (args->response_size + size >= args->response_max) {
+		size_t new_max = args->response_max + CMDARGS_ALLOC_BLOCK;
+		char *new_response = realloc(args->response, new_max);
+		if (!new_response) {
+			return KNOT_ENOMEM;
+		}
+
+		args->response = new_response;
+		args->response_max = new_max;
+	}
+
+	memcpy(args->response + args->response_size, data, size);
+	args->response_size += size;
+	args->response[args->response_size] = '\0';
+
+	return KNOT_EOK;
+}
+
+static void zone_apply(server_t *s, remote_cmdargs_t *a, remote_zonef_t *cb,
+                       const knot_dname_t *dname)
+{
+	int ret = KNOT_ENOZONE;
+
+	zone_t *zone = knot_zonedb_find(s->zone_db, dname);
+	if (zone != NULL) {
+		ret = cb(zone, a);
+	}
+
+	if (ret != KNOT_EOK) {
+		char name[KNOT_DNAME_TXT_MAXLEN] = "";
+		knot_dname_to_str(name, dname, sizeof(name));
+
+		char *msg = sprintf_alloc("%signoring [%s] %s",
+		                          (a->response_size > 0) ? "\n" : "",
+		                          name, knot_strerror(ret));
+		cmdargs_append(a, msg, strlen(msg));
+		free(msg);
+	}
+}
+
+/*! \brief Apply callback to all zones specified by RDATA. */
+static int zones_apply(server_t *s, remote_cmdargs_t *a, remote_zonef_t *cb)
+{
+	assert(s);
+	assert(a);
+	assert(cb);
+
+	rcu_read_lock();
+
+	/* Process all configured zones if none is specified. */
+	if (a->argc == 0) {
+		knot_zonedb_foreach(s->zone_db, cb, a);
+	} else {
+		/* Process all specified zones. */
+		for (unsigned i = 0; i < a->argc; i++) {
+			const knot_rrset_t *rr = &a->arg[i];
+			if (rr->type != KNOT_RRTYPE_NS) {
+				continue;
+			}
+
+			for (unsigned j = 0; j < rr->rrs.rr_count; j++) {
+				zone_apply(s, a, cb, knot_ns_name(&rr->rrs, j));
+			}
+		}
+	}
+
+	rcu_read_unlock();
+
+	return KNOT_EOK;
+}
+
+static char *dnssec_info(const zone_t *zone, char *buf, size_t buf_size)
+{
+	assert(zone);
+	assert(buf);
+
+	time_t refresh_at = zone_events_get_time(zone, ZONE_EVENT_DNSSEC);
+	struct tm time_gm = { 0 };
+
+	gmtime_r(&refresh_at, &time_gm);
+	size_t written = strftime(buf, buf_size, KNOT_LOG_TIME_FORMAT, &time_gm);
+	if (written == 0) {
+		return NULL;
+	}
+
+	return buf;
+}
+
+static int zone_status(zone_t *zone, remote_cmdargs_t *a)
+{
+	/* Fetch latest serial. */
+	uint32_t serial = 0;
+	if (zone->contents) {
+		const knot_rdataset_t *soa_rrs = node_rdataset(zone->contents->apex,
+		                                               KNOT_RRTYPE_SOA);
+		assert(soa_rrs != NULL);
+		serial = knot_soa_serial(soa_rrs);
+	}
+
+	/* Fetch next zone event. */
+	char when[128] = { '\0' };
+	zone_event_type_t next_type = ZONE_EVENT_INVALID;
+	const char *next_name = "";
+	if (next_type != ZONE_EVENT_INVALID) {
+		next_name = zone_events_get_name(next_type);
+		time_t next_time = zone_events_get_next(zone, &next_type);
+		next_time = next_time - time(NULL);
+		if (next_time < 0) {
+			memcpy(when, "pending", strlen("pending"));
+		} else if (snprintf(when, sizeof(when),
+		                    "in %lldh%lldm%llds",
+		                    (long long)(next_time / 3600),
+		                    (long long)(next_time % 3600) / 60,
+		                    (long long)(next_time % 60)) < 0) {
+			return KNOT_ESPACE;
+		}
+	} else {
+		memcpy(when, "idle", strlen("idle"));
+	}
+
+	/* Prepare zone info. */
+	char buf[512] = { '\0' };
+	char dnssec_buf[128] = { '\0' };
+	char *zone_name = knot_dname_to_str_alloc(zone->name);
+
+	conf_val_t val = conf_zone_get(conf(), C_DNSSEC_SIGNING, zone->name);
+	bool dnssec_enable = conf_bool(&val);
+	bool is_slave = zone_is_slave(zone);
+
+	int n = snprintf(buf, sizeof(buf),
+	                 "%s%s\ttype=%s | serial=%u | %s %s | %s %s",
+	                 (a->response_size > 0) ? "\n" : "",
+	                 zone_name,
+	                 is_slave ? "slave" : "master",
+	                 serial,
+	                 next_name,
+	                 when,
+	                 dnssec_enable ? "automatic DNSSEC, resigning at:" : "DNSSEC signing disabled",
+	                 dnssec_enable ? dnssec_info(zone, dnssec_buf, sizeof(dnssec_buf)) : "");
+	free(zone_name);
+	if (n < 0 || n >= sizeof(buf)) {
+		return KNOT_ESPACE;
+	}
+
+	return cmdargs_append(a, buf, n);
+}
+
+static int zone_reload(zone_t *zone, remote_cmdargs_t *a)
+{
+	UNUSED(a);
+
+	if (zone->flags & ZONE_EXPIRED) {
+		return KNOT_ENOTSUP;
+	}
+
+	zone_events_schedule(zone, ZONE_EVENT_LOAD, ZONE_EVENT_NOW);
+
+	return KNOT_EOK;
+}
+
+static int zone_refresh(zone_t *zone, remote_cmdargs_t *a)
+{
+	UNUSED(a);
+
+	if (!zone_is_slave(zone)) {
+		return KNOT_ENOTSUP;
+	}
+
+	zone_events_schedule(zone, ZONE_EVENT_REFRESH, ZONE_EVENT_NOW);
+
+	return KNOT_EOK;
+}
+
+static int zone_retransfer(zone_t *zone, remote_cmdargs_t *a)
+{
+	UNUSED(a);
+
+	if (!zone_is_slave(zone)) {
+		return KNOT_ENOTSUP;
+	}
+
+	zone->flags |= ZONE_FORCE_AXFR;
+	zone_events_schedule(zone, ZONE_EVENT_XFER, ZONE_EVENT_NOW);
+
+	return KNOT_EOK;
+}
+
+static int zone_flush(zone_t *zone, remote_cmdargs_t *a)
+{
+	UNUSED(a);
+
+	zone->flags |= ZONE_FORCE_FLUSH;
+	zone_events_schedule(zone, ZONE_EVENT_FLUSH, ZONE_EVENT_NOW);
+
+	return KNOT_EOK;
+}
+
+static int zone_sign(zone_t *zone, remote_cmdargs_t *a)
+{
+	UNUSED(a);
+
+	conf_val_t val = conf_zone_get(conf(), C_DNSSEC_SIGNING, zone->name);
+	if (!conf_bool(&val)) {
+		return KNOT_ENOTSUP;
+	}
+
+	zone->flags |= ZONE_FORCE_RESIGN;
+	zone_events_schedule(zone, ZONE_EVENT_DNSSEC, ZONE_EVENT_NOW);
+
+	return KNOT_EOK;
+}
+
+static int ctl_status(server_t *s, remote_cmdargs_t *a)
+{
+	UNUSED(s);
+
+	const char *msg = "Running";
+	cmdargs_append(a, msg, strlen(msg));
+
+	return KNOT_EOK;
+}
+
+static int ctl_stop(server_t *s, remote_cmdargs_t *a)
+{
+	UNUSED(s);
+	UNUSED(a);
+
+	return KNOT_CTL_ESTOP;
+}
+
+static int ctl_reload(server_t *s, remote_cmdargs_t *a)
+{
+	UNUSED(s);
+	UNUSED(a);
+
+	return server_reload(s, conf()->filename);
+}
+
+static int ctl_zone_status(server_t *s, remote_cmdargs_t *a)
+{
+	return zones_apply(s, a, zone_status);
+}
+
+static int ctl_zone_reload(server_t *s, remote_cmdargs_t *a)
+{
+	return zones_apply(s, a, zone_reload);
+}
+
+static int ctl_zone_refresh(server_t *s, remote_cmdargs_t *a)
+{
+	return zones_apply(s, a, zone_refresh);
+}
+
+static int ctl_zone_retransfer(server_t *s, remote_cmdargs_t *a)
+{
+	return zones_apply(s, a, zone_retransfer);
+}
+
+static int ctl_zone_flush(server_t *s, remote_cmdargs_t *a)
+{
+	return zones_apply(s, a, zone_flush);
+}
+
+static int ctl_zone_sign(server_t *s, remote_cmdargs_t *a)
+{
+	return zones_apply(s, a, zone_sign);
+}
+
+static int format_item(conf_io_t *io)
+{
+	remote_cmdargs_t *a = (remote_cmdargs_t *)io->misc;
+
+	// Get possible error message.
+	const char *err = io->error.str;
+	if (err == NULL && io->error.code != KNOT_EOK) {
+		err = knot_strerror(io->error.code);
+	}
+
+	// Get the item key and data strings.
+	char *key = conf_io_txt_key(io);
+	if (key == NULL) {
+		return KNOT_ERROR;
+	}
+	char *data = conf_io_txt_data(io);
+
+	// Format the item.
+	char *item = sprintf_alloc(
+		"%s%s%s%s%s%s%s",
+		(a->response_size > 0 ? "\n" : ""),
+		(err != NULL ? "Error (" : ""),
+		(err != NULL ? err : ""),
+		(err != NULL ? "): " : ""),
+		key,
+		(data != NULL ? " = " : ""),
+		(data != NULL ? data : ""));
+	free(key);
+	free(data);
+	if (item == NULL) {
+		return KNOT_ENOMEM;
+	}
+
+	// Append the item.
+	int ret = cmdargs_append(a, item, strlen(item));
+	free(item);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+
+	return KNOT_EOK;
+}
+
+static int ctl_conf_begin(server_t *s, remote_cmdargs_t *a)
+{
+	UNUSED(s);
+	UNUSED(a);
+
+	return conf_io_begin(false);
+}
+
+static int ctl_conf_commit(server_t *s, remote_cmdargs_t *a)
+{
+	UNUSED(a);
+
+	conf_io_t io = {
+		.fcn = format_item,
+		.misc = a
+	};
+
+	// First check the database.
+	int ret = conf_io_check(&io);
+	if (ret != KNOT_EOK) {
+		(void)conf_io_abort(false);
+		return ret;
+	}
+
+	ret = conf_io_commit(false);
+	if (ret != KNOT_EOK) {
+		(void)conf_io_abort(false);
+		return ret;
+	}
+
+	return server_reload(s, NULL);
+}
+
+static int ctl_conf_abort(server_t *s, remote_cmdargs_t *a)
+{
+	UNUSED(s);
+	UNUSED(a);
+
+	return conf_io_abort(false);
+}
+
+static int parse_conf_key(char *key, char **key0, char **id, char **key1)
+{
+	// Check for the empty argument.
+	if (key == NULL) {
+		*key0 = NULL;
+		*key1 = NULL;
+		*id = NULL;
+		return KNOT_EOK;
+	}
+
+	// Get key0.
+	char *_key0 = key;
+
+	// Check for id.
+	char *_id = strchr(key, '[');
+	if (_id != NULL) {
+		// Separate key0 and id.
+		*_id++ = '\0';
+
+		// Check for id end.
+		char *id_end = _id;
+		while ((id_end = strchr(id_end, ']')) != NULL) {
+			// Check for escaped character.
+			if (*(id_end - 1) != '\\') {
+				break;
+			}
+			id_end++;
+		}
+
+		// Check for unclosed id.
+		if (id_end == NULL) {
+			return KNOT_EINVAL;
+		}
+
+		// Separate id and key1.
+		*id_end = '\0';
+
+		key = id_end + 1;
+
+		// Key1 or nothing must follow.
+		if (*key != '.' && *key != '\0') {
+			return KNOT_EINVAL;
+		}
+	}
+
+	// Check for key1.
+	char *_key1 = strchr(key, '.');
+	if (_key1 != NULL) {
+		// Separate key0/id and key1.
+		*_key1++ = '\0';
+
+		if (*_key1 == '\0') {
+			return KNOT_EINVAL;
+		}
+	}
+
+	*key0 = _key0;
+	*key1 = _key1;
+	*id = _id;
+
+	return KNOT_EOK;
+}
+
+static int ctl_conf_list(server_t *s, remote_cmdargs_t *a)
+{
+	UNUSED(s);
+
+	if (a->argc > 1) {
+		return KNOT_EINVAL;
+	}
+
+	char *key = (a->argc == 1) ?
+	            (char *)remote_get_txt(&a->arg[0], 0, NULL) : NULL;
+
+	// Split key path.
+	char *key0, *key1, *id;
+	int ret = parse_conf_key(key, &key0, &id, &key1);
+	if (ret != KNOT_EOK) {
+		free(key);
+		return ret;
+	}
+
+	if (key1 != NULL || id != NULL) {
+		free(key);
+		return KNOT_EINVAL;
+	}
+
+	conf_io_t io = {
+		.fcn = format_item,
+		.misc = a
+	};
+
+	// Get items.
+	ret = conf_io_list(key0, &io);
+
+	free(key);
+
+	return ret;
+}
+
+static int ctl_conf_diff(server_t *s, remote_cmdargs_t *a)
+{
+	UNUSED(s);
+
+	if (a->argc > 1) {
+		return KNOT_EINVAL;
+	}
+
+	char *key = (a->argc == 1) ?
+	            (char *)remote_get_txt(&a->arg[0], 0, NULL) : NULL;
+
+	// Split key path.
+	char *key0, *key1, *id;
+	int ret = parse_conf_key(key, &key0, &id, &key1);
+	if (ret != KNOT_EOK) {
+		free(key);
+		return ret;
+	}
+
+	conf_io_t io = {
+		.fcn = format_item,
+		.misc = a
+	};
+
+	// Get the difference.
+	ret = conf_io_diff(key0, key1, id, &io);
+
+	free(key);
+
+	return ret;
+}
+
+static int conf_read(server_t *s, remote_cmdargs_t *a, bool get_current)
+{
+	UNUSED(s);
+
+	if (a->argc > 1) {
+		return KNOT_EINVAL;
+	}
+
+	char *key = (a->argc == 1) ?
+	            (char *)remote_get_txt(&a->arg[0], 0, NULL) : NULL;
+
+	// Split key path.
+	char *key0, *key1, *id;
+	int ret = parse_conf_key(key, &key0, &id, &key1);
+	if (ret != KNOT_EOK) {
+		free(key);
+		return ret;
+	}
+
+	conf_io_t io = {
+		.fcn = format_item,
+		.misc = a
+	};
+
+	// Get item(s) value.
+	ret = conf_io_get(key0, key1, id, get_current, &io);
+
+	free(key);
+
+	return ret;
+}
+
+static int ctl_conf_read(server_t *s, remote_cmdargs_t *a)
+{
+	return conf_read(s, a, true);
+}
+
+static int ctl_conf_get(server_t *s, remote_cmdargs_t *a)
+{
+	return conf_read(s, a, false);
+}
+
+static int ctl_conf_set(server_t *s, remote_cmdargs_t *a)
+{
+	UNUSED(s);
+
+	if (a->argc < 1 || a->argc > 255) {
+		return KNOT_EINVAL;
+	}
+
+	char *key = (char *)remote_get_txt(&a->arg[0], 0, NULL);
+
+	// Split key path.
+	char *key0, *key1, *id;
+	int ret = parse_conf_key(key, &key0, &id, &key1);
+	if (ret != KNOT_EOK) {
+		free(key);
+		return ret;
+	}
+
+	conf_io_t io = {
+		.fcn = format_item,
+		.misc = a
+	};
+
+	// Start child transaction.
+	ret = conf_io_begin(true);
+	if (ret != KNOT_EOK) {
+		free(key);
+		return ret;
+	}
+
+	// Add item with no data.
+	if (a->argc == 1) {
+		ret = conf_io_set(key0, key1, id, NULL, &io);
+	// Add item with specified data.
+	} else {
+		for (int i = 1; i < a->argc; i++) {
+			char *data = (char *)remote_get_txt(&a->arg[i], 0, NULL);
+			ret = conf_io_set(key0, key1, id, data, &io);
+			free(data);
+			if (ret != KNOT_EOK) {
+				break;
+			}
+		}
+	}
+
+	free(key);
+
+	// Finish child transaction.
+	if (ret == KNOT_EOK) {
+		return conf_io_commit(true);
+	} else {
+		(void)conf_io_abort(true);
+		return ret;
+	}
+}
+
+static int ctl_conf_unset(server_t *s, remote_cmdargs_t *a)
+{
+	UNUSED(s);
+
+	if (a->argc > 255) {
+		return KNOT_EINVAL;
+	}
+
+	char *key = (a->argc >= 1) ?
+	            (char *)remote_get_txt(&a->arg[0], 0, NULL) : NULL;
+
+	// Split key path.
+	char *key0, *key1, *id;
+	int ret = parse_conf_key(key, &key0, &id, &key1);
+	if (ret != KNOT_EOK) {
+		free(key);
+		return ret;
+	}
+
+	// Start child transaction.
+	ret = conf_io_begin(true);
+	if (ret != KNOT_EOK) {
+		free(key);
+		return ret;
+	}
+
+	// Delete item with no data.
+	if (a->argc <= 1) {
+		ret = conf_io_unset(key0, key1, id, NULL);
+	// Delete specified data.
+	} else {
+		for (int i = 1; i < a->argc; i++) {
+			char *data = (char *)remote_get_txt(&a->arg[i], 0, NULL);
+			ret = conf_io_unset(key0, key1, id, data);
+			free(data);
+			if (ret != KNOT_EOK) {
+				break;
+			}
+		}
+	}
+
+	free(key);
+
+	// Finish child transaction.
+	if (ret == KNOT_EOK) {
+		return conf_io_commit(true);
+	} else {
+		(void)conf_io_abort(true);
+		return ret;
+	}
+}
+
+/*! \brief Table of remote commands. */
+const remote_cmd_t remote_cmd_tbl[] = {
+	{ KNOT_CTL_STATUS,          ctl_status },
+	{ KNOT_CTL_STOP,            ctl_stop },
+	{ KNOT_CTL_RELOAD,          ctl_reload },
+
+	{ KNOT_CTL_ZONE_STATUS,     ctl_zone_status },
+	{ KNOT_CTL_ZONE_RELOAD,     ctl_zone_reload },
+	{ KNOT_CTL_ZONE_REFRESH,    ctl_zone_refresh },
+	{ KNOT_CTL_ZONE_RETRANSFER, ctl_zone_retransfer },
+	{ KNOT_CTL_ZONE_FLUSH,      ctl_zone_flush },
+	{ KNOT_CTL_ZONE_SIGN,       ctl_zone_sign },
+
+	{ KNOT_CTL_CONF_LIST,       ctl_conf_list },
+	{ KNOT_CTL_CONF_READ,       ctl_conf_read },
+	{ KNOT_CTL_CONF_BEGIN,      ctl_conf_begin },
+	{ KNOT_CTL_CONF_COMMIT,     ctl_conf_commit },
+	{ KNOT_CTL_CONF_ABORT,      ctl_conf_abort },
+	{ KNOT_CTL_CONF_DIFF,       ctl_conf_diff },
+	{ KNOT_CTL_CONF_GET,        ctl_conf_get },
+	{ KNOT_CTL_CONF_SET,        ctl_conf_set },
+	{ KNOT_CTL_CONF_UNSET,      ctl_conf_unset },
+	{ NULL }
+};
diff --git a/src/knot/ctl/commands.h b/src/knot/ctl/commands.h
new file mode 100644
index 0000000000000000000000000000000000000000..317e8908e773f283a1222dd13fc2e876e5186769
--- /dev/null
+++ b/src/knot/ctl/commands.h
@@ -0,0 +1,65 @@
+/*  Copyright (C) 2015 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "libknot/libknot.h"
+#include "knot/server/server.h"
+
+#define CMDARGS_ALLOC_BLOCK KNOT_WIRE_MAX_PKTSIZE
+
+#define KNOT_CTL_STATUS			"status"
+#define KNOT_CTL_STOP			"stop"
+#define KNOT_CTL_RELOAD			"reload"
+
+#define KNOT_CTL_ZONE_STATUS		"zone-status"
+#define KNOT_CTL_ZONE_RELOAD		"zone-reload"
+#define KNOT_CTL_ZONE_REFRESH		"zone-refresh"
+#define KNOT_CTL_ZONE_RETRANSFER	"zone-retransfer"
+#define KNOT_CTL_ZONE_FLUSH		"zone-flush"
+#define KNOT_CTL_ZONE_SIGN		"zone-sign"
+
+#define KNOT_CTL_CONF_LIST		"conf-list"
+#define KNOT_CTL_CONF_READ		"conf-read"
+#define KNOT_CTL_CONF_BEGIN		"conf-begin"
+#define KNOT_CTL_CONF_COMMIT		"conf-commit"
+#define KNOT_CTL_CONF_ABORT		"conf-abort"
+#define KNOT_CTL_CONF_DIFF		"conf-diff"
+#define KNOT_CTL_CONF_GET		"conf-get"
+#define KNOT_CTL_CONF_SET		"conf-set"
+#define KNOT_CTL_CONF_UNSET		"conf-unset"
+
+/*! \brief Remote command structure. */
+typedef struct {
+	const knot_rrset_t *arg;
+	unsigned argc;
+	knot_rcode_t rc;
+	char *response;
+	size_t response_size;
+	size_t response_max;
+} remote_cmdargs_t;
+
+/*! \brief Callback prototype for remote commands. */
+typedef int (*remote_cmdf_t)(server_t *, remote_cmdargs_t *);
+
+/*! \brief Remote command table item. */
+typedef struct {
+	const char *name;
+	remote_cmdf_t f;
+} remote_cmd_t;
+
+/*! \brief Table of remote commands. */
+extern const remote_cmd_t remote_cmd_tbl[];
diff --git a/src/knot/ctl/remote.c b/src/knot/ctl/remote.c
index 533aa03904fc70c20dee2ba43d3af1f4937c5099..882554973cfa18f1cbd88fa7d154caa227cbbaec 100644
--- a/src/knot/ctl/remote.c
+++ b/src/knot/ctl/remote.c
@@ -1,4 +1,4 @@
-/*  Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/*  Copyright (C) 2015 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
 
     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -18,19 +18,12 @@
 #include <urcu.h>
 
 #include "dnssec/random.h"
-#include "knot/common/fdset.h"
 #include "knot/common/log.h"
 #include "knot/conf/conf.h"
-#include "knot/conf/confdb.h"
-#include "knot/conf/confio.h"
+#include "knot/ctl/commands.h"
 #include "knot/ctl/remote.h"
-#include "knot/dnssec/zone-sign.h"
-#include "knot/dnssec/zone-nsec.h"
 #include "knot/server/tcp-handler.h"
-#include "knot/zone/timers.h"
 #include "libknot/libknot.h"
-#include "libknot/yparser/yptrafo.h"
-#include "contrib/macros.h"
 #include "contrib/net.h"
 #include "contrib/sockaddr.h"
 #include "contrib/string.h"
@@ -39,19 +32,8 @@
 
 #define KNOT_CTL_REALM "knot."
 #define KNOT_CTL_REALM_EXT ("." KNOT_CTL_REALM)
-#define CMDARGS_ALLOC_BLOCK KNOT_WIRE_MAX_PKTSIZE
 #define CMDARGS_BUFLEN_LOG 256
 
-/*! \brief Remote command structure. */
-typedef struct remote_cmdargs {
-	const knot_rrset_t *arg;
-	unsigned argc;
-	knot_rcode_t rc;
-	char *response;
-	size_t response_size;
-	size_t response_max;
-} remote_cmdargs_t;
-
 /*! \brief Initialize cmdargs_t structure. */
 static int cmdargs_init(remote_cmdargs_t *args)
 {
@@ -69,30 +51,6 @@ static int cmdargs_init(remote_cmdargs_t *args)
 	return KNOT_EOK;
 }
 
-/*! \brief Append data to the output buffer. */
-static int cmdargs_append(remote_cmdargs_t *args, const char *data, size_t size)
-{
-	assert(args);
-	assert(size <= CMDARGS_ALLOC_BLOCK);
-
-	if (args->response_size + size >= args->response_max) {
-		size_t new_max = args->response_max + CMDARGS_ALLOC_BLOCK;
-		char *new_response = realloc(args->response, new_max);
-		if (!new_response) {
-			return KNOT_ENOMEM;
-		}
-
-		args->response = new_response;
-		args->response_max = new_max;
-	}
-
-	memcpy(args->response + args->response_size, data, size);
-	args->response_size += size;
-	args->response[args->response_size] = '\0';
-
-	return KNOT_EOK;
-}
-
 /*! \brief Deinitialize cmdargs_t structure. */
 static void cmdargs_deinit(remote_cmdargs_t *args)
 {
@@ -102,804 +60,6 @@ static void cmdargs_deinit(remote_cmdargs_t *args)
 	memset(args, 0, sizeof(*args));
 }
 
-/*! \brief Callback prototype for remote commands. */
-typedef int (*remote_cmdf_t)(server_t *, remote_cmdargs_t *);
-
-/*! \brief Callback prototype for per-zone operations. */
-typedef int (remote_zonef_t)(zone_t *, remote_cmdargs_t *);
-
-/*! \brief Remote command table item. */
-typedef struct remote_cmd {
-	const char *name;
-	remote_cmdf_t f;
-} remote_cmd_t;
-
-/* Forward decls. */
-static int remote_c_stop(server_t *s, remote_cmdargs_t *a);
-static int remote_c_reload(server_t *s, remote_cmdargs_t *a);
-static int remote_c_refresh(server_t *s, remote_cmdargs_t *a);
-static int remote_c_retransfer(server_t *s, remote_cmdargs_t *a);
-static int remote_c_status(server_t *s, remote_cmdargs_t *a);
-static int remote_c_zonestatus(server_t *s, remote_cmdargs_t *a);
-static int remote_c_flush(server_t *s, remote_cmdargs_t *a);
-static int remote_c_signzone(server_t *s, remote_cmdargs_t *a);
-static int remote_c_conf_begin(server_t *s, remote_cmdargs_t *a);
-static int remote_c_conf_commit(server_t *s, remote_cmdargs_t *a);
-static int remote_c_conf_abort(server_t *s, remote_cmdargs_t *a);
-static int remote_c_conf_desc(server_t *s, remote_cmdargs_t *a);
-static int remote_c_conf_diff(server_t *s, remote_cmdargs_t *a);
-static int remote_c_conf_read(server_t *s, remote_cmdargs_t *a);
-static int remote_c_conf_get(server_t *s, remote_cmdargs_t *a);
-static int remote_c_conf_set(server_t *s, remote_cmdargs_t *a);
-static int remote_c_conf_unset(server_t *s, remote_cmdargs_t *a);
-
-/*! \brief Table of remote commands. */
-struct remote_cmd remote_cmd_tbl[] = {
-	{ "stop",        &remote_c_stop },
-	{ "reload",      &remote_c_reload },
-	{ "refresh",     &remote_c_refresh },
-	{ "retransfer",  &remote_c_retransfer },
-	{ "status",      &remote_c_status },
-	{ "zonestatus",  &remote_c_zonestatus },
-	{ "flush",       &remote_c_flush },
-	{ "signzone",    &remote_c_signzone },
-	{ "conf-begin",  &remote_c_conf_begin },
-	{ "conf-commit", &remote_c_conf_commit },
-	{ "conf-abort",  &remote_c_conf_abort },
-	{ "conf-desc",   &remote_c_conf_desc },
-	{ "conf-diff",   &remote_c_conf_diff },
-	{ "conf-read",   &remote_c_conf_read },
-	{ "conf-get",    &remote_c_conf_get },
-	{ "conf-set",    &remote_c_conf_set },
-	{ "conf-unset",  &remote_c_conf_unset },
-	{ NULL }
-};
-
-/* Private APIs. */
-
-/*! \brief Apply callback to all zones specified by RDATA of NS RRs. */
-static int remote_rdata_apply(server_t *s, remote_cmdargs_t *a, remote_zonef_t *cb)
-{
-	if (!s || !a || !cb) {
-		return KNOT_EINVAL;
-	}
-
-	zone_t *zone = NULL;
-	int ret = KNOT_EOK;
-
-	for (unsigned i = 0; i < a->argc; ++i) {
-		/* Process all zones in data section. */
-		const knot_rrset_t *rr = &a->arg[i];
-		if (rr->type != KNOT_RRTYPE_NS) {
-			continue;
-		}
-
-		uint16_t rr_count = rr->rrs.rr_count;
-		for (uint16_t i = 0; i < rr_count; i++) {
-			const knot_dname_t *dn = knot_ns_name(&rr->rrs, i);
-			zone = knot_zonedb_find(s->zone_db, dn);
-			if (zone == NULL) {
-				char zname[KNOT_DNAME_MAXLEN];
-				knot_dname_to_str(zname, dn, KNOT_DNAME_MAXLEN);
-				log_warning("remote control, zone %s not found.",
-				            zname);
-			} else if (cb(zone, a) != KNOT_EOK) {
-				a->rc = KNOT_RCODE_SERVFAIL;
-			}
-		}
-	}
-
-	return ret;
-}
-
-/*! \brief Zone refresh callback. */
-static int remote_zone_refresh(zone_t *zone, remote_cmdargs_t *a)
-{
-	UNUSED(a);
-
-	rcu_read_lock();
-	bool is_slave = zone_is_slave(zone);
-	rcu_read_unlock();
-
-	if (!is_slave) {
-		return KNOT_EINVAL;
-	}
-
-	zone_events_schedule(zone, ZONE_EVENT_REFRESH, ZONE_EVENT_NOW);
-	return KNOT_EOK;
-}
-
-/*! \brief Zone reload callback. */
-static int remote_zone_reload(zone_t *zone, remote_cmdargs_t *a)
-{
-	UNUSED(a);
-
-	if (zone->flags & ZONE_EXPIRED) {
-		log_zone_warning(zone->name, "cannot reload expired zone");
-		return KNOT_EOK;
-	}
-
-	zone_events_schedule(zone, ZONE_EVENT_LOAD, ZONE_EVENT_NOW);
-	return KNOT_EOK;
-}
-
-/*! \brief Zone refresh callback. */
-static int remote_zone_retransfer(zone_t *zone, remote_cmdargs_t *a)
-{
-	UNUSED(a);
-
-	rcu_read_lock();
-	bool is_slave = zone_is_slave(zone);
-	rcu_read_unlock();
-
-	if (!is_slave) {
-		return KNOT_EINVAL;
-	}
-
-	zone->flags |= ZONE_FORCE_AXFR;
-	zone_events_schedule(zone, ZONE_EVENT_XFER, ZONE_EVENT_NOW);
-	return KNOT_EOK;
-}
-
-/*! \brief Zone flush callback. */
-static int remote_zone_flush(zone_t *zone, remote_cmdargs_t *a)
-{
-	UNUSED(a);
-
-	zone->flags |= ZONE_FORCE_FLUSH;
-	zone_events_schedule(zone, ZONE_EVENT_FLUSH, ZONE_EVENT_NOW);
-	return KNOT_EOK;
-}
-
-/*! \brief Sign zone callback. */
-static int remote_zone_sign(zone_t *zone, remote_cmdargs_t *a)
-{
-	UNUSED(a);
-
-	rcu_read_lock();
-	conf_val_t val = conf_zone_get(conf(), C_DNSSEC_SIGNING, zone->name);
-	bool dnssec_enable = conf_bool(&val);
-	rcu_read_unlock();
-
-	if (!dnssec_enable) {
-		return KNOT_EINVAL;
-	}
-
-	zone->flags |= ZONE_FORCE_RESIGN;
-	zone_events_schedule(zone, ZONE_EVENT_DNSSEC, ZONE_EVENT_NOW);
-	return KNOT_EOK;
-}
-
-/*!
- * \brief Remote command 'stop' handler.
- *
- * QNAME: stop
- * DATA: NULL
- */
-static int remote_c_stop(server_t *s, remote_cmdargs_t *a)
-{
-	UNUSED(a);
-	UNUSED(s);
-	return KNOT_CTL_STOP;
-}
-
-/*!
- * \brief Remote command 'reload' handler.
- *
- * QNAME: reload
- * DATA: NONE for all zones
- *       NS RRs with zones in RDATA
- */
-static int remote_c_reload(server_t *s, remote_cmdargs_t *a)
-{
-	int ret = KNOT_EOK;
-
-	if (a->argc == 0) {
-		/* Reload all. */
-		ret = server_reload(s, conf()->filename);
-	} else {
-		rcu_read_lock();
-		/* Reload specific zones. */
-		ret = remote_rdata_apply(s, a, &remote_zone_reload);
-		rcu_read_unlock();
-	}
-
-	return (ret != KNOT_EOK) ? ret : KNOT_CTL_ACCEPTED;
-}
-
-/*!
- * \brief Remote command 'status' handler.
- *
- * QNAME: status
- * DATA: NONE
- */
-static int remote_c_status(server_t *s, remote_cmdargs_t *a)
-{
-	UNUSED(s);
-	UNUSED(a);
-	return KNOT_EOK;
-}
-
-static char *dnssec_info(const zone_t *zone, char *buf, size_t buf_size)
-{
-	assert(zone);
-	assert(buf);
-
-	time_t refresh_at = zone_events_get_time(zone, ZONE_EVENT_DNSSEC);
-	struct tm time_gm = { 0 };
-
-	gmtime_r(&refresh_at, &time_gm);
-	size_t written = strftime(buf, buf_size, KNOT_LOG_TIME_FORMAT, &time_gm);
-	if (written == 0) {
-		return NULL;
-	}
-
-	return buf;
-}
-
-static int remote_zonestatus(zone_t *zone, remote_cmdargs_t *a)
-{
-	if (zone == NULL || a == NULL) {
-		return KNOT_EINVAL;
-	}
-
-	/* Fetch latest serial. */
-	const knot_rdataset_t *soa_rrs = NULL;
-	uint32_t serial = 0;
-	if (zone->contents) {
-		soa_rrs = node_rdataset(zone->contents->apex,
-		                        KNOT_RRTYPE_SOA);
-		assert(soa_rrs != NULL);
-		serial = knot_soa_serial(soa_rrs);
-	}
-
-	/* Fetch next zone event. */
-	char when[128] = { '\0' };
-	zone_event_type_t next_type = ZONE_EVENT_INVALID;
-	const char *next_name = "";
-	time_t next_time = zone_events_get_next(zone, &next_type);
-	if (next_type != ZONE_EVENT_INVALID) {
-		next_name = zone_events_get_name(next_type);
-		next_time = next_time - time(NULL);
-		if (next_time < 0) {
-			memcpy(when, "pending", strlen("pending"));
-		} else if (snprintf(when, sizeof(when),
-		                    "in %lldh%lldm%llds",
-		                    (long long)(next_time / 3600),
-		                    (long long)(next_time % 3600) / 60,
-		                    (long long)(next_time % 60)) < 0) {
-			return KNOT_ESPACE;
-		}
-	} else {
-		memcpy(when, "idle", strlen("idle"));
-	}
-
-	/* Prepare zone info. */
-	char buf[512] = { '\0' };
-	char dnssec_buf[128] = { '\0' };
-	char *zone_name = knot_dname_to_str_alloc(zone->name);
-
-	conf_val_t val = conf_zone_get(conf(), C_DNSSEC_SIGNING, zone->name);
-	bool dnssec_enable = conf_bool(&val);
-	bool is_slave = zone_is_slave(zone);
-
-	int n = snprintf(buf, sizeof(buf),
-	                 "%s\ttype=%s | serial=%u | %s %s | %s %s\n",
-	                 zone_name,
-	                 is_slave ? "slave" : "master",
-	                 serial,
-	                 next_name,
-	                 when,
-	                 dnssec_enable ? "automatic DNSSEC, resigning at:" : "DNSSEC signing disabled",
-	                 dnssec_enable ? dnssec_info(zone, dnssec_buf, sizeof(dnssec_buf)) : "");
-	free(zone_name);
-	if (n < 0 || n >= sizeof(buf)) {
-		return KNOT_ESPACE;
-	}
-
-	return cmdargs_append(a, buf, n);
-}
-
-/*!
- * \brief Remote command 'zonestatus' handler.
- *
- * QNAME: zonestatus
- * DATA: NONE for all zones
- *       NS RRs with zones in RDATA
- */
-static int remote_c_zonestatus(server_t *s, remote_cmdargs_t *a)
-{
-	rcu_read_lock();
-	if (a->argc == 0) {
-		knot_zonedb_foreach(s->zone_db, remote_zonestatus, a);
-	} else {
-		remote_rdata_apply(s, a, remote_zonestatus);
-	}
-	rcu_read_unlock();
-
-	return KNOT_EOK;
-}
-
-/*!
- * \brief Remote command 'refresh' handler.
- *
- * QNAME: refresh
- * DATA: NONE for all zones
- *       NS RRs with zones in RDATA
- */
-static int remote_c_refresh(server_t *s, remote_cmdargs_t *a)
-{
-	rcu_read_lock();
-	if (a->argc == 0) {
-		/* Refresh all. */
-		knot_zonedb_foreach(s->zone_db, remote_zone_refresh, NULL);
-	} else {
-		/* Refresh specific zones. */
-		remote_rdata_apply(s, a, remote_zone_refresh);
-	}
-	rcu_read_unlock();
-
-	return KNOT_CTL_ACCEPTED;
-}
-
-/*!
- * \brief Remote command 'retransfer' handler.
- *
- * QNAME: retransfer
- * DATA: NS RRs with zones in RDATA
- */
-static int remote_c_retransfer(server_t *s, remote_cmdargs_t *a)
-{
-	if (a->argc == 0) {
-		/* Retransfer all. */
-		return KNOT_CTL_ARG_REQ;
-	} else {
-		rcu_read_lock();
-		/* Retransfer specific zones. */
-		remote_rdata_apply(s, a, remote_zone_retransfer);
-		rcu_read_unlock();
-	}
-
-	return KNOT_CTL_ACCEPTED;
-
-}
-
-/*!
- * \brief Remote command 'flush' handler.
- *
- * QNAME: flush
- * DATA: NONE for all zones
- *       NS RRs with zones in RDATA
- */
-static int remote_c_flush(server_t *s, remote_cmdargs_t *a)
-{
-	rcu_read_lock();
-	if (a->argc == 0) {
-		/* Flush all. */
-		knot_zonedb_foreach(s->zone_db, remote_zone_flush, NULL);
-	} else {
-		/* Flush specific zones. */
-		remote_rdata_apply(s, a, remote_zone_flush);
-	}
-	rcu_read_unlock();
-
-	return KNOT_CTL_ACCEPTED;
-}
-
-/*!
- * \brief Remote command 'signzone' handler.
- */
-static int remote_c_signzone(server_t *s, remote_cmdargs_t *a)
-{
-	if (a->argc == 0) {
-		/* Resign all. */
-		return KNOT_CTL_ARG_REQ;
-	} else {
-		rcu_read_lock();
-		/* Resign specific zones. */
-		remote_rdata_apply(s, a, remote_zone_sign);
-		rcu_read_unlock();
-	}
-
-	return KNOT_CTL_ACCEPTED;
-}
-
-static int format_item(conf_io_t *io)
-{
-	remote_cmdargs_t *a = (remote_cmdargs_t *)io->misc;
-
-	// Get possible error message.
-	const char *err = io->error.str;
-	if (err == NULL && io->error.code != KNOT_EOK) {
-		err = knot_strerror(io->error.code);
-	}
-
-	// Get the item key and data strings.
-	char *key = conf_io_txt_key(io);
-	if (key == NULL) {
-		return KNOT_ERROR;
-	}
-	char *data = conf_io_txt_data(io);
-
-	// Format the item.
-	char *item = sprintf_alloc(
-		"%s%s%s%s%s%s%s",
-		(a->response_size > 0 ? "\n" : ""),
-		(err != NULL ? "Error (" : ""),
-		(err != NULL ? err : ""),
-		(err != NULL ? "): " : ""),
-		key,
-		(data != NULL ? " = " : ""),
-		(data != NULL ? data : ""));
-	free(key);
-	free(data);
-	if (item == NULL) {
-		return KNOT_ENOMEM;
-	}
-
-	// Append the item.
-	int ret = cmdargs_append(a, item, strlen(item));
-	free(item);
-	if (ret != KNOT_EOK) {
-		return ret;
-	}
-
-	return KNOT_EOK;
-}
-
-/*!
- * \brief Remote command 'conf-begin' handler.
- */
-static int remote_c_conf_begin(server_t *s, remote_cmdargs_t *a)
-{
-	UNUSED(s);
-	UNUSED(a);
-
-	return conf_io_begin(false);
-}
-
-/*!
- * \brief Remote command 'conf-commit' handler.
- */
-static int remote_c_conf_commit(server_t *s, remote_cmdargs_t *a)
-{
-	UNUSED(a);
-
-	conf_io_t io = {
-		.fcn = format_item,
-		.misc = a
-	};
-
-	// First check the database.
-	int ret = conf_io_check(&io);
-	if (ret != KNOT_EOK) {
-		(void)conf_io_abort(false);
-		return ret;
-	}
-
-	ret = conf_io_commit(false);
-	if (ret != KNOT_EOK) {
-		(void)conf_io_abort(false);
-		return ret;
-	}
-
-	return server_reload(s, NULL);
-}
-
-/*!
- * \brief Remote command 'conf-abort' handler.
- */
-static int remote_c_conf_abort(server_t *s, remote_cmdargs_t *a)
-{
-	UNUSED(s);
-	UNUSED(a);
-
-	return conf_io_abort(false);
-}
-
-/*!
- * \brief Parse config key path key0[id].key1.
- */
-static int parse_conf_key(char *key, char **key0, char **id, char **key1)
-{
-	// Check for the empty argument.
-	if (key == NULL) {
-		*key0 = NULL;
-		*key1 = NULL;
-		*id = NULL;
-		return KNOT_EOK;
-	}
-
-	// Get key0.
-	char *_key0 = key;
-
-	// Check for id.
-	char *_id = strchr(key, '[');
-	if (_id != NULL) {
-		// Separate key0 and id.
-		*_id++ = '\0';
-
-		// Check for id end.
-		char *id_end = _id;
-		while ((id_end = strchr(id_end, ']')) != NULL) {
-			// Check for escaped character.
-			if (*(id_end - 1) != '\\') {
-				break;
-			}
-			id_end++;
-		}
-
-		// Check for unclosed id.
-		if (id_end == NULL) {
-			return KNOT_EINVAL;
-		}
-
-		// Separate id and key1.
-		*id_end = '\0';
-
-		key = id_end + 1;
-
-		// Key1 or nothing must follow.
-		if (*key != '.' && *key != '\0') {
-			return KNOT_EINVAL;
-		}
-	}
-
-	// Check for key1.
-	char *_key1 = strchr(key, '.');
-	if (_key1 != NULL) {
-		// Separate key0/id and key1.
-		*_key1++ = '\0';
-
-		if (*_key1 == '\0') {
-			return KNOT_EINVAL;
-		}
-	}
-
-	*key0 = _key0;
-	*key1 = _key1;
-	*id = _id;
-
-	return KNOT_EOK;
-}
-
-/*!
- * \brief Remote command 'conf-desc' handler.
- */
-static int remote_c_conf_desc(server_t *s, remote_cmdargs_t *a)
-{
-	UNUSED(s);
-
-	if (a->argc > 1) {
-		return KNOT_EINVAL;
-	}
-
-	char *key = (a->argc == 1) ?
-	            (char *)remote_get_txt(&a->arg[0], 0, NULL) : NULL;
-
-	// Split key path.
-	char *key0, *key1, *id;
-	int ret = parse_conf_key(key, &key0, &id, &key1);
-	if (ret != KNOT_EOK) {
-		free(key);
-		return ret;
-	}
-
-	if (key1 != NULL || id != NULL) {
-		free(key);
-		return KNOT_EINVAL;
-	}
-
-	conf_io_t io = {
-		.fcn = format_item,
-		.misc = a
-	};
-
-	// Get items.
-	ret = conf_io_desc(key0, &io);
-
-	free(key);
-
-	return ret;
-}
-
-/*!
- * \brief Remote command 'conf-diff' handler.
- */
-static int remote_c_conf_diff(server_t *s, remote_cmdargs_t *a)
-{
-	UNUSED(s);
-
-	if (a->argc > 1) {
-		return KNOT_EINVAL;
-	}
-
-	char *key = (a->argc == 1) ?
-	            (char *)remote_get_txt(&a->arg[0], 0, NULL) : NULL;
-
-	// Split key path.
-	char *key0, *key1, *id;
-	int ret = parse_conf_key(key, &key0, &id, &key1);
-	if (ret != KNOT_EOK) {
-		free(key);
-		return ret;
-	}
-
-	conf_io_t io = {
-		.fcn = format_item,
-		.misc = a
-	};
-
-	// Get the difference.
-	ret = conf_io_diff(key0, key1, id, &io);
-
-	free(key);
-
-	return ret;
-}
-
-static int conf_read(server_t *s, remote_cmdargs_t *a, bool get_current)
-{
-	UNUSED(s);
-
-	if (a->argc > 1) {
-		return KNOT_EINVAL;
-	}
-
-	char *key = (a->argc == 1) ?
-	            (char *)remote_get_txt(&a->arg[0], 0, NULL) : NULL;
-
-	// Split key path.
-	char *key0, *key1, *id;
-	int ret = parse_conf_key(key, &key0, &id, &key1);
-	if (ret != KNOT_EOK) {
-		free(key);
-		return ret;
-	}
-
-	conf_io_t io = {
-		.fcn = format_item,
-		.misc = a
-	};
-
-	// Get item(s) value.
-	ret = conf_io_get(key0, key1, id, get_current, &io);
-
-	free(key);
-
-	return ret;
-}
-
-/*!
- * \brief Remote command 'conf-read' handler.
- */
-static int remote_c_conf_read(server_t *s, remote_cmdargs_t *a)
-{
-	return conf_read(s, a, true);
-}
-
-/*!
- * \brief Remote command 'conf-get' handler.
- */
-static int remote_c_conf_get(server_t *s, remote_cmdargs_t *a)
-{
-	return conf_read(s, a, false);
-}
-
-/*!
- * \brief Remote command 'conf-set' handler.
- */
-static int remote_c_conf_set(server_t *s, remote_cmdargs_t *a)
-{
-	UNUSED(s);
-
-	if (a->argc < 1 || a->argc > 255) {
-		return KNOT_EINVAL;
-	}
-
-	char *key = (char *)remote_get_txt(&a->arg[0], 0, NULL);
-
-	// Split key path.
-	char *key0, *key1, *id;
-	int ret = parse_conf_key(key, &key0, &id, &key1);
-	if (ret != KNOT_EOK) {
-		free(key);
-		return ret;
-	}
-
-	conf_io_t io = {
-		.fcn = format_item,
-		.misc = a
-	};
-
-	// Start child transaction.
-	ret = conf_io_begin(true);
-	if (ret != KNOT_EOK) {
-		free(key);
-		return ret;
-	}
-
-	// Add item with no data.
-	if (a->argc == 1) {
-		ret = conf_io_set(key0, key1, id, NULL, &io);
-	// Add item with specified data.
-	} else {
-		for (int i = 1; i < a->argc; i++) {
-			char *data = (char *)remote_get_txt(&a->arg[i], 0, NULL);
-			ret = conf_io_set(key0, key1, id, data, &io);
-			free(data);
-			if (ret != KNOT_EOK) {
-				break;
-			}
-		}
-	}
-
-	free(key);
-
-	// Finish child transaction.
-	if (ret == KNOT_EOK) {
-		return conf_io_commit(true);
-	} else {
-		(void)conf_io_abort(true);
-		return ret;
-	}
-}
-
-/*!
- * \brief Remote command 'conf-unset' handler.
- */
-static int remote_c_conf_unset(server_t *s, remote_cmdargs_t *a)
-{
-	UNUSED(s);
-
-	if (a->argc > 255) {
-		return KNOT_EINVAL;
-	}
-
-	char *key = (a->argc >= 1) ?
-	            (char *)remote_get_txt(&a->arg[0], 0, NULL) : NULL;
-
-	// Split key path.
-	char *key0, *key1, *id;
-	int ret = parse_conf_key(key, &key0, &id, &key1);
-	if (ret != KNOT_EOK) {
-		free(key);
-		return ret;
-	}
-
-	// Start child transaction.
-	ret = conf_io_begin(true);
-	if (ret != KNOT_EOK) {
-		free(key);
-		return ret;
-	}
-
-	// Delete item with no data.
-	if (a->argc <= 1) {
-		ret = conf_io_unset(key0, key1, id, NULL);
-	// Delete specified data.
-	} else {
-		for (int i = 1; i < a->argc; i++) {
-			char *data = (char *)remote_get_txt(&a->arg[i], 0, NULL);
-			ret = conf_io_unset(key0, key1, id, data);
-			free(data);
-			if (ret != KNOT_EOK) {
-				break;
-			}
-		}
-	}
-
-	free(key);
-
-	// Finish child transaction.
-	if (ret == KNOT_EOK) {
-		return conf_io_commit(true);
-	} else {
-		(void)conf_io_abort(true);
-		return ret;
-	}
-}
-
 int remote_bind(const char *path)
 {
 	if (path == NULL) {
@@ -1116,7 +276,7 @@ int remote_answer(int sock, server_t *s, knot_pkt_t *pkt)
 
 	log_command(cmd, &args);
 
-	remote_cmd_t *c = remote_cmd_tbl;
+	const remote_cmd_t *c = remote_cmd_tbl;
 	while (c->name != NULL) {
 		if (strcmp(cmd, c->name) == 0) {
 			ret = c->f(s, &args);
diff --git a/src/knot/main.c b/src/knot/main.c
index a1eed00cdde420b6e58cd7a3ef0ddfe7ff0edf65..25630f6d1f5a5ac2c6df60790012fe96557cdf68 100644
--- a/src/knot/main.c
+++ b/src/knot/main.c
@@ -212,7 +212,7 @@ static void event_loop(server_t *server)
 		/* Events. */
 		if (ret > 0) {
 			ret = remote_process(server, sock, buf, buflen);
-			if (ret == KNOT_CTL_STOP) {
+			if (ret == KNOT_CTL_ESTOP) {
 				break;
 			}
 		}
diff --git a/src/libknot/errcode.h b/src/libknot/errcode.h
index 84ce514d2dc82bb066c91e35c8b6a4dafa79150c..fe607686f8464683d7a4ac053d9f4a1280e88b26 100644
--- a/src/libknot/errcode.h
+++ b/src/libknot/errcode.h
@@ -89,9 +89,9 @@ enum knot_error {
 	KNOT_EFILE,
 
 	/* Control states. */
-	KNOT_CTL_STOP,
-	KNOT_CTL_ACCEPTED,
-	KNOT_CTL_ARG_REQ,
+	KNOT_CTL_ESTOP,
+	KNOT_CTL_EACCEPTED,
+	KNOT_CTL_EARG_REQ,
 
 	/* Network errors. */
 	KNOT_NET_EADDR,
diff --git a/src/libknot/error.c b/src/libknot/error.c
index 5892a4258e62946e8ac851e0f9cceeec0dbba2cb..e65faeed44c24802aa355e4e23519124df0c683c 100644
--- a/src/libknot/error.c
+++ b/src/libknot/error.c
@@ -88,9 +88,9 @@ static const struct error errors[] = {
 	{ KNOT_EFILE,        "file error" },
 
 	/* Control states. */
-	{ KNOT_CTL_STOP,     "stopping server" },
-	{ KNOT_CTL_ACCEPTED, "command accepted" },
-	{ KNOT_CTL_ARG_REQ,  "argument required" },
+	{ KNOT_CTL_ESTOP,     "stopping server" },
+	{ KNOT_CTL_EACCEPTED, "command accepted" },
+	{ KNOT_CTL_EARG_REQ,  "argument required" },
 
 	/* Network errors. */
 	{ KNOT_NET_EADDR,    "bad address or host name" },