diff --git a/daemon/engine.c b/daemon/engine.c
index 33699df99904fa142d69b70f8515d9ad23e2095b..c8dfb40d36f0e184072cd33d531e30f7360f0234 100644
--- a/daemon/engine.c
+++ b/daemon/engine.c
@@ -203,6 +203,9 @@ static int init_resolver(struct engine *engine)
 {
 	/* Open resolution context */
 	engine->resolver.modules = &engine->modules;
+	/* Set default root hints */
+	kr_zonecut_init(&engine->resolver.root_hints, (const uint8_t *)"", engine->pool);
+	kr_zonecut_set_sbelt(&engine->resolver, &engine->resolver.root_hints);
 	/* Open NS rtt + reputation cache */
 	engine->resolver.cache_rtt = malloc(lru_size(kr_nsrep_lru_t, LRU_RTT_SIZE));
 	if (engine->resolver.cache_rtt) {
@@ -292,6 +295,7 @@ void engine_deinit(struct engine *engine)
 	}
 
 	network_deinit(&engine->net);
+	kr_zonecut_deinit(&engine->resolver.root_hints);
 	kr_cache_close(&engine->resolver.cache);
 	lru_deinit(engine->resolver.cache_rtt);
 	lru_deinit(engine->resolver.cache_rep);
diff --git a/lib/resolve.c b/lib/resolve.c
index 0bc3fbd995017192ab8882deee7d73ce540e9ec2..74e9cf4e7c47a72c643900ac39b8612fd7e7e677 100644
--- a/lib/resolve.c
+++ b/lib/resolve.c
@@ -54,7 +54,7 @@ static int ns_fetch_cut(struct kr_query *qry, struct kr_request *req)
 	struct kr_cache_txn txn;
 	int ret = 0;
 	if (kr_cache_txn_begin(&req->ctx->cache, &txn, NAMEDB_RDONLY) != 0) {
-		ret = kr_zonecut_set_sbelt(&qry->zone_cut);
+		ret = kr_zonecut_set_sbelt(req->ctx, &qry->zone_cut);
 	} else {
 		/* If at/subdomain of parent zone cut, start from 'one up' to avoid loops */
 		struct kr_query *parent = qry->parent;
diff --git a/lib/resolve.h b/lib/resolve.h
index 3411f3fa050893e89cfeda67757c7a793b10d1c8..8a976229d1e38e0c69475ee821b35fae1b28af40 100644
--- a/lib/resolve.h
+++ b/lib/resolve.h
@@ -104,6 +104,7 @@ typedef array_t(struct kr_module *) module_array_t;
 struct kr_context
 {
 	mm_ctx_t *pool;
+	struct kr_zonecut root_hints;
 	struct kr_cache cache;
 	kr_nsrep_lru_t *cache_rtt;
 	kr_nsrep_lru_t *cache_rep;
diff --git a/lib/zonecut.c b/lib/zonecut.c
index 810dc96a305238dbe54ce39eb96ac51cd8e08959..5ea9f0f2cbd20237e2a8604208d654cee09a62aa 100644
--- a/lib/zonecut.c
+++ b/lib/zonecut.c
@@ -64,7 +64,7 @@ static void update_cut_name(struct kr_zonecut *cut, const knot_dname_t *name)
 
 int kr_zonecut_init(struct kr_zonecut *cut, const knot_dname_t *name, mm_ctx_t *pool)
 {
-	if (cut == NULL || name == NULL) {
+	if (!cut || !name) {
 		return kr_error(EINVAL);
 	}
 
@@ -87,7 +87,7 @@ static int free_addr_set(const char *k, void *v, void *baton)
 
 void kr_zonecut_deinit(struct kr_zonecut *cut)
 {
-	if (cut == NULL) {
+	if (!cut) {
 		return;
 	}
 	mm_free(cut->pool, cut->name);
@@ -97,16 +97,51 @@ void kr_zonecut_deinit(struct kr_zonecut *cut)
 
 void kr_zonecut_set(struct kr_zonecut *cut, const knot_dname_t *name)
 {
-	if (cut == NULL || name == NULL) {
+	if (!cut || !name) {
 		return;
 	}
 	kr_zonecut_deinit(cut);
 	kr_zonecut_init(cut, name, cut->pool);
 }
 
+static int copy_addr_set(const char *k, void *v, void *baton)
+{
+	pack_t *addr_set = v;
+	struct kr_zonecut *dst = baton;
+	/* Clone addr_set pack */
+	pack_t *new_set = mm_alloc(dst->pool, sizeof(*new_set));
+	if (!new_set) {
+		return kr_error(ENOMEM);
+	}
+	new_set->at = mm_alloc(dst->pool, addr_set->len);
+	if (!new_set->at) {
+		mm_free(dst->pool, new_set);
+		return kr_error(ENOMEM);
+	}
+	memcpy(new_set->at, addr_set->at, addr_set->len);
+	new_set->len = addr_set->len;
+	new_set->cap = new_set->len;
+	/* Reinsert */
+	if (map_set(&dst->nsset, k, new_set) != 0) {
+		pack_clear_mm(*new_set, mm_free, dst->pool);
+		mm_free(dst->pool, new_set);
+		return kr_error(ENOMEM);
+	}
+	return kr_ok();
+}
+
+int kr_zonecut_copy(struct kr_zonecut *dst, const struct kr_zonecut *src)
+{
+	if (!dst || !src) {
+		return kr_error(EINVAL);
+	}
+	/* We're not touching src nsset, I promise */
+	return map_walk((map_t *)&src->nsset, copy_addr_set, dst);
+}
+
 int kr_zonecut_add(struct kr_zonecut *cut, const knot_dname_t *ns, const knot_rdata_t *rdata)
 {
-	if (cut == NULL || ns == NULL) {
+	if (!cut || !ns) {
 		return kr_error(EINVAL);
 	}
 	/* Fetch/insert nameserver. */
@@ -138,7 +173,7 @@ int kr_zonecut_add(struct kr_zonecut *cut, const knot_dname_t *ns, const knot_rd
 
 int kr_zonecut_del(struct kr_zonecut *cut, const knot_dname_t *ns, const knot_rdata_t *rdata)
 {
-	if (cut == NULL || ns == NULL) {
+	if (!cut || !ns) {
 		return kr_error(EINVAL);
 	}
 
@@ -163,7 +198,7 @@ int kr_zonecut_del(struct kr_zonecut *cut, const knot_dname_t *ns, const knot_rd
 
 pack_t *kr_zonecut_find(struct kr_zonecut *cut, const knot_dname_t *ns)
 {
-	if (cut == NULL || ns == NULL) {
+	if (!cut || !ns) {
 		return NULL;
 	}
 
@@ -172,13 +207,23 @@ pack_t *kr_zonecut_find(struct kr_zonecut *cut, const knot_dname_t *ns)
 	return map_get(nsset, key);
 }
 
-int kr_zonecut_set_sbelt(struct kr_zonecut *cut)
+int kr_zonecut_set_sbelt(struct kr_context *ctx, struct kr_zonecut *cut)
 {
-	if (cut == NULL) {
+	if (!ctx || !cut) {
 		return kr_error(EINVAL);
 	}
 
 	update_cut_name(cut, U8(""));
+
+	/* Copy root hints from resolution context. */
+	if (ctx->root_hints.nsset.root) {
+		int ret = kr_zonecut_copy(cut, &ctx->root_hints);
+		if (ret == 0) {
+			return ret;
+		}
+	}
+
+	/* Copy compiled-in root hints */
 	for (unsigned i = 0; i < HINT_COUNT; ++i) {
 		const struct hint_info *hint = &SBELT[i];
 		knot_rdata_t rdata[knot_rdata_array_size(HINT_ADDRLEN)];
@@ -239,7 +284,7 @@ static int fetch_ns(struct kr_context *ctx, struct kr_zonecut *cut, const knot_d
 
 	/* Always keep SBELT as a backup for root */
 	if (name[0] == '\0') {
-		kr_zonecut_set_sbelt(cut);
+		kr_zonecut_set_sbelt(ctx, cut);
 	}
 
 	return kr_ok();
@@ -269,5 +314,5 @@ int kr_zonecut_find_cached(struct kr_context *ctx, struct kr_zonecut *cut, const
 	}
 
 	/* Name server not found, start with SBELT. */
-	return kr_zonecut_set_sbelt(cut);
+	return kr_zonecut_set_sbelt(ctx, cut);
 }
diff --git a/lib/zonecut.h b/lib/zonecut.h
index 990220aba43e714456307ff13e18817488300575..c9be6b327666526c4bc130598179908b23583428 100644
--- a/lib/zonecut.h
+++ b/lib/zonecut.h
@@ -55,6 +55,14 @@ void kr_zonecut_deinit(struct kr_zonecut *cut);
  */
 void kr_zonecut_set(struct kr_zonecut *cut, const knot_dname_t *name);
 
+/** 
+ * Copy zone cut, including all data.
+ * @param dst destination zone cut
+ * @param src source zone cut
+ * @return 0 or an error code
+ */
+int kr_zonecut_copy(struct kr_zonecut *dst, const struct kr_zonecut *src);
+
 /**
  * Add address record to the zone cut.
  *
@@ -92,10 +100,11 @@ pack_t *kr_zonecut_find(struct kr_zonecut *cut, const knot_dname_t *ns);
 /**
  * Populate zone cut with a root zone using SBELT :rfc:`1034`
  *
+ * @param ctx resolution context (to fetch root hints)
  * @param cut zone cut to be populated
  * @return 0 or error code
  */
-int kr_zonecut_set_sbelt(struct kr_zonecut *cut);
+int kr_zonecut_set_sbelt(struct kr_context *ctx, struct kr_zonecut *cut);
 
 /**
  * Populate zone cut address set from cache.
diff --git a/modules/hints/README.rst b/modules/hints/README.rst
index 0b83ced7e717c3222486517e219bda680d8d7706..e69bbc5ac850e4430dc76dffe4ba778dc3c7d1ac 100644
--- a/modules/hints/README.rst
+++ b/modules/hints/README.rst
@@ -4,6 +4,8 @@ Static hints
 ------------
 
 This is a module providing static hints from ``/etc/hosts`` like file.
+You can also use it to change root hints that are used as a safety belt or if the root NS
+drops out of cache.
 
 Properties
 ^^^^^^^^^^
@@ -28,3 +30,34 @@ Properties
   :return: ``{ result: bool }``
 
   Set hostname - address pair hint.
+
+.. function:: hints.root()
+
+  :return: ``{ ['a.root-servers.net'] = { '1.2.3.4', '5.6.7.8', ...}, ... }``
+
+  .. tip:: If no parameters are passed, returns current root hints set.
+
+.. function:: hints.root(root_hints)
+
+  :param table root_hints: new set of root hints i.e. ``{['name'] = 'addr', ...}``
+  :return: ``{ ['a.root-servers.net'] = { '1.2.3.4', '5.6.7.8', ...}, ... }``
+
+  Replace current root hints and return the current table of root hints.
+
+  Example:
+
+  .. code-block:: lua
+
+	> hints.root({
+		['l.root-servers.net.'] = '199.7.83.42',
+		['m.root-servers.net.'] = '202.12.27.33'
+	})
+	[l.root-servers.net.] => {
+	    [1] => 199.7.83.42
+	}
+	[m.root-servers.net.] => {
+	    [1] => 202.12.27.33
+	}
+
+  .. tip:: A good rule of thumb is to select only a few fastest root hints. The server learns RTT and NS quality over time, and thus tries all servers available. You can help it by preselecting the candidates.
+  
\ No newline at end of file
diff --git a/modules/hints/hints.c b/modules/hints/hints.c
index 8d42359480cf4dd34d8a317d50013f3399d71d1a..80f81fb2e80e92fa5511244483d40e835e814a86 100644
--- a/modules/hints/hints.c
+++ b/modules/hints/hints.c
@@ -28,7 +28,7 @@
 #include <ccan/json/json.h>
 #include <ucw/mempool.h>
 
-#include "lib/layer/iterate.h"
+#include "daemon/engine.h"
 #include "lib/zonecut.h"
 #include "lib/module.h"
 #include "lib/layer.h"
@@ -222,6 +222,24 @@ static char* hint_set(void *env, struct kr_module *module, const char *args)
 	return result;
 }
 
+/** @internal Pack address list into JSON array. */
+static JsonNode *pack_addrs(pack_t *pack)
+{
+	char buf[SOCKADDR_STRLEN];
+	JsonNode *root = json_mkarray();
+	uint8_t *addr = pack_head(*pack);
+	while (addr != pack_tail(*pack)) {
+		size_t len = pack_obj_len(addr);
+		int family = len == sizeof(struct in_addr) ? AF_INET : AF_INET6;
+		if (!inet_ntop(family, pack_obj_val(addr), buf, sizeof(buf))) {
+			break;
+		}
+		json_append_element(root, json_mkstring(buf));
+		addr = pack_obj_next(addr);
+	}
+	return root;
+}
+
 /**
  * Retrieve address hint for given name.
  *
@@ -240,21 +258,59 @@ static char* hint_get(void *env, struct kr_module *module, const char *args)
 		return NULL;
 	}
 
-	char buf[SOCKADDR_STRLEN];
-	JsonNode *root = json_mkarray();
-	uint8_t *addr = pack_head(*pack);
-	while (addr != pack_tail(*pack)) {
-		size_t len = pack_obj_len(addr);
-		int family = len == sizeof(struct in_addr) ? AF_INET : AF_INET6;
-		if (!inet_ntop(family, pack_obj_val(addr), buf, sizeof(buf))) {
-			break;
-		}
-		json_append_element(root, json_mkstring(buf));
-		addr = pack_obj_next(addr);
+	char *result = NULL;
+	JsonNode *root = pack_addrs(pack);
+	if (root) {
+		result = json_encode(root);
+		json_delete(root);
 	}
+	return result;
+}
+
+/** Retrieve hint list. */
+static int pack_hint(const char *k, void *v, void *baton)
+{
+	char nsname_str[KNOT_DNAME_MAXLEN] = {'\0'};
+	knot_dname_to_str(nsname_str, (const uint8_t *)k, sizeof(nsname_str));
+	JsonNode *root_node = baton;
+	JsonNode *addr_list = pack_addrs((pack_t *)v);
+	if (!addr_list) {
+		return kr_error(ENOMEM);
+	}
+	json_append_member(root_node, nsname_str, addr_list);
+	return kr_ok();
+}
 
-	char *result = json_encode(root);
-	json_delete(root);
+/**
+ * Get/set root hints set.
+ *
+ * Input:  { name: [addr_list], ... }
+ * Output: current list
+ *
+ */
+static char* hint_root(void *env, struct kr_module *module, const char *args)
+{
+	struct engine *engine = env;
+	struct kr_context *ctx = &engine->resolver;
+	/* Replace root hints if parameter is set */
+	if (args && strlen(args) > 0) {
+		JsonNode *node = NULL;
+		JsonNode *root_node = json_decode(args);
+		kr_zonecut_set(&ctx->root_hints, (const uint8_t *)"");
+		json_foreach(node, root_node) {
+			switch(node->tag) {
+			case JSON_STRING: add_pair(&ctx->root_hints, node->key, node->string_); break;
+			default: continue;
+			}
+		}
+	}
+	/* Return current root hints */
+	char *result = NULL;
+	JsonNode *root_node = json_mkobject();
+	if (map_walk(&ctx->root_hints.nsset, pack_hint, root_node) == 0) {
+		result = json_encode(root_node);
+	}
+	json_delete(root_node);
 	return result;
 }
 
@@ -298,6 +354,7 @@ struct kr_prop *hints_props(void)
 	static struct kr_prop prop_list[] = {
 	    { &hint_set,    "set", "Set {name, address} hint.", },
 	    { &hint_get,    "get", "Retrieve hint for given name.", },
+	    { &hint_root,   "root", "Replace root hints set (empty value to return current list).", },
 	    { NULL, NULL, NULL }
 	};
 	return prop_list;
diff --git a/tests/test_zonecut.c b/tests/test_zonecut.c
index 24b9a8fec5408b9b9cf2f7ce0d637c06a6946395..202899c3a4341321802c34b74ccad07e6be2a763 100644
--- a/tests/test_zonecut.c
+++ b/tests/test_zonecut.c
@@ -29,14 +29,34 @@ static void test_zonecut_params(void **state)
 	assert_int_not_equal(kr_zonecut_add(NULL, NULL, NULL), 0);
 	assert_null((void *)kr_zonecut_find(NULL, NULL));
 	assert_null((void *)kr_zonecut_find(&cut, NULL));
-	assert_int_not_equal(kr_zonecut_set_sbelt(NULL), 0);
+	assert_int_not_equal(kr_zonecut_set_sbelt(NULL, NULL), 0);
 	assert_int_not_equal(kr_zonecut_find_cached(NULL, NULL, NULL, NULL, 0), 0);
 }
 
+static void test_zonecut_copy(void **state)
+{
+	const knot_dname_t *root = (const uint8_t *)"";
+	struct kr_zonecut cut1, cut2;
+	kr_zonecut_init(&cut1, root, NULL);
+	kr_zonecut_init(&cut2, root, NULL);
+	/* Insert some values */
+	assert_int_equal(kr_zonecut_add(&cut1, (const uint8_t *)"dead", NULL), 0);
+	assert_int_equal(kr_zonecut_add(&cut1, (const uint8_t *)"beef", NULL), 0);
+	/* Copy */
+	assert_int_equal(kr_zonecut_copy(&cut2, &cut1), 0);
+	/* Check if exist */
+	assert_non_null(kr_zonecut_find(&cut2, (const uint8_t *)"dead"));
+	assert_non_null(kr_zonecut_find(&cut2, (const uint8_t *)"beef"));
+	assert_null(kr_zonecut_find(&cut2, (const uint8_t *)"corn"));
+	kr_zonecut_deinit(&cut1);
+	kr_zonecut_deinit(&cut2);
+}
+
 int main(void)
 {
 	const UnitTest tests[] = {
-	        unit_test(test_zonecut_params)
+	        unit_test(test_zonecut_params),
+	        unit_test(test_zonecut_copy)
 	};
 
 	return run_tests(tests);