diff --git a/daemon/worker.c b/daemon/worker.c
index fd2419caffff1e07a0c5565f1c7c70ee6f915d01..c25f35aaddb019c4f92ca00cf7dafd12539bfdd6 100644
--- a/daemon/worker.c
+++ b/daemon/worker.c
@@ -361,6 +361,34 @@ static int request_start(struct request_ctx *ctx, knot_pkt_t *query)
 	/* Remember query source EDNS data */
 	if (query->opt_rr) {
 		req->qsource.opt = knot_rrset_copy(query->opt_rr, &req->pool);
+		/* Client may explicitly ask to keep
+		 * connection alive after getting answer.
+		 * Although rfc7828 doesn't require us to send
+		 * this edns option back to the client
+		 * (https://tools.ietf.org/html/rfc7828#section-3.3.2
+		 * states "MAY include") we are going to do it.
+		 */
+		if (knot_edns_get_option(query->opt_rr,
+					 KNOT_EDNS_OPTION_TCP_KEEPALIVE)) {
+			knot_mm_t *pool = &req->answer->mm;
+			knot_rrset_t *opt_rr = mm_alloc(pool, sizeof(knot_rrset_t));
+			if (!opt_rr) {
+				return kr_error(ENOMEM);
+			}
+			struct engine *engine = ctx->worker->engine;
+			struct network *net = &engine->net;
+			uint64_t timeout = net->tcp.in_idle_timeout / 100;
+			if (timeout > UINT16_MAX) {
+				timeout = UINT16_MAX;
+			}
+			uint16_t ka_size = knot_edns_keepalive_size(timeout);
+			uint8_t ka_buf[ka_size];
+			knot_edns_keepalive_write(ka_buf, ka_size, timeout);
+			knot_edns_init(opt_rr, KR_EDNS_PAYLOAD, 0, KR_EDNS_VERSION, pool);
+			knot_edns_add_option(opt_rr, KNOT_EDNS_OPTION_TCP_KEEPALIVE,
+					     ka_size, ka_buf, pool);
+			req->answer->opt_rr = opt_rr;
+		}
 	}
 	/* Start resolution */
 	struct worker_ctx *worker = ctx->worker;
diff --git a/lib/resolve.c b/lib/resolve.c
index fd067520e91399a003f70555a33da1187205a282..bc557c610a7c54949aa8fd5d9856630452c405d0 100644
--- a/lib/resolve.c
+++ b/lib/resolve.c
@@ -424,9 +424,16 @@ static int edns_erase_and_reserve(knot_pkt_t *pkt)
 	return knot_pkt_reserve(pkt, len);
 }
 
