diff --git a/doc/man/knot.conf.5in b/doc/man/knot.conf.5in
index b032e555c61088fcd58e5407b3a255b82f800f29..0b5e78fc5c3907e558d6281f7f86036a21bd97bd 100644
--- a/doc/man/knot.conf.5in
+++ b/doc/man/knot.conf.5in
@@ -200,6 +200,7 @@ server:
     xdp\-route\-check: BOOL
     listen: ADDR[@INT] ...
     listen\-xdp: STR[@INT] | ADDR[@INT] ...
+    xdp\-tcp: BOOL
 .ft P
 .fi
 .UNINDENT
@@ -476,6 +477,17 @@ intended to offer the DNS service, at least to fulfil the DNS requirement for
 working TCP.
 .UNINDENT
 .UNINDENT
+.SS xdp\-tcp
+.sp
+Also answer TCP traffic (queries) with XDP workers.
+.sp
+\fBWARNING:\fP
+.INDENT 0.0
+.INDENT 3.5
+This feature is highly experimental and it may eat your hamster as well as any
+other hamsters connected to the network.
+.UNINDENT
+.UNINDENT
 .SH CONTROL SECTION
 .sp
 Configuration of the server control interface.
diff --git a/doc/reference.rst b/doc/reference.rst
index 2cde3bcde01bcfd70546cb643b3d8427a682ec55..2006edd4d1788a498ee402e0d9a2b2dfc670e992 100644
--- a/doc/reference.rst
+++ b/doc/reference.rst
@@ -151,6 +151,7 @@ General options related to the server.
      xdp-route-check: BOOL
      listen: ADDR[@INT] ...
      listen-xdp: STR[@INT] | ADDR[@INT] ...
+     xdp-tcp: BOOL
 
 .. CAUTION::
    When you change configuration parameters dynamically or via configuration file
@@ -493,6 +494,17 @@ Change of this parameter requires restart of the Knot server to take effect.
    intended to offer the DNS service, at least to fulfil the DNS requirement for
    working TCP.
 
+.. _server_xdp-tcp:
+
+xdp-tcp
+-------
+
+Also answer TCP traffic (queries) with XDP workers.
+
+.. WARNING::
+   This feature is highly experimental and it may eat your hamster as well as any
+   other hamsters connected to the network.
+
 .. _Control section:
 
 Control section
