diff --git a/src/knot/nameserver/update.c b/src/knot/nameserver/update.c
index 4ecf54a03dea4764d30e9745696345ee195d42a0..99f1d71721f92b0c919031d12f493eeba127d53b 100644
--- a/src/knot/nameserver/update.c
+++ b/src/knot/nameserver/update.c
@@ -10,6 +10,10 @@
 #include "libknot/tsig-op.h"
 #include "knot/zone/zone.h"
 
+/* UPDATE-specific logging (internal, expects 'qdata' variable set). */
+#define UPDATE_LOG(severity, msg...) \
+	QUERY_LOG(severity, qdata, "UPDATE", msg)
+
 #warning merge file with ddns.c
 
 static int update_forward(knot_pkt_t *pkt, struct query_data *qdata)
@@ -185,8 +189,14 @@ static int sign_update(zone_t *zone, const zone_contents_t *old_contents,
 	return ret;
 }
 
-int process_ddns_pkt(zone_t *zone, const knot_pkt_t *query, uint16_t *rcode)
+static int process_authenticated(uint16_t *rcode, struct query_data *qdata)
 {
+	assert(rcode);
+	assert(qdata);
+
+	const knot_pkt_t *query = qdata->query;
+	zone_t *zone = (zone_t *)qdata->zone;
+
 	int ret = ddns_process_prereqs(query, zone->contents, rcode);
 	if (ret != KNOT_EOK) {
 		return ret;
@@ -265,4 +275,40 @@ int process_ddns_pkt(zone_t *zone, const knot_pkt_t *query, uint16_t *rcode)
 	return ret;
 }
 
+
+int update_process_query(knot_pkt_t *pkt, struct query_data *qdata)
+{
+	assert(pkt);
+	assert(qdata);
+
+	UPDATE_LOG(LOG_INFO, "Started.");
+
+	/* Keep original state. */
+	struct timeval t_start, t_end;
+	gettimeofday(&t_start, NULL);
+	const zone_t *zone = qdata->zone;
+	const uint32_t old_serial = zone_contents_serial(zone->contents);
+
+	/* Process authenticated packet. */
+	uint16_t rcode = KNOT_RCODE_NOERROR;
+	int ret = process_authenticated(&rcode, qdata);
+	if (ret != KNOT_EOK) {
+		UPDATE_LOG(LOG_ERR, "%s\n", knot_strerror(ret));
+		knot_wire_set_rcode(pkt->wire, rcode);
+		return ret;
+	}
+
+	/* Evaluate response. */
+	const uint32_t new_serial = zone_contents_serial(zone->contents);
+	if (new_serial == old_serial) {
+		UPDATE_LOG(LOG_NOTICE, "No change to zone made.");
+		return KNOT_EOK;
+	}
+
+	gettimeofday(&t_end, NULL);
+	UPDATE_LOG(LOG_INFO, "Serial %u -> %u\n", old_serial, new_serial);
+	UPDATE_LOG(LOG_INFO, "Finished in %.02fs.", time_diff(&t_start, &t_end) / 1000.0);
+	return KNOT_EOK;
+}
+
 #undef UPDATE_LOG
diff --git a/src/knot/nameserver/update.h b/src/knot/nameserver/update.h
index 08628e08cef2fe72bcd2893e8bc52546309e613b..2f4acf5abf54a1a71001b7cf33ca1402c193c123 100644
--- a/src/knot/nameserver/update.h
+++ b/src/knot/nameserver/update.h
@@ -41,18 +41,16 @@ struct query_data;
 int update_answer(knot_pkt_t *pkt, struct query_data *qdata);
 
 
-/*! \brief Process already authenticated packet. */
 /*!
  * \brief Processes serialized packet with DDNS. Function expects that the
  *        query is already authenticated and TSIG signature is verified.
  *
- * \param zone   Zone to be updated.
- * \param query  Packet containing DDNS.
- * \param rcode  Output response code.
+ * \param pkt    Prepared response packet.
+ * \param qdata  Minimal query data context.
  *
  * \return KNOT_E*
  */
-int process_ddns_pkt(zone_t *zone, const knot_pkt_t *query, uint16_t *rcode);
+int update_process_query(knot_pkt_t *pkt, struct query_data *qdata);
 
 #endif /* _KNOT_UPDATE_H_ */
 
diff --git a/src/knot/zone/events.c b/src/knot/zone/events.c
index d8ea2047de026c170172687464701df2e11b89a8..3e98c690b86dff591ed959e9b8fb771e95c0edd1 100644
--- a/src/knot/zone/events.c
+++ b/src/knot/zone/events.c
@@ -309,53 +309,39 @@ static int event_xfer(zone_t *zone)
 	return ret;
 }
 