-static int edns_create(knot_pkt_t *pkt, knot_pkt_t *template, struct kr_request *req)
+static int edns_create(knot_pkt_t *pkt, knot_rrset_t *additional, struct kr_request *req)
 {
 	pkt->opt_rr = knot_rrset_copy(req->ctx->opt_rr, &pkt->mm);
+	if (additional) {
+		int ret = kr_edns_append(pkt->opt_rr, additional->rrs.rdata, &pkt->mm);
+		if (ret != 0) {
+			return ret;
+		}
+	}
+
 	size_t wire_size = knot_edns_wire_size(pkt->opt_rr);
 #if defined(ENABLE_COOKIES)
 	if (req->ctx->cookie_ctx.clnt.enabled ||
@@ -448,12 +455,16 @@ static int edns_create(knot_pkt_t *pkt, knot_pkt_t *template, struct kr_request
 
 static int answer_prepare(knot_pkt_t *answer, knot_pkt_t *query, struct kr_request *req)
 {
+	/* There may be some edns options (like tcp-keeelalive) prepared by daemon.
+	 * knot_pkt_init_response() is going to clear opt_rr field,
+	 * so we must to save it. */
+	knot_rrset_t *daemon_edns_opts = answer->opt_rr;
 	if (knot_pkt_init_response(answer, query) != 0) {
 		return kr_error(ENOMEM); /* Failed to initialize answer */
 	}
 	/* Handle EDNS in the query */
 	if (knot_pkt_has_edns(query)) {
-		int ret = edns_create(answer, query, req);
+		int ret = edns_create(answer, daemon_edns_opts, req);
 		if (ret != 0){
 			return ret;
 		}
@@ -461,6 +472,9 @@ static int answer_prepare(knot_pkt_t *answer, knot_pkt_t *query, struct kr_reque
 		if (knot_pkt_has_dnssec(query)) {
 			knot_edns_set_do(answer->opt_rr);
 		}
+		/* edns_create() created new rrset and appended daemon_edns_opts to it.
+		 * Deallocate daemon_edns_opts since we don't need it anymore. */
+		knot_rrset_free(daemon_edns_opts, &answer->mm);
 	}
 	return kr_ok();
 }
@@ -687,7 +701,7 @@ static int query_finalize(struct kr_request *request, struct kr_query *qry, knot
 		/* Remove any EDNS records from any previous iteration. */
 		ret = edns_erase_and_reserve(pkt);
 		if (ret == 0) {
-			ret = edns_create(pkt, request->answer, request);
+			ret = edns_create(pkt, NULL, request);
 		}
 		if (ret == 0) {
 			/* Stub resolution (ask for +rd and +do) */
diff --git a/lib/utils.c b/lib/utils.c
index 678cec22b253c69d1c119f7349210f1c5dd110a5..852e754e7fdff86f6331689824ff9752afca078f 100644
--- a/lib/utils.c
+++ b/lib/utils.c
@@ -1023,6 +1023,36 @@ int knot_dname_lf2wire(knot_dname_t * const dst, uint8_t len, const uint8_t *lf)
 	return d - dst;
 }
 
+int kr_edns_append(knot_rrset_t *opt, knot_rdata_t *data, knot_mm_t *mm)
+{
+	assert(opt->rrs.count == 1);
+
+	uint8_t *old_data = opt->rrs.rdata->data;
+	uint16_t old_data_len = opt->rrs.rdata->len;
+
+	/* construct new RDATA */
+	uint32_t new_data_len = old_data_len + data->len;
+	if (new_data_len > UINT16_MAX) {
+		return kr_error(ENOSPC);
+	}
+
+	knot_rdata_t *new_data = mm_alloc(mm, offsetof(knot_rdata_t, data) + new_data_len);
+	if (!new_data) {
+		return kr_error(ENOMEM);
+	}
+
+	/* concatenate two RDATA's */
+	memcpy(new_data->data, old_data, old_data_len);
+	memcpy(new_data->data + old_data_len, data->data, data->len);
+	new_data->len = new_data_len;
+
+	/* free old RDATA */
+	mm_free(mm, opt->rrs.rdata);
+
+	opt->rrs.rdata = new_data;
+	return kr_ok();
+}
+
 void kr_rrset_init(knot_rrset_t *rrset, knot_dname_t *owner,
 			uint16_t type, uint16_t rclass, uint32_t ttl)
 {
diff --git a/lib/utils.h b/lib/utils.h
index 1e6cb16ff719651e0c3481ad4253f2de856673ec..4761ed66ce2a8b2af1723db2ece3fbb74bfed859 100644
--- a/lib/utils.h
+++ b/lib/utils.h
@@ -440,6 +440,19 @@ static inline int kr_dname_lf(uint8_t *dst, const knot_dname_t *src, bool add_wi
 	return KNOT_EOK;
 };
 
+/*!
+ * \brief Append buffer with new EDNS options to existing opt_rr.
+ *
+ * \param opt   OPT RR structure to add the data to.
+ * \param data  Buffer with edns options to be added.
+ * \param mm    Memory context.
+ *
+ * \return 0 - success
+ *         kr_error(ENOMEM) - out-of-memory
+ *         kr_error(ENOSPC) - resulting buffer would be too long
+ */
+KR_EXPORT int kr_edns_append(knot_rrset_t *opt, knot_rdata_t *data, knot_mm_t *mm);
+
 /* Trivial non-inline wrappers, to be used in lua. */
 KR_EXPORT void kr_rrset_init(knot_rrset_t *rrset, knot_dname_t *owner,
 				uint16_t type, uint16_t rclass, uint32_t ttl);
diff --git a/tests/test_utils.c b/tests/test_utils.c
index 07856560eba064c57678ba8ecde226c3a25d0776..223dc580adaa887cb44738a1a02827e6e6a96bc9 100644
--- a/tests/test_utils.c
+++ b/tests/test_utils.c
@@ -79,11 +79,69 @@ static void test_straddr(void **state)
 	assert_int_not_equal(test_bitcmp(ip6_sub, ip6_out, 4), 0);
 }
 
+static void test_edns_append(void **state)
+{
+	uint8_t *source_option[KNOT_EDNS_MAX_OPTION_CODE];
+	int source_option_len[KNOT_EDNS_MAX_OPTION_CODE];
+	knot_pkt_t *pkt = knot_pkt_new(NULL, KNOT_WIRE_MAX_PKTSIZE, NULL);
+	knot_rrset_t *opt_rr = mm_alloc(NULL, sizeof(knot_rrset_t));
+	knot_edns_init(opt_rr, KR_EDNS_PAYLOAD, 0, KR_EDNS_VERSION, NULL);
+	pkt->opt_rr = opt_rr;
+
+	for (uint16_t i = 0; i < KNOT_EDNS_MAX_OPTION_CODE; ++i) {
+		if (i == KNOT_EDNS_OPTION_TCP_KEEPALIVE) {
+			source_option[i] = NULL;
+			source_option_len[i] = 0;
+			continue;
+		}
+		int opt_len = kr_rand_uint(16);
+		if (opt_len == 0) {
+			opt_len = 16;
+		}
+		uint8_t *opt_buf = malloc(opt_len);
+		assert_non_null(opt_buf);
+		knot_rrset_t *opt_rr_new = mm_alloc(NULL, sizeof(knot_rrset_t));
+		assert_non_null(opt_rr_new);
+
+		for (int j = 0; j < opt_len; ++j) {
+			opt_buf[j] = kr_rand_uint(255);
+		}
+
+		source_option[i] = opt_buf;
+		source_option_len[i] = opt_len;
+
+		knot_edns_init(opt_rr_new, KR_EDNS_PAYLOAD, 0, KR_EDNS_VERSION, NULL);
+		knot_edns_add_option(opt_rr_new, i, opt_len, opt_buf, NULL);
+		kr_edns_append(opt_rr, opt_rr_new->rrs.rdata, NULL);
+		knot_rrset_free(opt_rr_new, NULL);
+	}
+
+	knot_edns_options_t *options;
+	knot_edns_get_options(opt_rr, &options, NULL);
+
+	for (uint16_t i = 0; i < KNOT_EDNS_MAX_OPTION_CODE; ++i) {
+		uint8_t *opt = knot_edns_get_option(opt_rr,  i);
+		if (source_option[i] == NULL) {
+			assert_null(opt);
+			continue;
+		}
+		int opt_len = knot_edns_opt_get_length(opt);
+		assert_int_equal(opt_len, source_option_len[i]);
+		assert_int_equal(memcmp(&opt[4], source_option[i], opt_len), 0);
+		free(source_option[i]);
+	}
+
+	mm_free(NULL, options);
+	knot_rrset_free(opt_rr, NULL);
+	knot_pkt_free(pkt);
+}
+
 int main(void)
 {
 	const UnitTest tests[] = {
 		unit_test(test_strcatdup),
 		unit_test(test_straddr),
+		unit_test(test_edns_append)
 	};
 
 	return run_tests(tests);