diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c
index bb68d6db586..3b96cc00dbb 100644
--- a/lib/dns/resolver.c
+++ b/lib/dns/resolver.c
@@ -10735,6 +10735,8 @@ fail:
 void
 dns_resolver_cancelfetch(dns_fetch_t *fetch) {
 	fetchctx_t *fctx = NULL;
+	dns_fetchevent_t *event_trystale = NULL;
+	dns_fetchevent_t *event_fetchdone = NULL;
 
 	REQUIRE(DNS_FETCH_VALID(fetch));
 	fctx = fetch->private;
@@ -10745,24 +10747,69 @@ dns_resolver_cancelfetch(dns_fetch_t *fetch) {
 	LOCK(&fctx->lock);
 
 	/*
-	 * Find the completion event for this fetch (as opposed
-	 * to those for other fetches that have joined the same
-	 * fctx) and send it with result = ISC_R_CANCELED.
+	 * Find all the events associated with the provided 'fetch' (as opposed
+	 * to those for other fetches that have joined the same fctx).  The
+	 * event(s) found are only sent and removed from the fctx->events list
+	 * after this loop is finished (i.e. the fctx->events list is not
+	 * modified during iteration).
 	 */
 	for (dns_fetchevent_t *event = ISC_LIST_HEAD(fctx->events);
 	     event != NULL; event = ISC_LIST_NEXT(event, ev_link))
 	{
-		if (event->fetch == fetch) {
-			isc_task_t *task = event->ev_sender;
-			ISC_LIST_UNLINK(fctx->events, event, ev_link);
-			event->ev_sender = fctx;
-			event->result = ISC_R_CANCELED;
+		/*
+		 * Only process events associated with the provided 'fetch'.
+		 */
+		if (event->fetch != fetch) {
+			continue;
+		}
+
+		/*
+		 * Track various events associated with the provided 'fetch' in
+		 * separate variables as they will need to be sent in a
+		 * specific order.
+		 */
+		switch (event->ev_type) {
+		case DNS_EVENT_TRYSTALE:
+			INSIST(event_trystale == NULL);
+			event_trystale = event;
+			break;
+		case DNS_EVENT_FETCHDONE:
+			INSIST(event_fetchdone == NULL);
+			event_fetchdone = event;
+			break;
+		default:
+			UNREACHABLE();
+		}
 
-			isc_task_sendanddetach(&task, ISC_EVENT_PTR(&event));
+		/*
+		 * Break out of the loop once all possible types of events
+		 * associated with the provided 'fetch' are found.
+		 */
+		if (event_trystale != NULL && event_fetchdone != NULL) {
 			break;
 		}
 	}
 
+	/*
+	 * The "trystale" event must be sent before the "fetchdone" event,
+	 * because the latter clears the "recursing" query attribute, which is
+	 * required by both events (handled by the same callback function).
+	 */
+	if (event_trystale != NULL) {
+		isc_task_t *etask = event_trystale->ev_sender;
+		ISC_LIST_UNLINK(fctx->events, event_trystale, ev_link);
+		event_trystale->ev_sender = fctx;
+		event_trystale->result = ISC_R_CANCELED;
+		isc_task_sendanddetach(&etask, ISC_EVENT_PTR(&event_trystale));
+	}
+	if (event_fetchdone != NULL) {
+		isc_task_t *etask = event_fetchdone->ev_sender;
+		ISC_LIST_UNLINK(fctx->events, event_fetchdone, ev_link);
+		event_fetchdone->ev_sender = fctx;
+		event_fetchdone->result = ISC_R_CANCELED;
+		isc_task_sendanddetach(&etask, ISC_EVENT_PTR(&event_fetchdone));
+	}
+
 	/*
 	 * The fctx continues running even if no fetches remain;
 	 * the answer is still cached.
diff --git a/lib/ns/query.c b/lib/ns/query.c
index 1055c950bd6..9d39fdc8f63 100644
--- a/lib/ns/query.c
+++ b/lib/ns/query.c
@@ -6247,7 +6247,9 @@ fetch_callback(isc_task_t *task, isc_event_t *event) {
 	CTRACE(ISC_LOG_DEBUG(3), "fetch_callback");
 
 	if (event->ev_type == DNS_EVENT_TRYSTALE) {
-		query_lookup_stale(client);
+		if (devent->result != ISC_R_CANCELED) {
+			query_lookup_stale(client);
+		}
 		isc_event_free(ISC_EVENT_PTR(&event));
 		return;
 	}
