diff --git a/src/knot/catalog/interpret.c b/src/knot/catalog/interpret.c
index b70bd65fc51210989021c2150170891110aedb3c..622b4f0c6deca5b4ccdfe04a2f9f46a000afcefa 100644
--- a/src/knot/catalog/interpret.c
+++ b/src/knot/catalog/interpret.c
@@ -28,6 +28,12 @@ typedef struct {
 	catalog_t *check;
 } cat_upd_ctx_t;
 
+static bool label_eq(const knot_dname_t *a, const char *_b)
+{
+	const knot_dname_t *b = (const knot_dname_t *)_b;
+	return a[0] == b[0] && memcmp(a + 1, b + 1, a[0]) == 0;
+}
+
 static bool check_zone_version(const zone_contents_t *zone)
 {
 	size_t zone_size = knot_dname_size(zone->apex->owner);
@@ -126,8 +132,7 @@ static int cat_update_add_node(zone_node_t *node, void *data)
 		return cat_update_add_memb(node->owner, ptr, ctx);
 	}
 	if (labels_diff == 1 && txt != NULL && txt->count == 1 &&
-	    node->owner[0] == CATALOG_GROUP_LABEL[0] &&
-	    memcmp(node->owner, CATALOG_GROUP_LABEL, CATALOG_GROUP_LABEL[0] + 1) == 0) {
+	    label_eq(node->owner, CATALOG_GROUP_LABEL)) {
 		const knot_dname_t *own = NULL;
 		const knot_dname_t  *memb = property_get_member(node, ctx->complete_conts, &own);
 		return cat_update_add_grp(memb, own, txt, ctx);
@@ -137,13 +142,8 @@ static int cat_update_add_node(zone_node_t *node, void *data)
 
 int catalog_update_from_zone(catalog_update_t *u, struct zone_contents *zone,
                              const struct zone_contents *complete_contents,
-                             bool remove, bool check_ver, catalog_t *check,
-                             ssize_t *upd_count)
+                             bool remove, catalog_t *check, ssize_t *upd_count)
 {
-	if (check_ver && !check_zone_version(zone)) {
-		return KNOT_EZONEINVAL;
-	}
-
 	knot_dname_storage_t sub;
 	if (knot_dname_store(sub, (uint8_t *)CATALOG_ZONES_LABEL) == 0 ||
 	    catalog_dname_append(sub, zone->apex->owner) == 0) {
@@ -163,3 +163,46 @@ int catalog_update_from_zone(catalog_update_t *u, struct zone_contents *zone,
 	pthread_mutex_unlock(&u->mutex);
 	return ret;
 }
+
+static int rr_count(const zone_node_t *node, uint16_t type)
+{
+	const knot_rdataset_t *rd = node_rdataset(node, type);
+	return rd == NULL ? 0 : rd->count;
+}
+
+static int cat_node_verify(zone_node_t *node, void *data)
+{
+	cat_upd_ctx_t *ctx = data;
+	int labels_diff = knot_dname_labels(node->owner, NULL) - ctx->apex_labels - 2;
+
+	if (labels_diff == 0 && rr_count(node, KNOT_RRTYPE_PTR) > 1) {
+		return KNOT_EISRECORD;
+	}
+
+	if (labels_diff == 1 && label_eq(node->owner, CATALOG_GROUP_LABEL) &&
+	    rr_count(node, KNOT_RRTYPE_TXT) > 1) {
+		return KNOT_EISRECORD;
+	}
+
+	return KNOT_EOK;
+}
+
+int catalog_zone_verify(const struct zone_contents *zone)
+{
+	if (!check_zone_version(zone)) {
+		return KNOT_EZONEINVAL;
+	}
+
+	knot_dname_storage_t sub;
+	if (knot_dname_store(sub, (uint8_t *)CATALOG_ZONES_LABEL) == 0 ||
+	    catalog_dname_append(sub, zone->apex->owner) == 0) {
+		return KNOT_EINVAL;
+	}
+
+	if (zone_contents_find_node(zone, sub) == NULL) {
+		return KNOT_EOK;
+	}
+
+	cat_upd_ctx_t ctx = { NULL, zone, knot_dname_labels(zone->apex->owner, NULL), false, NULL };
+	return zone_tree_sub_apply(zone->nodes, sub, true, cat_node_verify, &ctx);
+}
diff --git a/src/knot/catalog/interpret.h b/src/knot/catalog/interpret.h
index 6d640312f823f37decb9d4a3ca5f45cd76802979..76ea1386e769ce818d99ad1ac90712a024e86641 100644
--- a/src/knot/catalog/interpret.h
+++ b/src/knot/catalog/interpret.h
@@ -20,6 +20,17 @@
 
 struct zone_contents;
 
+/*!
+ * \brief Validate if given zone is valid catalog.
+ *
+ * \param zone   Catalog zone in question.
+ *
+ * \retval KNOT_EZONEINVAL   Invalid varsion record.
+ * \retval KNOT_EISRECORD    Some of single-record RRSets has multiple RRs.
+ * \return KNOT_EOK          All OK.
+ */
+int catalog_zone_verify(const struct zone_contents *zone);
+
 /*!
  * \brief Iterate over PTR records in given zone contents and add members to catalog update.
  *
@@ -27,7 +38,6 @@ struct zone_contents;
  * \param zone               Zone contents to be searched for member PTR records.
  * \param complete_contents  Complete zone contents (zone might be from a changeset).
  * \param remove             Add removals of found member zones.
- * \param check_ver          Do check catalog zone version record first.
  * \param check              Optional: existing catalog database to be checked for existence
  *                           of such record (useful for removals).
  * \param upd_count          Output: number of resulting updates to catalog database.
@@ -36,5 +46,4 @@ struct zone_contents;
  */
 int catalog_update_from_zone(catalog_update_t *u, struct zone_contents *zone,
                              const struct zone_contents *complete_contents,
-                             bool remove, bool check_ver, catalog_t *check,
-                             ssize_t *upd_count);
+                             bool remove, catalog_t *check, ssize_t *upd_count);
diff --git a/src/knot/updates/zone-update.c b/src/knot/updates/zone-update.c
index 31ae6073117620661a8cffe3d3903dd5cabee9e3..2e51fc3f03e7cff2cb024cd0a681c143853043bf 100644
--- a/src/knot/updates/zone-update.c
+++ b/src/knot/updates/zone-update.c
@@ -719,16 +719,20 @@ static int update_catalog(conf_t *conf, zone_update_t *update)
 
 	zone_set_flag(update->zone, ZONE_IS_CATALOG);
 
-	int ret = KNOT_EOK;
+	int ret = catalog_zone_verify(update->new_cont);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+
 	ssize_t upd_count = 0;
 	if ((update->flags & UPDATE_INCREMENTAL)) {
 		ret = catalog_update_from_zone(zone_catalog_upd(update->zone),
 		                               update->change.remove, update->new_cont,
-		                               true, false, zone_catalog(update->zone), &upd_count);
+		                               true, zone_catalog(update->zone), &upd_count);
 		if (ret == KNOT_EOK) {
 			ret = catalog_update_from_zone(zone_catalog_upd(update->zone),
 			                               update->change.add, update->new_cont,
-			                               false, false, NULL, &upd_count);
+			                               false, NULL, &upd_count);
 		}
 	} else {
 		ret = catalog_update_del_all(zone_catalog_upd(update->zone),
@@ -737,7 +741,7 @@ static int update_catalog(conf_t *conf, zone_update_t *update)
 		if (ret == KNOT_EOK) {
 			ret = catalog_update_from_zone(zone_catalog_upd(update->zone),
 			                               update->new_cont, update->new_cont,
-			                               false, true, NULL, &upd_count);
+			                               false, NULL, &upd_count);
 		}
 	}
 