-static knot_pkt_t *create_ddns_resp(const knot_pkt_t *query, uint16_t rcode)
-{
-	knot_pkt_t *resp = knot_pkt_new(NULL, KNOT_WIRE_MAX_PKTSIZE, NULL);
-	if (resp == KNOT_EOK) {
-		return NULL;
-	}
-
-	int ret = knot_pkt_init_response(resp, query);
-	if (ret != KNOT_EOK) {
-		knot_pkt_free(&resp);
-	}
-
-	knot_wire_set_rcode(resp->wire, rcode);
-	return resp;
-}
-
 static int event_update(zone_t *zone)
 {
 	assert(zone);
-	fprintf(stderr, "UPDATE of '%s' started.\n", zone->conf->name);
 
-	struct request_data *update = zone_update_dequeue(zone);
-	assert(update);
-	uint16_t rcode = KNOT_RCODE_NOERROR;
-	const uint32_t old_serial = zone_contents_serial(zone->contents);
-	int ret = process_ddns_pkt(zone, update->query, &rcode);
-	const uint32_t new_serial = zone_contents_serial(zone->contents);
-
-	if (old_serial != new_serial) {
-		fprintf(stderr, "UPDATE of '%s': finished: %s\n.",
-		        zone->conf->name, knot_strerror(ret));
-	} else {
-		fprintf(stderr, "UPDATE of '%s': No change to zone made.\n",
-		        zone->conf->name);
-	}
-
-	knot_pkt_t *resp = create_ddns_resp(update->query, rcode);
+	knot_pkt_t *resp = knot_pkt_new(NULL, KNOT_WIRE_MAX_PKTSIZE, NULL);
 	if (resp == NULL) {
-		knot_pkt_free(&resp);
-		knot_pkt_free(&update->query);
 		return KNOT_ENOMEM;
 	}
 
-	// Send response to knsupdate.
+	struct request_data *update = zone_update_dequeue(zone);
+
+	/* Initialize query response. */
+	assert(update);
+	assert(update->query);
+	knot_pkt_init_response(resp, update->query);
+
+	/* Create minimal query data context. */
+	struct process_query_param param = {0};
+	param.remote = &update->remote;
+	struct query_data qdata = {0};
+	qdata.param = &param;
+	qdata.query = update->query;
+	qdata.zone  = zone;
+
+	/* Process the update query. */
+	int ret = update_process_query(resp, &qdata);
+
+	/* Send response. */
+#warning TODO: proper API for this
 	sendto(update->fd, resp->wire, resp->size, 0,
-	       (struct sockaddr *)update->remote, sockaddr_len(update->remote));
+	       (struct sockaddr *)param.remote, sockaddr_len(param.remote));
 
+	/* Cleanup. */
 	knot_pkt_free(&resp);
 	knot_pkt_free(&update->query);
 	free(update);
diff --git a/src/knot/zone/zone.c b/src/knot/zone/zone.c
index 8abae45391009da7e936e691cb39d8b775b70c17..fee200dac7dfb544848ee4a73914174b4008c2f1 100644
--- a/src/knot/zone/zone.c
+++ b/src/knot/zone/zone.c
@@ -319,7 +319,7 @@ int zone_update_enqueue(zone_t *zone, knot_pkt_t *pkt, struct process_query_para
 
 	memset(req, 0, sizeof(struct request_data));
 	req->fd = param->socket;
-	req->remote = param->remote;
+	memcpy(&req->remote, param->remote, sizeof(struct sockaddr_storage));
 	req->query = knot_pkt_copy(pkt, NULL);
 	if (req->query == NULL) {
 		free(req);