diff --git a/src/lua/error.lua b/src/lua/error.lua
index 85e86599d46f18eb1483c4ac369411575309cd97..b2646cb0101834c0c6bce832fb031cc71c4fe750 100644
--- a/src/lua/error.lua
+++ b/src/lua/error.lua
@@ -38,6 +38,9 @@ error_set_prev(struct error *e, struct error *prev);
 
 const char *
 box_error_custom_type(const struct error *e);
+
+void
+error_unref(struct error *e);
 ]]
 
 local REFLECTION_CACHE = {}
@@ -114,6 +117,12 @@ end
 local function error_prev(err)
     local e = err._cause;
     if e ~= nil then
+        local INT32_MAX = 2147483647
+        if e._refs >= INT32_MAX then
+            error("Too many references to error object")
+        end
+        e._refs = e._refs + 1
+        e = ffi.gc(e, ffi.C.error_unref)
         return e
     else
         return nil
diff --git a/test/box/error.result b/test/box/error.result
index 01920178e1a50c12054dd379d11a46386eb75ce4..59a53013b8650d252610ba5eacb93fe0ee6b1816 100644
--- a/test/box/error.result
+++ b/test/box/error.result
@@ -888,3 +888,71 @@ e = box.error.new({type = string.rep('a', 128)})
  | ---
  | - 63
  | ...
+
+-- gh-4887: accessing 'prev' member also refs it so that after
+-- error is gone, its 'prev' is staying alive.
+--
+lua_code = [[function(tuple) local json = require('json') return json.encode(tuple) end]]
+ | ---
+ | ...
+box.schema.func.create('runtimeerror', {body = lua_code, is_deterministic = true, is_sandboxed = true})
+ | ---
+ | ...
+s = box.schema.space.create('withdata')
+ | ---
+ | ...
+pk = s:create_index('pk')
+ | ---
+ | ...
+idx = s:create_index('idx', {func = box.func.runtimeerror.id, parts = {{1, 'string'}}})
+ | ---
+ | ...
+
+function test_func() return pcall(s.insert, s, {1}) end
+ | ---
+ | ...
+ok, err = test_func()
+ | ---
+ | ...
+preve = err.prev
+ | ---
+ | ...
+gc_err = setmetatable({preve}, {__mode = 'v'})
+ | ---
+ | ...
+err:set_prev(nil)
+ | ---
+ | ...
+err.prev
+ | ---
+ | - null
+ | ...
+collectgarbage('collect')
+ | ---
+ | - 0
+ | ...
+--  Still one reference to err.prev so it should not be collected.
+--
+gc_err
+ | ---
+ | - - '[string "return function(tuple) local json = require(''..."]:1: attempt to call
+ |     global ''require'' (a nil value)'
+ | ...
+preve = nil
+ | ---
+ | ...
+collectgarbage('collect')
+ | ---
+ | - 0
+ | ...
+gc_err
+ | ---
+ | - []
+ | ...
+
+s:drop()
+ | ---
+ | ...
+box.schema.func.drop('runtimeerror')
+ | ---
+ | ...
diff --git a/test/box/error.test.lua b/test/box/error.test.lua
index 38f740615448e33e7be54e04320ac7fc0ff90e7f..d63e83539cfd0e9dc326792bb49c6730f8593a75 100644
--- a/test/box/error.test.lua
+++ b/test/box/error.test.lua
@@ -244,3 +244,29 @@ e:unpack()
 -- Try too long type name.
 e = box.error.new({type = string.rep('a', 128)})
 #e.type
+
+-- gh-4887: accessing 'prev' member also refs it so that after
+-- error is gone, its 'prev' is staying alive.
+--
+lua_code = [[function(tuple) local json = require('json') return json.encode(tuple) end]]
+box.schema.func.create('runtimeerror', {body = lua_code, is_deterministic = true, is_sandboxed = true})
+s = box.schema.space.create('withdata')
+pk = s:create_index('pk')
+idx = s:create_index('idx', {func = box.func.runtimeerror.id, parts = {{1, 'string'}}})
+
+function test_func() return pcall(s.insert, s, {1}) end
+ok, err = test_func()
+preve = err.prev
+gc_err = setmetatable({preve}, {__mode = 'v'})
+err:set_prev(nil)
+err.prev
+collectgarbage('collect')
+--  Still one reference to err.prev so it should not be collected.
+--
+gc_err
+preve = nil
+collectgarbage('collect')
+gc_err
+
+s:drop()
+box.schema.func.drop('runtimeerror')