@@ -746,6 +750,11 @@ static int update_catalog(conf_t *conf, zone_update_t *update)
 		if (kill(getpid(), SIGUSR1) != 0) {
 			ret = knot_map_errno();
 		}
+	} else {
+		// this cant normally happen, just some ENOMEM or so
+		(void)catalog_update_del_all(zone_catalog_upd(update->zone),
+		                             zone_catalog(update->zone),
+		                             update->zone->name, &upd_count);
 	}
 
 	return ret;
diff --git a/tests-extra/tests/catalog/basic/test.py b/tests-extra/tests/catalog/basic/test.py
index 80f6abbf31ffd77ea78d2d7fd269bda298676542..74f1760ffd5227ef10c6d7db8c070c26adb5c1ac 100644
--- a/tests-extra/tests/catalog/basic/test.py
+++ b/tests-extra/tests/catalog/basic/test.py
@@ -156,6 +156,15 @@ resp = slave.dig("cataloged2.", "SOA", dnssec=True)
 resp.check(rcode="NOERROR")
 check_keys(slave, "cataloged2", 2)
 
+# Check adding two-RR member PTR
+up = master.update(zone[1])
+up.add("bar2.zones.catalog1.", 0, "PTR", "catalogedx.")
+up.add("bar4.zones.catalog1.", 0, "PTR", "catalogedy.")
+up.send("SERVFAIL")
+t.sleep(6)
+resp = master.dig("catalogedy.", "SOA")
+resp.check(rcode="REFUSED")
+
 master.ctl("zone-backup +journal +backupdir %s/backup %s" % (master.dir, zone[1].name))
 # Check removing cataloged zone
 up = master.update(zone[1])