diff --git a/src/knot/conf/conf.c b/src/knot/conf/conf.c
index 693c3fe8c69d1e4d37ebf8b8214d06882cca23a5..108d88589308e99a5c0cfffc7f42d9cd4616be57 100644
--- a/src/knot/conf/conf.c
+++ b/src/knot/conf/conf.c
@@ -1188,7 +1188,7 @@ size_t conf_xdp_threads_txn(
 	while (val.code == KNOT_EOK) {
 		struct sockaddr_storage addr = conf_addr(&val, NULL);
 		conf_xdp_iface_t iface;
-		int ret = conf_xdp_iface(&addr, &iface);
+		int ret = conf_xdp_iface(&addr, false, &iface);
 		if (ret == KNOT_EOK) {
 			workers += iface.queues;
 		}
@@ -1351,6 +1351,7 @@ conf_remote_t conf_remote_txn(
 
 int conf_xdp_iface(
 	struct sockaddr_storage *addr,
+	bool tcp,
 	conf_xdp_iface_t *iface)
 {
 #ifndef ENABLE_XDP
@@ -1367,12 +1368,14 @@ int conf_xdp_iface(
 		const char *port = strchr(addr_str, '@');
 		if (port != NULL) {
 			iface->name[port - addr_str] = '\0';
-			int ret = str_to_u16(port + 1, &iface->port);
+			uint16_t res = 0;
+			int ret = str_to_u16(port + 1, &res);
 			if (ret != KNOT_EOK) {
 				return ret;
-			} else if (iface->port == 0) {
+			} else if (res == 0) {
 				return KNOT_EINVAL;
 			}
+			iface->port = res;
 		} else {
 			iface->port = 53;
 		}
@@ -1395,6 +1398,10 @@ int conf_xdp_iface(
 	}
 	iface->queues = queues;
 
+	if (tcp) {
+		iface->port |= KNOT_XDP_LISTEN_PORT_TCP;
+	}
+
 	return KNOT_EOK;
 #endif
 }
diff --git a/src/knot/conf/conf.h b/src/knot/conf/conf.h
index 24755a2405115f784fa12542121ff70a8f96adec..1353b3fc25300b5f88e9d10927f2dbede94ef671 100644
--- a/src/knot/conf/conf.h
+++ b/src/knot/conf/conf.h
@@ -828,8 +828,8 @@ static inline conf_remote_t conf_remote(
 typedef struct {
 	/*! Interface name. */
 	char name[32];
-	/*! UDP port to listen on. */
-	uint16_t port;
+	/*! UDP port to listen on, including XDP flags. */
+	uint32_t port;
 	/*! Number of active IO queues. */
 	uint16_t queues;
 } conf_xdp_iface_t;
@@ -838,11 +838,13 @@ typedef struct {
  * Gets the XDP interface parameters for a given configuration value.
  *
  * \param[in] addr    XDP interface name stored in the configuration.
+ * \param[in] tcp     Allow TCP listening on this XDP iface.
  * \param[out] iface  Interface parameters.
  *
  * \return Error code, KNOT_EOK if success.
  */
 int conf_xdp_iface(
 	struct sockaddr_storage *addr,
+	bool tcp,
 	conf_xdp_iface_t *iface
 );
diff --git a/src/knot/conf/schema.c b/src/knot/conf/schema.c
index 88cee943e6dc6ef2674caa97f6d22be4f6ae2b44..adbb8520c1f656b78c2bac3d4d46b8c713febfa6 100644
--- a/src/knot/conf/schema.c
+++ b/src/knot/conf/schema.c
@@ -209,6 +209,7 @@ static const yp_item_t desc_server[] = {
 	{ C_XDP_ROUTE_CHECK,      YP_TBOOL, YP_VNONE },
 	{ C_LISTEN,               YP_TADDR, YP_VADDR = { 53 }, YP_FMULTI, { check_listen } },
 	{ C_LISTEN_XDP,           YP_TADDR, YP_VADDR = { 53 }, YP_FMULTI, { check_xdp } },
+	{ C_XDP_TCP,              YP_TBOOL, YP_VNONE },
 	{ C_COMMENT,              YP_TSTR,  YP_VNONE },
 	// Legacy items.
 	{ C_MAX_TCP_CLIENTS,      YP_TINT,  YP_VINT = { 0, INT32_MAX, YP_NIL } },
diff --git a/src/knot/conf/schema.h b/src/knot/conf/schema.h
index b09ded0a77e4098522bb1433642ad6a3fdf1be0a..f3157d62314684ee923e107b60fac15432dbaae9 100644
--- a/src/knot/conf/schema.h
+++ b/src/knot/conf/schema.h
@@ -135,6 +135,7 @@
 #define C_VERSION		"\x07""version"
 #define C_VIA			"\x03""via"
 #define C_XDP_ROUTE_CHECK	"\x0F""xdp-route-check"
+#define C_XDP_TCP		"\x07""xdp-tcp"
 #define C_ZONE			"\x04""zone"
 #define C_ZONEFILE_LOAD		"\x0D""zonefile-load"
 #define C_ZONEFILE_SYNC		"\x0D""zonefile-sync"
diff --git a/src/knot/conf/tools.c b/src/knot/conf/tools.c
index ae6de6f07adc4af40f0452bfde3c6b97c8972b74..922cc58b2b708bb7be1f792f04e574887cebf2e3 100644
--- a/src/knot/conf/tools.c
+++ b/src/knot/conf/tools.c
@@ -256,7 +256,7 @@ int check_xdp(
 	bool no_port;
 	struct sockaddr_storage ss = yp_addr(args->data, &no_port);
 	conf_xdp_iface_t if_new;
-	int ret = conf_xdp_iface(&ss, &if_new);
+	int ret = conf_xdp_iface(&ss, false, &if_new);
 	if (ret != KNOT_EOK) {
 		args->err_str = "invalid XDP interface specification";
 		return ret;
@@ -268,7 +268,7 @@ int check_xdp(
 	while (xdp.code == KNOT_EOK && count-- > 1) {
 		struct sockaddr_storage addr = conf_addr(&xdp, NULL);
 		conf_xdp_iface_t if_prev;
-		ret = conf_xdp_iface(&addr, &if_prev);
+		ret = conf_xdp_iface(&addr, false, &if_prev);
 		if (ret != KNOT_EOK) {
 			return ret;
 		}
@@ -382,7 +382,7 @@ static void check_mtu(knotd_conf_check_args_t *args, conf_val_t *xdp)
 	while (xdp->code == KNOT_EOK) {
 		struct sockaddr_storage addr = conf_addr(xdp, NULL);
 		conf_xdp_iface_t iface;
-		int ret = conf_xdp_iface(&addr, &iface);
+		int ret = conf_xdp_iface(&addr, false, &iface);
 		if (ret != KNOT_EOK) {
 			CONF_LOG(LOG_WARNING, "failed to check XDP interface MTU");
 			return;
diff --git a/src/knot/server/server.c b/src/knot/server/server.c
index b6fd988eb27b2736ad74b57cbfb77bf58bd7d7ce..325eb750c77a9e9cc9131bda8c1b06072ae0b00e 100644
--- a/src/knot/server/server.c
+++ b/src/knot/server/server.c
@@ -221,14 +221,14 @@ static int disable_pmtudisc(int sock, int family)
 }
 
 static iface_t *server_init_xdp_iface(struct sockaddr_storage *addr, bool route_check,
-                                      unsigned *thread_id_start)
+                                      unsigned *thread_id_start, bool tcp)
 {
 #ifndef ENABLE_XDP
 	assert(0);
 	return NULL;
 #else
 	conf_xdp_iface_t iface;
-	int ret = conf_xdp_iface(addr, &iface);
+	int ret = conf_xdp_iface(addr, tcp, &iface);
 	if (ret != KNOT_EOK) {
 		log_error("failed to initialize XDP interface (%s)",
 		          knot_strerror(ret));
@@ -559,12 +559,15 @@ static int configure_sockets(conf_t *conf, server_t *s)
 	unsigned thread_id = s->handlers[IO_UDP].handler.unit->size +
 	                     s->handlers[IO_TCP].handler.unit->size;
 	while (lisxdp_val.code == KNOT_EOK) {
+		conf_val_t xdp_tcp_val = conf_get(conf, C_SRV, C_XDP_TCP);
+		bool xdp_tcp = conf_bool(&xdp_tcp_val);
+
 		struct sockaddr_storage addr = conf_addr(&lisxdp_val, NULL);
 		char addr_str[SOCKADDR_STRLEN] = { 0 };
 		sockaddr_tostr(addr_str, sizeof(addr_str), &addr);
-		log_info("binding to XDP interface %s", addr_str);
+		log_info("binding to XDP interface %s%s", addr_str, xdp_tcp ? " with TCP" : "");
 
-		iface_t *new_if = server_init_xdp_iface(&addr, route_check, &thread_id);
+		iface_t *new_if = server_init_xdp_iface(&addr, route_check, &thread_id, xdp_tcp);
 		if (new_if == NULL) {
 			server_deinit_iface_list(newlist, nifs);
 			return KNOT_ERROR;