diff --git a/changelogs/unreleased/gh-9480-dont-rollback-on-quorum-wait-cancel.md b/changelogs/unreleased/gh-9480-dont-rollback-on-quorum-wait-cancel.md
new file mode 100644
index 0000000000000000000000000000000000000000..2ec6a52d1ebbe08712f4f8c5e96792b7fcb23457
--- /dev/null
+++ b/changelogs/unreleased/gh-9480-dont-rollback-on-quorum-wait-cancel.md
@@ -0,0 +1,4 @@
+## bugfix/replication
+
+* Now transactions are not rolled back if the transaction fiber is
+  cancelled when waiting for quorum from replicas (gh-9480).
diff --git a/src/box/txn.c b/src/box/txn.c
index a8d81dacd9cb123f84689518c41f91bf1b5df9d3..0a304fab861cf47850d7da0159556c8cd72fa6cd 100644
--- a/src/box/txn.c
+++ b/src/box/txn.c
@@ -1105,8 +1105,15 @@ txn_commit(struct txn *txn)
 			txn_limbo_ack(&txn_limbo, txn_limbo.owner_id,
 				      limbo_entry->lsn);
 		}
-		if (txn_limbo_wait_complete(&txn_limbo, limbo_entry) < 0)
-			goto rollback;
+		int rc = txn_limbo_wait_complete(&txn_limbo, limbo_entry);
+		if (rc < 0) {
+			if (fiber_is_cancelled()) {
+				txn->fiber = NULL;
+				return -1;
+			} else {
+				goto rollback;
+			}
+		}
 	}
 	assert(txn_has_flag(txn, TXN_IS_DONE));
 	assert(txn->signature >= 0);
diff --git a/src/box/txn_limbo.c b/src/box/txn_limbo.c
index e4b77ba7e4a40a864d9c540eb02bdf9ef829e287..00618bb94424dbef7b34077cc452634c0ba041a0 100644
--- a/src/box/txn_limbo.c
+++ b/src/box/txn_limbo.c
@@ -283,6 +283,8 @@ txn_limbo_wait_complete(struct txn_limbo *limbo, struct txn_limbo_entry *entry)
 		int rc = fiber_cond_wait_timeout(&limbo->wait_cond, timeout);
 		if (txn_limbo_entry_is_complete(entry))
 			goto complete;
+		if (rc != 0 && fiber_is_cancelled())
+			return -1;
 		if (txn_limbo_is_frozen(limbo))
 			goto wait;
 		if (rc != 0)
diff --git a/src/box/txn_limbo.h b/src/box/txn_limbo.h
index b81b71645127c5ef8ce56b650e43b9a617dc059a..ce93c9d10c100476addf324a474d81ae07423085 100644
--- a/src/box/txn_limbo.h
+++ b/src/box/txn_limbo.h
@@ -343,6 +343,7 @@ txn_limbo_ack(struct txn_limbo *limbo, uint32_t replica_id, int64_t lsn);
  * entry is either committed or rolled back.
  * If timeout is reached before acks are collected, the tx is
  * rolled back as well as all the txs in the limbo following it.
+ * If fiber is cancelled before acks are collected, the tx is left in limbo.
  * Returns -1 when rollback was performed and tx has to be freed.
  *          0 when tx processing can go on.
  */
diff --git a/test/replication-luatest/gh_9480_dont_rollback_on_quorum_wait_cancel_test.lua b/test/replication-luatest/gh_9480_dont_rollback_on_quorum_wait_cancel_test.lua
new file mode 100644
index 0000000000000000000000000000000000000000..ad9c8703b290c21e38f6414e8e3d1d8ec784d2f1
--- /dev/null
+++ b/test/replication-luatest/gh_9480_dont_rollback_on_quorum_wait_cancel_test.lua
@@ -0,0 +1,46 @@
+local t = require('luatest')
+local server = require('luatest.server')
+
+local g = t.group()
+
+g.before_each(function(cg)
+    local box_cfg = {
+        election_mode='off',
+        replication_synchro_timeout=1000,
+        replication_synchro_quorum = 2,
+        memtx_use_mvcc_engine = false,
+    }
+    cg.server = server:new({box_cfg = box_cfg})
+    cg.server:start()
+end)
+
+g.after_each(function(cg)
+    cg.server:drop()
+end)
+
+-- Test that if we cancel TX while it is waiting quorum in limbo it is
+-- not rolled back.
+g.test_cancel_tx_waiting_in_limbo = function(cg)
+    cg.server:exec(function()
+        local fiber = require('fiber')
+
+        local space = box.schema.create_space('test', {is_sync = true})
+        space:create_index('pk')
+        box.ctl.promote()
+
+        local f = fiber.new(function()
+            space:insert({1})
+        end)
+        f:set_joinable(true)
+        f:wakeup()
+
+        t.helpers.retrying({timeout = 3}, function()
+            t.assert(box.info.synchro.queue.len == 1)
+        end)
+        f:cancel()
+        local ret, err = f:join()
+        t.assert_equals(ret, false)
+        t.assert_equals(err:unpack().type, 'FiberIsCancelled')
+        t.assert_not_equals(space:get({1}), nil)
+    end)
+end