diff --git a/src/knot/nameserver/update.c b/src/knot/nameserver/update.c
index c0db3fa601f95becf34d4f692c808964be60a1dd..0891d24421374e4d9a4b279ce12a9cf1f6b6f213 100644
--- a/src/knot/nameserver/update.c
+++ b/src/knot/nameserver/update.c
@@ -279,7 +279,7 @@ static int zones_process_update_auth(struct query_data *qdata)
 		sec_chs = knot_changesets_create();
 		sec_ch = knot_changesets_create_changeset(sec_chs);
 		if (sec_chs == NULL || sec_ch == NULL) {
-			xfrin_rollback_update(old_contents, &new_contents);
+			xfrin_rollback_update(zone, old_contents, &new_contents);
 			knot_changesets_free(&chgsets);
 			free(msg);
 			return KNOT_ENOMEM;
@@ -311,7 +311,7 @@ static int zones_process_update_auth(struct query_data *qdata)
 		if (ret != KNOT_EOK) {
 			log_zone_error("%s: Failed to sign incoming update (%s)"
 			               "\n", msg, knot_strerror(ret));
-			xfrin_rollback_update(old_contents, &new_contents);
+			xfrin_rollback_update(zone, old_contents, &new_contents);
 			knot_changesets_free(&chgsets);
 			knot_changesets_free(&sec_chs);
 			free(msg);
@@ -326,7 +326,7 @@ static int zones_process_update_auth(struct query_data *qdata)
 	if (ret != KNOT_EOK) {
 		log_zone_error("%s: Failed to save new entry to journal (%s)\n",
 		               msg, knot_strerror(ret));
-		xfrin_rollback_update(old_contents, &new_contents);
+		xfrin_rollback_update(zone, old_contents, &new_contents);
 		zones_free_merged_changesets(chgsets, sec_chs);
 		free(msg);
 		return ret;
@@ -335,7 +335,7 @@ static int zones_process_update_auth(struct query_data *qdata)
 	bool new_signatures = !knot_changeset_is_empty(sec_ch);
 	// Apply DNSSEC changeset
 	if (new_signatures) {
-		ret = xfrin_apply_changesets_dnssec_ddns(old_contents,
+		ret = xfrin_apply_changesets_dnssec_ddns(zone, old_contents,
 		                                    new_contents,
 		                                    sec_chs,
 		                                    chgsets);
@@ -363,7 +363,7 @@ static int zones_process_update_auth(struct query_data *qdata)
 		if (ret != KNOT_EOK) {
 			zones_store_changesets_rollback(transaction);
 			zones_free_merged_changesets(chgsets, sec_chs);
-			xfrin_rollback_update(old_contents, &new_contents);
+			xfrin_rollback_update(zone, old_contents, &new_contents);
 			free(msg);
 			return ret;
 		}
@@ -375,7 +375,7 @@ static int zones_process_update_auth(struct query_data *qdata)
 		if (ret != KNOT_EOK) {
 			log_zone_error("%s: Failed to commit new journal entry "
 			               "(%s).\n", msg, knot_strerror(ret));
-			xfrin_rollback_update(old_contents, &new_contents);
+			xfrin_rollback_update(zone, old_contents, &new_contents);
 			zones_free_merged_changesets(chgsets, sec_chs);
 			free(msg);
 			return ret;
@@ -391,7 +391,7 @@ static int zones_process_update_auth(struct query_data *qdata)
 		log_zone_error("%s: Failed to replace current zone (%s)\n",
 		               msg, knot_strerror(ret));
 		// Cleanup old and new contents
-		xfrin_rollback_update(old_contents, &new_contents);
+		xfrin_rollback_update(zone, old_contents, &new_contents);
 
 		/* Free changesets, but not the data. */
 		zones_free_merged_changesets(chgsets, sec_chs);
@@ -401,7 +401,7 @@ static int zones_process_update_auth(struct query_data *qdata)
 	}
 
 	// Cleanup.
-	xfrin_cleanup_successful_update(old_contents);
+	xfrin_cleanup_successful_update(zone);
 
 	// Free changesets, but not the data.
 	zones_free_merged_changesets(chgsets, sec_chs);
diff --git a/src/knot/server/zones.c b/src/knot/server/zones.c
index 4db77baabda3ca63792045d0128bb8d004a5b922..55ee7c0860d395415ad6ab59b8d32cebdc995102 100644
--- a/src/knot/server/zones.c
+++ b/src/knot/server/zones.c
@@ -1478,7 +1478,7 @@ int zones_store_and_apply_chgsets(knot_changesets_t *chs,
 	/* Commit transaction. */
 	ret = zones_store_changesets_commit(transaction);
 	if (ret != KNOT_EOK) {
-		xfrin_rollback_update(zone->contents, new_contents);
+		xfrin_rollback_update(zone, zone->contents, new_contents);
 		log_zone_error("%s Failed to commit stored changesets.\n", msgpref);
 		knot_changesets_free(&chs);
 		return ret;
@@ -1494,14 +1494,14 @@ int zones_store_and_apply_chgsets(knot_changesets_t *chs,
 	if (switch_ret != KNOT_EOK) {
 		log_zone_error("%s Failed to replace current zone.\n", msgpref);
 		// Cleanup old and new contents
-		xfrin_rollback_update(zone->contents, new_contents);
+		xfrin_rollback_update(zone, zone->contents, new_contents);
 
 		/* Free changesets, but not the data. */
 		knot_changesets_free(&chs);
 		return KNOT_ERROR;
 	}
 
-	xfrin_cleanup_successful_update(zone->contents);
+	xfrin_cleanup_successful_update(zone);
 
 	/* Free changesets, but not the data. */
 	knot_changesets_free(&chs);
@@ -1924,7 +1924,7 @@ int zones_journal_apply(zone_t *zone)
 							      XFR_TYPE_IIN);
 				rcu_read_lock();
 				if (apply_ret == KNOT_EOK) {
-					xfrin_cleanup_successful_update(NULL);
+					xfrin_cleanup_successful_update(zone);
 				} else {
 					log_zone_error("Failed to apply "
 					               "changesets to '%s' - Switch failed: "
@@ -1933,7 +1933,8 @@ int zones_journal_apply(zone_t *zone)
 					ret = KNOT_ERROR;
 
 					// Cleanup old and new contents
-					xfrin_rollback_update(zone->contents,
+					xfrin_rollback_update(zone,
+					                      zone->contents,
 					                      &contents);
 				}
 			}
@@ -2015,7 +2016,8 @@ static int diff_after_load(zone_t *zone, zone_t *old_zone,
 	if (*diff_chs != NULL) {
 		assert(!zones_changesets_empty(*diff_chs));
 		/* Apply DNSSEC changeset to the new zone. */
-		ret = xfrin_apply_changesets_directly(zone->contents,
+		ret = xfrin_apply_changesets_directly(zone,
+		                                      zone->contents,
 		                                      *diff_chs);
 
 		if (ret == KNOT_EOK) {
@@ -2033,7 +2035,7 @@ static int diff_after_load(zone_t *zone, zone_t *old_zone,
 			return ret;
 		}
 
-		xfrin_cleanup_successful_update(NULL);
+		xfrin_cleanup_successful_update(zone);
 		knot_changesets_free(diff_chs);
 		assert(zone->conf->build_diffs);
 	}
@@ -2103,7 +2105,7 @@ static int store_chgsets_after_load(zone_t *old_zone, zone_t *zone,
 		if (zone_changed) {
 			assert(!old_zone ||
 			       old_zone->contents != zone->contents);
-			ret = xfrin_apply_changesets_directly(zone->contents,
+			ret = xfrin_apply_changesets_directly(zone, zone->contents,
 			                                      diff_chs);
 			if (ret == KNOT_EOK) {
 				ret = xfrin_finalize_updated_zone(
@@ -2127,7 +2129,7 @@ static int store_chgsets_after_load(zone_t *old_zone, zone_t *zone,
 			return ret;
 		}
 
-		xfrin_cleanup_successful_update(NULL);
+		xfrin_cleanup_successful_update(zone);
 	}
 
 	/* Commit transaction. */
@@ -2156,7 +2158,7 @@ static int store_chgsets_after_load(zone_t *old_zone, zone_t *zone,
 			               "switching zone (%s).\n",
 			               zone->conf->name, knot_strerror(ret));
 			// Cleanup old and new contents
-			xfrin_rollback_update(zone->contents, &new_contents);
+			xfrin_rollback_update(zone, zone->contents, &new_contents);
 			return ret;
 		}
 
diff --git a/src/knot/updates/ddns.c b/src/knot/updates/ddns.c
index efe534e2fde1e329f34ba45757dce88314bbd962..a52336343000b3b8207b278800efa23440083887 100644
--- a/src/knot/updates/ddns.c
+++ b/src/knot/updates/ddns.c
@@ -433,7 +433,7 @@ static bool skip_record_addition(knot_changeset_t *changeset,
                                  knot_rrset_t *rr)
 {
 	knot_rr_ln_t *rr_node = NULL;
-	WALK_LIST(rr_node, changeset->remove) {
+	WALK_LIST(rr_node, changeset->add) {
 		knot_rrset_t *rrset = rr_node->rr;
 		if (should_replace(rr, rrset)) {
 			knot_rrset_free(&rrset, NULL);
@@ -795,6 +795,11 @@ static int knot_ddns_process_rr(const knot_rrset_t *rr,
 	}
 }
 
+/*
+ * Check if the record is SOA. If yes, check the SERIAL.
+ * If this record should cause the SOA to be replaced in the
+ * zone, use it as the ending SOA.
+ */
 static bool skip_soa(const knot_rrset_t *rr, int64_t sn)
 {
 	if (rr->type == KNOT_RRTYPE_SOA
@@ -828,19 +833,11 @@ int knot_ddns_process_update(knot_zone_contents_t *zone,
 		return KNOT_EINVAL;
 	}
 
-	int ret = KNOT_EOK;
-
 	/* Copy base SOA RR. */
-	knot_rrset_t *soa_begin = knot_node_create_rrset(knot_zone_contents_apex(zone),
+	knot_rrset_t *soa_begin = knot_node_create_rrset(zone->apex,
 	                                                 KNOT_RRTYPE_SOA);
 	knot_rrset_t *soa_end = NULL;
-	if (ret == KNOT_EOK) {
-		knot_changeset_add_soa(changeset, soa_begin,
-		                       KNOT_CHANGESET_REMOVE);
-	} else {
-		*rcode = KNOT_RCODE_SERVFAIL;
-		return ret;
-	}
+	knot_changeset_add_soa(changeset, soa_begin, KNOT_CHANGESET_REMOVE);
 
 	/* Current SERIAL */
 	int64_t sn = knot_rrs_soa_serial(&soa_begin->rrs);
@@ -852,48 +849,39 @@ int knot_ddns_process_update(knot_zone_contents_t *zone,
 		assert(sn_new != KNOT_EINVAL);
 	} else {
 		*rcode = KNOT_RCODE_SERVFAIL;
-		return ret;
+		return KNOT_EINVAL;
 	}
 
 	/* Process all RRs the Authority (Update) section. */
 
-	const knot_rrset_t *rr = NULL;
-
 	dbg_ddns("Processing UPDATE section.\n");
 	size_t apex_ns_removals = 0;
 	const knot_pktsection_t *authority = knot_pkt_section(query, KNOT_AUTHORITY);
-	for (int i = 0; i < authority->count; ++i) {
-
-		rr = &authority->rr[i];
+	for (uint16_t i = 0; i < authority->count; ++i) {
+		const knot_rrset_t *rr = &authority->rr[i];
 
 		/* Check if the entry is correct. */
-		ret = knot_ddns_check_update(rr, query, rcode);
+		int ret = knot_ddns_check_update(rr, query, rcode);
 		if (ret != KNOT_EOK) {
 			return ret;
 		}
 
-		/* Check if the record is SOA. If yes, check the SERIAL.
-		 * If this record should cause the SOA to be replaced in the
-		 * zone, use it as the ending SOA.
-		 *
-		 * Also handle cases where there are multiple SOAs to be added
-		 * in the same UPDATE. The one with the largest SERIAL should
-		 * be used.
-		 */
 		1 == 1; // multiple SOAs test
 		if (skip_soa(rr, sn)) {
 			continue;
 		}
 
-		dbg_ddns_verb("Processing RR %p...\n", rr);
 		ret = knot_ddns_process_rr(rr, zone, changeset, &apex_ns_removals);
 		if (ret != KNOT_EOK) {
 			*rcode = ret_to_rcode(ret);
 			return ret;
 		}
 
-		// we need the RR copy, that's why this code is here
 		if (rr->type == KNOT_RRTYPE_SOA) {
+			// Using new SOA that came in the update
+			if (soa_end == NULL) {
+				knot_rrset_free(&soa_end, NULL);
+			}
 			int64_t sn_rr = knot_rrs_soa_serial(&rr->rrs);
 			assert(knot_serial_compare(sn_rr, sn) > 0);
 			sn_new = sn_rr;
@@ -904,23 +892,20 @@ int knot_ddns_process_update(knot_zone_contents_t *zone,
 		}
 	}
 
-	/* Ending SOA (not in the UPDATE) */
 	if (soa_end == NULL) {
-		// If the changeset is empty, do not process anything further
+		// No SOA in the update, create according to current policy
 		if (knot_changeset_is_empty(changeset)) {
+			// No change, no new SOA
 			return KNOT_EOK;
 		}
 
-		/* If not set, create new SOA. */
-		ret = knot_rrset_copy(soa_begin, &soa_end, NULL);
-		if (ret != KNOT_EOK) {
-			*rcode = ret_to_rcode(ret);
-			return ret;
+		soa_end = knot_rrset_cpy(soa_begin, NULL);
+		if (soa_end == NULL) {
+			*rcode = KNOT_RCODE_SERVFAIL;
+			return KNOT_ENOMEM;
 		}
 		knot_rrs_soa_serial_set(&soa_end->rrs, sn_new);
 	}
 
-	ret = knot_ddns_final_soa_to_chgset(soa_end, changeset);
-
-	return ret;
+	return knot_ddns_final_soa_to_chgset(soa_end, changeset);
 }
diff --git a/src/knot/updates/xfr-in.c b/src/knot/updates/xfr-in.c
index b287c500a6119d87dabb9bb4e9f39d493bcfbf69..511ba5ae32bdf4a76d2619ba4f42430bfb9fc78e 100644
--- a/src/knot/updates/xfr-in.c
+++ b/src/knot/updates/xfr-in.c
@@ -628,7 +628,7 @@ void xfrin_zone_contents_free(knot_zone_contents_t **contents)
 
 /*----------------------------------------------------------------------------*/
 
-void xfrin_cleanup_successful_update(knot_zone_contents_t *zone)
+void xfrin_cleanup_successful_update(zone_t *zone)
 {
 	if (zone == NULL) {
 		return;
@@ -636,6 +636,8 @@ void xfrin_cleanup_successful_update(knot_zone_contents_t *zone)
 
 	rrs_list_clear(&zone->old_data, NULL);
 	ptrlist_free(&zone->new_data, NULL);
+	init_list(&zone->new_data);
+	init_list(&zone->old_data);
 }
 
 /*----------------------------------------------------------------------------*/
@@ -729,11 +731,14 @@ static void xfrin_cleanup_failed_update(knot_zone_contents_t *old_contents,
 
 /*----------------------------------------------------------------------------*/
 
-void xfrin_rollback_update(knot_zone_contents_t *old_contents,
+void xfrin_rollback_update(zone_t *zone,
+                           knot_zone_contents_t *old_contents,
                            knot_zone_contents_t **new_contents)
 {
-	rrs_list_clear(&old_contents->new_data, NULL);
-	ptrlist_free(&old_contents->old_data, NULL);
+	rrs_list_clear(&zone->new_data, NULL);
+	ptrlist_free(&zone->old_data, NULL);
+	init_list(&zone->new_data);
+	init_list(&zone->old_data);
 	xfrin_cleanup_failed_update(old_contents, new_contents);
 }
 
@@ -937,8 +942,6 @@ static int xfrin_apply_changeset(list_t *old_rrs, list_t *new_rrs,
 		return KNOT_ERROR;
 	}
 
-	init_list(new_rrs);
-	init_list(old_rrs);
 	int ret = xfrin_apply_remove(contents, chset, old_rrs, new_rrs);
 	if (ret != KNOT_EOK) {
 		return ret;
@@ -1160,7 +1163,8 @@ int xfrin_finalize_updated_zone(knot_zone_contents_t *contents_copy,
 
 /*----------------------------------------------------------------------------*/
 
-int xfrin_apply_changesets_directly(knot_zone_contents_t *contents,
+int xfrin_apply_changesets_directly(zone_t *zone,
+                                    knot_zone_contents_t *contents,
                                     knot_changesets_t *chsets)
 {
 	if (contents == NULL || chsets == NULL) {
@@ -1169,8 +1173,8 @@ int xfrin_apply_changesets_directly(knot_zone_contents_t *contents,
 
 	knot_changeset_t *set = NULL;
 	WALK_LIST(set, chsets->sets) {
-		int ret = xfrin_apply_changeset(&contents->old_data,
-		                                &contents->new_data,
+		int ret = xfrin_apply_changeset(&zone->old_data,
+		                                &zone->new_data,
 		                                contents, set);
 		if (ret != KNOT_EOK) {
 			return ret;
@@ -1183,12 +1187,13 @@ int xfrin_apply_changesets_directly(knot_zone_contents_t *contents,
 /*----------------------------------------------------------------------------*/
 
 /* Post-DDNS application, no need to shallow copy. */
-int xfrin_apply_changesets_dnssec_ddns(knot_zone_contents_t *z_old,
+int xfrin_apply_changesets_dnssec_ddns(zone_t *zone,
+                                       knot_zone_contents_t *z_old,
                                        knot_zone_contents_t *z_new,
                                        knot_changesets_t *sec_chsets,
                                        knot_changesets_t *chsets)
 {
-	if (z_old == NULL || z_new == NULL ||
+	if (zone == NULL || z_old == NULL || z_new == NULL ||
 	    sec_chsets == NULL || chsets == NULL) {
 		return KNOT_EINVAL;
 	}
@@ -1197,10 +1202,10 @@ int xfrin_apply_changesets_dnssec_ddns(knot_zone_contents_t *z_old,
 	knot_zone_contents_set_gen_old(z_new);
 
 	/* Apply changes. */
-	int ret = xfrin_apply_changesets_directly(z_new,
+	int ret = xfrin_apply_changesets_directly(zone, z_new,
 	                                          sec_chsets);
 	if (ret != KNOT_EOK) {
-		xfrin_rollback_update(z_old, &z_new);
+		xfrin_rollback_update(zone, z_old, &z_new);
 		dbg_xfrin("Failed to apply changesets to zone: "
 		          "%s\n", knot_strerror(ret));
 		return ret;
@@ -1211,7 +1216,7 @@ int xfrin_apply_changesets_dnssec_ddns(knot_zone_contents_t *z_old,
 	if (ret != KNOT_EOK) {
 		dbg_xfrin("Failed to finalize updated zone: %s\n",
 		          knot_strerror(ret));
-		xfrin_rollback_update(z_old, &z_new);
+		xfrin_rollback_update(zone, z_old, &z_new);
 		return ret;
 	}
 
@@ -1254,12 +1259,12 @@ int xfrin_apply_changesets(zone_t *zone,
 		       old_contents->apex, contents_copy->apex);
 	knot_changeset_t *set = NULL;
 	WALK_LIST(set, chsets->sets) {
-		ret = xfrin_apply_changeset(&zone->contents->old_data,
-		                            &zone->contents->new_data,
+		ret = xfrin_apply_changeset(&zone->old_data,
+		                            &zone->new_data,
 		                            contents_copy, set);
 		if (ret != KNOT_EOK) {
-			xfrin_rollback_update(old_contents,
-					       &contents_copy);
+			xfrin_rollback_update(zone, old_contents,
+			                      &contents_copy);
 			dbg_xfrin("Failed to apply changesets to zone: "
 				  "%s\n", knot_strerror(ret));
 			return ret;
@@ -1276,7 +1281,7 @@ int xfrin_apply_changesets(zone_t *zone,
 	if (ret != KNOT_EOK) {
 		dbg_xfrin("Failed to finalize updated zone: %s\n",
 			  knot_strerror(ret));
-		xfrin_rollback_update(old_contents, &contents_copy);
+		xfrin_rollback_update(zone, old_contents, &contents_copy);
 		return ret;
 	}
 
diff --git a/src/knot/updates/xfr-in.h b/src/knot/updates/xfr-in.h
index 4f2425f7eca872a4dedc513cbdc25c4782a85a48..35651e95921c28f9fc1c7f242fe5558b2ca8745c 100644
--- a/src/knot/updates/xfr-in.h
+++ b/src/knot/updates/xfr-in.h
@@ -165,7 +165,7 @@ int xfrin_apply_changesets(zone_t *zone,
  * by the UPDATE-processing function. It uses new and old zones from this
  * operation.
  */
-int xfrin_apply_changesets_dnssec_ddns(knot_zone_contents_t *z_old,
+int xfrin_apply_changesets_dnssec_ddns(zone_t *zone, knot_zone_contents_t *z_old,
                                        knot_zone_contents_t *z_new,
                                        knot_changesets_t *sec_chsets,
                                        knot_changesets_t *chsets);
@@ -183,7 +183,7 @@ int xfrin_apply_changesets_dnssec_ddns(knot_zone_contents_t *z_old,
  * \retval KNOT_EINVAL if given one of the arguments is NULL.
  * \return Other error code if the application went wrong.
  */
-int xfrin_apply_changesets_directly(knot_zone_contents_t *contents,
+int xfrin_apply_changesets_directly(zone_t *zone, knot_zone_contents_t *contents,
                                     knot_changesets_t *chsets);
 
 int xfrin_prepare_zone_copy(knot_zone_contents_t *old_contents,
@@ -202,7 +202,7 @@ int xfrin_switch_zone(zone_t *zone,
                       knot_zone_contents_t *new_contents,
                       int transfer_type);
 
-void xfrin_rollback_update(knot_zone_contents_t *old_contents,
+void xfrin_rollback_update(zone_t *zone, knot_zone_contents_t *old_contents,
                            knot_zone_contents_t **new_contents);
 
 int xfrin_copy_rrset(knot_node_t *node, uint16_t type,
@@ -215,7 +215,7 @@ int xfrin_replace_rrset_in_node(knot_node_t *node,
                                 knot_zone_contents_t *contents);
 
 void xfrin_zone_contents_free(knot_zone_contents_t **contents);
-void xfrin_cleanup_successful_update(knot_zone_contents_t *zone);
+void xfrin_cleanup_successful_update(zone_t *zone);
 
 #endif /* _KNOTXFR_IN_H_ */
 
diff --git a/src/knot/zone/zone-contents.h b/src/knot/zone/zone-contents.h
index ca953bde7a7f6f86b303325dbcbe96fcaad44112..fae0bb2f4fe792baf1346442c75aa82fc4cb121c 100644
--- a/src/knot/zone/zone-contents.h
+++ b/src/knot/zone/zone-contents.h
@@ -70,8 +70,6 @@ typedef struct knot_zone_contents_t {
 	 * - 0xx - ANY queries enabled
 	 */
 	uint8_t flags;
-	list_t old_data;
-	list_t new_data;
 } knot_zone_contents_t;
 
 /*!< \brief Helper linked list list for CNAME loop checking */
diff --git a/src/knot/zone/zone.c b/src/knot/zone/zone.c
index 09f6ffa3363a77178e1635dc4e337b37da361192..025b9b7c11edd1eba659ed91f7836422a994f61d 100644
--- a/src/knot/zone/zone.c
+++ b/src/knot/zone/zone.c
@@ -212,6 +212,9 @@ zone_t* zone_new(conf_zone_t *conf)
 		}
 	}
 
+	init_list(&zone->old_data);
+	init_list(&zone->new_data);
+
 	return zone;
 }
 
diff --git a/src/knot/zone/zone.h b/src/knot/zone/zone.h
index dac7feed11bec03937c60ff98deedbda201af876..79a6d5aa43378878dafa7d6bcc76995eb2c340bc 100644
--- a/src/knot/zone/zone.h
+++ b/src/knot/zone/zone.h
@@ -95,6 +95,8 @@ typedef struct zone_t {
 	/*! \brief Zone IXFR history. */
 	journal_t *ixfr_db;
 	event_t *ixfr_dbsync;   /*!< Syncing IXFR db to zonefile. */
+	list_t old_data;
+	list_t new_data;
 } zone_t;
 
 /*----------------------------------------------------------------------------*/