diff --git a/services/listen_dnsport.c b/services/listen_dnsport.c
index 5db2b940b..dc1946d99 100644
--- a/services/listen_dnsport.c
+++ b/services/listen_dnsport.c
@@ -2167,7 +2167,8 @@ void tcp_req_info_clear(struct tcp_req_info* req)
 	open = req->open_req_list;
 	while(open) {
 		nopen = open->next;
-		mesh_state_remove_reply(open->mesh, open->mesh_state, req->cp);
+		mesh_state_remove_reply(open->mesh, open->mesh_state, req->cp,
+			NULL);
 		free(open);
 		open = nopen;
 	}
@@ -3597,15 +3598,29 @@ doq_conn_create(struct comm_point* c, struct doq_pkt_addr* paddr,
 	return conn;
 }
 
+/** The arguments for doq stream tree del. */
+struct doq_stream_tree_del_args {
+	/** The doq table. */
+	struct doq_table* table;
+	/** The doq connection for the stream. */
+	struct doq_conn* conn;
+};
+
 /** delete stream tree node */
 static void
 stream_tree_del(rbnode_type* node, void* arg)
 {
-	struct doq_table* table = (struct doq_table*)arg;
+	struct doq_stream_tree_del_args* args = (struct doq_stream_tree_del_args*)arg;
+	struct doq_table* table = args->table;
 	struct doq_stream* stream;
 	if(!node)
 		return;
 	stream = (struct doq_stream*)node;
+	if(stream->mesh_state) {
+		mesh_state_remove_reply(stream->mesh, stream->mesh_state,
+			args->conn->doq_socket->cp, stream);
+		stream->mesh_state = NULL;
+	}
 	if(stream->in)
 		doq_table_quic_size_subtract(table, stream->inlen);
 	if(stream->out)
@@ -3627,7 +3642,11 @@ doq_conn_delete(struct doq_conn* conn, struct doq_table* table)
 	 * because the ngtcp2 conn is deleted. */
 	SSL_set_app_data(conn->ssl, NULL);
 	if(conn->stream_tree.count != 0) {
-		traverse_postorder(&conn->stream_tree, stream_tree_del, table);
+		struct doq_stream_tree_del_args args;
+		memset(&args, 0, sizeof(args));
+		args.table = table;
+		args.conn = conn;
+		traverse_postorder(&conn->stream_tree, stream_tree_del, &args);
 	}
 	free(conn->key.dcid);
 	SSL_free(conn->ssl);
@@ -3940,6 +3959,11 @@ doq_stream_close(struct doq_conn* conn, struct doq_stream* stream,
 	if(stream->is_closed)
 		return 1;
 	stream->is_closed = 1;
+	if(stream->mesh_state) {
+		mesh_state_remove_reply(stream->mesh, stream->mesh_state,
+			conn->doq_socket->cp, stream);
+		stream->mesh_state = NULL;
+	}
 	doq_stream_off_write_list(conn, stream);
 	if(send_shutdown) {
 		verbose(VERB_ALGO, "doq: shutdown stream_id %d with app_error_code %d",
@@ -4012,7 +4036,33 @@ doq_stream_send_reply(struct doq_conn* conn, struct doq_stream* stream,
 	doq_conn_write_enable(conn);
 	return 1;
 }
+#endif /* HAVE_NGTCP2 */
 
+void
+doq_stream_add_meshstate(struct doq_stream* stream,
+	struct mesh_area* mesh, struct mesh_state* m)
+{
+#ifdef HAVE_NGTCP2
+	stream->mesh = mesh;
+	stream->mesh_state = m;
+#else
+	(void)stream; (void)mesh; (void)m;
+#endif
+}
+
+void
+doq_stream_remove_mesh_state(struct doq_stream* stream)
+{
+#ifdef HAVE_NGTCP2
+	if(!stream)
+		return;
+	stream->mesh_state = NULL;
+#else
+	(void)stream;
+#endif
+}
+
+#ifdef HAVE_NGTCP2
 /** doq stream data length has completed, allocations can be done. False on
  * allocation failure. */
 static int
@@ -4067,6 +4117,7 @@ doq_stream_data_complete(struct doq_conn* conn, struct doq_stream* stream)
 		return 0;
 	}
 	c->repinfo.doq_streamid = stream->stream_id;
+	c->repinfo.doq_stream = stream;
 	conn->doq_socket->current_conn = conn;
 	fptr_ok(fptr_whitelist_comm_point(c->callback));
 	if( (*c->callback)(c, c->cb_arg, NETEVENT_NOERROR, &c->repinfo)) {
diff --git a/services/listen_dnsport.h b/services/listen_dnsport.h
index 963595a1c..88bbb10de 100644
--- a/services/listen_dnsport.h
+++ b/services/listen_dnsport.h
@@ -61,6 +61,8 @@ struct config_file;
 struct addrinfo;
 struct sldns_buffer;
 struct tcl_list;
+struct mesh_area;
+struct mesh_state;
 
 /**
  * Listening for queries structure.
@@ -689,6 +691,11 @@ struct doq_stream {
 	uint8_t* out;
 	/** if the stream is on the write list */
 	uint8_t on_write_list;
+	/** The mesh area and mesh state, set when this stream's query was
+	 * dispatched into the mesh; used to detach the reply on stream close */
+	struct mesh_area* mesh;
+	/** the mesh state for the query, is nonNULL when there is one. */
+	struct mesh_state* mesh_state;
 	/** the prev and next on the write list, if on the list */
 	struct doq_stream* write_prev, *write_next;
 };
@@ -791,7 +798,16 @@ int doq_stream_close(struct doq_conn* conn, struct doq_stream* stream,
 /** send reply for a connection */
 int doq_stream_send_reply(struct doq_conn* conn, struct doq_stream* stream,
 	struct sldns_buffer* buf);
+#endif /* HAVE_NGTCP2 */
 
+/** add mesh state to doq stream */
+void doq_stream_add_meshstate(struct doq_stream* stream,
+	struct mesh_area* mesh, struct mesh_state* m);
+
+/** remove mesh state from doq stream */
+void doq_stream_remove_mesh_state(struct doq_stream* stream);
+
+#ifdef HAVE_NGTCP2
 /** the connection has write interest, wants to write packets */
 void doq_conn_write_enable(struct doq_conn* conn);
 
diff --git a/services/mesh.c b/services/mesh.c
index 286901047..f06c1cb9d 100644
--- a/services/mesh.c
+++ b/services/mesh.c
@@ -467,6 +467,8 @@ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo,
 				"incoming query.");
 			if(rep->c->use_h2)
 				http2_stream_remove_mesh_state(rep->c->h2_stream);
+			else if(rep->c->type == comm_doq && rep->doq_stream)
+				doq_stream_remove_mesh_state(rep->doq_stream);
 			comm_point_drop_reply(rep);
 			mesh->stats_dropped++;
 			return;
@@ -480,6 +482,8 @@ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo,
 				"dropping incoming query.");
 			if(rep->c->use_h2)
 				http2_stream_remove_mesh_state(rep->c->h2_stream);
+			else if(rep->c->type == comm_doq && rep->doq_stream)
+				doq_stream_remove_mesh_state(rep->doq_stream);
 			comm_point_drop_reply(rep);
 			mesh->num_queries_replyaddr_limit++;
 			return;
@@ -552,6 +556,8 @@ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo,
 	}
 	if(rep->c->use_h2) {
 		http2_stream_add_meshstate(rep->c->h2_stream, mesh, s);
+	} else if(rep->c->type == comm_doq && rep->doq_stream) {
+		doq_stream_add_meshstate(rep->doq_stream, mesh, s);
 	}
 	/* add serve expired timer if required and not already there */
 	if(timeout && !mesh_serve_expired_init(s, timeout)) {
@@ -605,6 +611,8 @@ servfail_mem:
 		qinfo, qid, qflags, edns);
 	if(rep->c->use_h2)
 		http2_stream_remove_mesh_state(rep->c->h2_stream);
+	else if(rep->c->type == comm_doq && rep->doq_stream)
+		doq_stream_remove_mesh_state(rep->doq_stream);
 	comm_point_send_reply(rep);
 	if(added)
 		mesh_state_delete(&s->s);
@@ -1484,6 +1492,10 @@ mesh_send_reply(struct mesh_state* m, int rcode, struct reply_info* rep,
 		 * for HTTP/2 stream to refer to mesh state, in case
 		 * connection gets cleanup before HTTP/2 stream close. */
 		r->h2_stream->mesh_state = NULL;
+#ifdef HAVE_NGTCP2
+	} else if(r->query_reply.doq_stream) {
+		r->query_reply.doq_stream->mesh_state = NULL;
+#endif
 	}
 	/* send the reply */
 	/* We don't reuse the encoded answer if:
@@ -1777,6 +1789,8 @@ void mesh_query_done(struct mesh_state* mstate)
 				mstate->reply_list = NULL;
 				if(r->query_reply.c->use_h2)
 					http2_stream_remove_mesh_state(r->h2_stream);
+				else if(r->query_reply.doq_stream)
+					doq_stream_remove_mesh_state(r->query_reply.doq_stream);
 				comm_point_drop_reply(&r->query_reply);
 				mstate->reply_list = reply_list;
 				log_assert(mstate->s.env->mesh->num_reply_addrs > 0);
@@ -1814,6 +1828,8 @@ void mesh_query_done(struct mesh_state* mstate)
 			mstate->reply_list = NULL;
 			if(r->query_reply.c->use_h2) {
 				http2_stream_remove_mesh_state(r->h2_stream);
+			} else if(r->query_reply.doq_stream) {
+				doq_stream_remove_mesh_state(r->query_reply.doq_stream);
 			}
 			comm_point_drop_reply(&r->query_reply);
 			mstate->reply_list = reply_list;
@@ -2009,6 +2025,8 @@ int mesh_state_add_reply(struct mesh_state* s, struct edns_data* edns,
 	if(rep->c->use_h2)
 		r->h2_stream = rep->c->h2_stream;
 	else	r->h2_stream = NULL;
+	if(rep->c->type != comm_doq)
+		r->query_reply.doq_stream = NULL;
 
 	/* Data related to local alias stored in 'qinfo' (if any) is ephemeral
 	 * and can be different for different original queries (even if the
@@ -2366,7 +2384,7 @@ void mesh_list_remove(struct mesh_state* m, struct mesh_state** fp,
 }
 
 void mesh_state_remove_reply(struct mesh_area* mesh, struct mesh_state* m,
-	struct comm_point* cp)
+	struct comm_point* cp, struct doq_stream* doq_stream)
 {
 	struct mesh_reply* n, *prev = NULL;
 	n = m->reply_list;
@@ -2374,7 +2392,8 @@ void mesh_state_remove_reply(struct mesh_area* mesh, struct mesh_state* m,
 	 * there is no accounting twice */
 	if(!n) return; /* nothing to remove, also no accounting needed */
 	while(n) {
-		if(n->query_reply.c == cp) {
+		if(n->query_reply.c == cp
+			&& (!doq_stream || n->query_reply.doq_stream == doq_stream)) {
 			/* unlink it */
 			if(prev) prev->next = n->next;
 			else m->reply_list = n->next;
@@ -2387,6 +2406,10 @@ void mesh_state_remove_reply(struct mesh_area* mesh, struct mesh_state* m,
 			 * share the same comm_point); make sure the streams
 			 * don't point back. */
 			if(n->h2_stream) n->h2_stream->mesh_state = NULL;
+#ifdef HAVE_NGTCP2
+			if(n->query_reply.doq_stream)
+				n->query_reply.doq_stream->mesh_state = NULL;
+#endif
 
 			/* prev = prev; */
 			n = n->next;
@@ -2554,6 +2577,8 @@ mesh_serve_expired_callback(void* arg)
 			mstate->reply_list = NULL;
 			if(r->query_reply.c->use_h2)
 				http2_stream_remove_mesh_state(r->h2_stream);
+			else if(r->query_reply.doq_stream)
+				doq_stream_remove_mesh_state(r->query_reply.doq_stream);
 			comm_point_drop_reply(&r->query_reply);
 			mstate->reply_list = reply_list;
 			mstate->s.env->mesh->num_queries_discard_timeout++;
diff --git a/services/mesh.h b/services/mesh.h
index 9ee585156..352260e26 100644
--- a/services/mesh.h
+++ b/services/mesh.h
@@ -683,9 +683,11 @@ void mesh_list_remove(struct mesh_state* m, struct mesh_state** fp,
  * @param mesh: to update the counters.
  * @param m: the mesh state.
  * @param cp: the comm_point to remove from the list.
+ * @param doq_stream: if not NULL, it specifies the doq_stream to match
+ *	for the delete.
  */
 void mesh_state_remove_reply(struct mesh_area* mesh, struct mesh_state* m,
-	struct comm_point* cp);
+	struct comm_point* cp, struct doq_stream* doq_stream);
 
 /** Callback for when the serve expired client timer has run out.  Tries to
  * find an expired answer in the cache and reply that to the client.
diff --git a/util/netevent.c b/util/netevent.c
index a86e22518..12cce1a60 100644
--- a/util/netevent.c
+++ b/util/netevent.c
@@ -3174,7 +3174,7 @@ static void http2_stream_delete(struct http2_session* h2_session,
 {
 	if(h2_stream->mesh_state) {
 		mesh_state_remove_reply(h2_stream->mesh, h2_stream->mesh_state,
-			h2_session->c);
+			h2_session->c, NULL);
 		h2_stream->mesh_state = NULL;
 	}
 	http2_req_stream_clear(h2_stream);
diff --git a/util/netevent.h b/util/netevent.h
index c5114bbbe..e4bc216aa 100644
--- a/util/netevent.h
+++ b/util/netevent.h
@@ -187,6 +187,8 @@ struct comm_reply {
 	/** port number for doq */
 	int doq_srcport;
 #endif /* HAVE_NGTCP2 */
+	/** The doq stream to register mesh states to. */
+	struct doq_stream* doq_stream;
 };
 
 /**
