diff --git a/daemon/lua/trust_anchors.lua b/daemon/lua/trust_anchors.lua
index 7f3c287bc8887c66ff96eb1ea597e80809e24838..cfc7a9ecc55b2c89354f48a38d31862898f39159 100644
--- a/daemon/lua/trust_anchors.lua
+++ b/daemon/lua/trust_anchors.lua
@@ -1,9 +1,6 @@
 local kres = require('kres')
 local C = require('ffi').C
 
--- Add or remove hold-down timer
-local hold_down_time = 30 * day
-
 -- RFC5011 state table
 local key_state = {
 	Start = 'Start', AddPend = 'AddPend', Valid = 'Valid',
@@ -16,7 +13,7 @@ local function ta_find(keyset, rr)
 		local ta = keyset[i]
 		-- Match key owner and content
 		if ta.owner == rr.owner and
-		   C.kr_dnssec_key_match(ta.rdata, #ta.rdata, rr.rdata, #rr.rdata) then
+		   C.kr_dnssec_key_match(ta.rdata, #ta.rdata, rr.rdata, #rr.rdata) == 0 then
 		   return ta
 		end
 	end
@@ -24,10 +21,10 @@ local function ta_find(keyset, rr)
 end
 
 -- Evaluate TA status according to RFC5011
-local function ta_present(keyset, rr, force)
+local function ta_present(keyset, rr, hold_down_time, force)
 	if not C.kr_dnssec_key_ksk(rr.rdata) then
 		return false -- Ignore
-	end	
+	end
 	-- Find the key in current key set and check its status
 	local now = os.time()
 	local key_revoked = C.kr_dnssec_key_revoked(rr.rdata)
@@ -59,6 +56,7 @@ local function ta_present(keyset, rr, force)
 		print('[trust_anchors] key: '..key_tag..' state: '..ta.state)
 		return true
 	elseif not key_revoked then -- First time seen (NewKey)
+		rr.key_tag = key_tag
 		if force then
 			rr.state = key_state.Valid
 		else
@@ -73,7 +71,7 @@ local function ta_present(keyset, rr, force)
 end
 
 -- TA is missing in the new key set
-local function ta_missing(keyset, ta)
+local function ta_missing(keyset, ta, hold_down_time)
 	-- Key is removed (KeyRem)
 	local keep_ta = true
 	local key_tag = C.kr_dnssec_key_tag(ta.type, ta.rdata, #ta.rdata)
@@ -89,24 +87,79 @@ local function ta_missing(keyset, ta)
 	return keep_ta
 end
 
+-- Plan refresh event and re-schedule itself based on the result of the callback
+local function refresh_plan(trust_anchors, timeout, refresh_cb)
+	if trust_anchors.refresh_ev ~= nil then event.cancel(trust_anchors.refresh_ev) end
+	trust_anchors.refresh_ev = event.after(timeout, function (ev)
+		worker.resolve('.', kres.type.DNSKEY, kres.class.IN, kres.query.NO_CACHE,
+		function (pkt)
+			-- Schedule itself with updated timeout
+			local next_time = refresh_cb(trust_anchors, kres.pkt_t(pkt))
+			next_time = math.min(next_time, trust_anchors.refresh_time)
+			print('[trust_anchors] next refresh: '..next_time)
+			refresh_plan(trust_anchors, next_time, refresh_cb)
+		end)
+	end)
+end
+
+-- Active refresh, return time of the next check
+local function active_refresh(trust_anchors, pkt)
+	local retry = true
+	if pkt:rcode() == kres.rcode.NOERROR then
+		local records = pkt:section(kres.section.ANSWER)
+		local keyset = {}
+		for i = 1, #records do
+			local rr = records[i]
+			if rr.type == kres.type.DNSKEY then
+				table.insert(keyset, rr)
+			end
+		end
+		trust_anchors.update(keyset, false)
+		retry = false
+	end
+	-- Calculate refresh/retry timer (RFC 5011, 2.3)
+	local min_ttl = retry and day or 15 * day
+	for i, rr in ipairs(trust_anchors.keyset) do -- 10 or 50% of the original TTL
+		min_ttl = math.min(min_ttl, (retry and 100 or 500) * rr.ttl)
+	end
+	return math.max(hour, min_ttl)
+end
+
+-- Write keyset to a file
+local function keyset_write(keyset, path)
+	local file = assert(io.open(path..'.lock', 'w'))
+	for i = 1, #keyset do
+		local ta = keyset[i]
+		local rr_str = string.format('%s ; %s\n', kres.rr2str(ta), ta.state)
+		if ta.state ~= key_state.Valid and ta.state ~= key_state.Missing then
+			rr_str = '; '..rr_str -- Invalidate key string
+		end
+		file:write(rr_str)
+	end
+	file:close()
+	os.rename(path..'.lock', path)
+end
+
 -- TA store management
 local trust_anchors = {
 	keyset = {},
 	insecure = {},
+	hold_down_time = 30 * day,
 	-- Update existing keyset
 	update = function (new_keys, initial)
 		if not new_keys then return false end
 		-- Filter TAs to be purged from the keyset (KeyRem)
+		local hold_down = trust_anchors.hold_down_time / 1000
 		local keyset_keep = {}
 		local keyset = trust_anchors.keyset
 		for i = 1, #keyset do
 			local ta = keyset[i]
 			local keep = true
 			if not ta_find(new_keys, ta) then
-				keep = ta_missing(keyset, ta)
+				keep = ta_missing(trust_anchors, keyset, ta, hold_down)
 			end
 			if keep then
-				table.insert(keyset_keep, rr)
+				table.insert(keyset_keep, ta)
 			end
 		end
 		keyset = keyset_keep
@@ -114,12 +167,13 @@ local trust_anchors = {
 		for i = 1, #new_keys do
 			local rr = new_keys[i]
 			if rr.type == kres.type.DNSKEY then
-				ta_present(keyset, rr, initial)
+				ta_present(keyset, rr, hold_down, initial)
 			end
 		end
 		-- Publish active TAs
 		local store = kres.context().trust_anchors
 		C.kr_ta_clear(store)
+		if #keyset == 0 then return false end
 		for i = 1, #keyset do
 			local ta = keyset[i]
 			-- Key MAY be used as a TA only in these two states (RFC5011, 4.2)
@@ -128,12 +182,21 @@ local trust_anchors = {
 			end
 		end
 		trust_anchors.keyset = keyset
+		-- Store keyset in the file
+		if trust_anchors.file_current ~= nil then
+			keyset_write(keyset, trust_anchors.file_current)
+		end
 		return true
 	end,
 	-- Load keys from a file (managed)
-	config = function (path)
+	config = function (path, is_unmanaged)
 		local new_keys = require('zonefile').parse_file(path)
-		trust_anchors.update(new_keys, true)
+		trust_anchors.file_current = path
+		if is_unmanaged then trust_anchors.file_current = nil end
+		trust_anchors.keyset = {}
+		if trust_anchors.update(new_keys, true) then
+			refresh_plan(trust_anchors, sec, active_refresh)
+		end
 	end,
 	-- Add DS/DNSKEY record(s) (unmanaged)
 	add = function (keystr)