diff --git a/changelogs/unreleased/implement-on-shutdown-api.md b/changelogs/unreleased/implement-on-shutdown-api.md
new file mode 100644
index 0000000000000000000000000000000000000000..c7d7425796595db80f9c8e96fe514404a28c73f9
--- /dev/null
+++ b/changelogs/unreleased/implement-on-shutdown-api.md
@@ -0,0 +1,5 @@
+## feature/core
+
+*  Implemented on_shutdown API, which allows tarantool module
+   developer to register functions that will be called when
+   tarantool stopped (gh-5723).
\ No newline at end of file
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 76c410a7010bf55953cd1bd5546ed6b560d3801f..fb528b666644a9e0bad34f845c88bf76ca20e407 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -98,6 +98,8 @@ add_library(crc32 STATIC
 )
 target_link_libraries(crc32 cpu_feature)
 
+add_library(shutdown STATIC on_shutdown.c)
+
 set (server_sources
      find_path.c
      curl.c
@@ -142,6 +144,7 @@ set (server_sources
 set(api_headers
     ${CMAKE_BINARY_DIR}/src/trivia/config.h
     ${CMAKE_SOURCE_DIR}/src/trivia/util.h
+    ${CMAKE_SOURCE_DIR}/src/on_shutdown.h
     ${CMAKE_SOURCE_DIR}/src/lib/core/say.h
     ${CMAKE_SOURCE_DIR}/src/lib/core/fiber.h
     ${CMAKE_SOURCE_DIR}/src/lib/core/fiber_cond.h
@@ -187,7 +190,7 @@ target_link_libraries(server core coll http_parser bit uri uuid swim swim_udp
 # Rule of thumb: if exporting a symbol from a static library, list the
 # library here.
 set (reexport_libraries server core misc bitset csv swim swim_udp swim_ev
-     ${LUAJIT_LIBRARIES} ${MSGPUCK_LIBRARIES} ${ICU_LIBRARIES})
+     shutdown ${LUAJIT_LIBRARIES} ${MSGPUCK_LIBRARIES} ${ICU_LIBRARIES})
 
 set (common_libraries
     ${reexport_libraries}
diff --git a/src/exports.h b/src/exports.h
index 1b95e75f627b25990a2e04081cbf50e7d8ec1255..eb72bc928e56fb70a58c00a5aa45046f1d818f4c 100644
--- a/src/exports.h
+++ b/src/exports.h
@@ -48,6 +48,7 @@ EXPORT(box_latch_lock)
 EXPORT(box_latch_new)
 EXPORT(box_latch_trylock)
 EXPORT(box_latch_unlock)
+EXPORT(box_on_shutdown)
 EXPORT(box_region_aligned_alloc)
 EXPORT(box_region_alloc)
 EXPORT(box_region_truncate)
diff --git a/src/on_shutdown.c b/src/on_shutdown.c
new file mode 100644
index 0000000000000000000000000000000000000000..5444f802e38d3f775b43d47c864bf5c56115b985
--- /dev/null
+++ b/src/on_shutdown.c
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2010-2021, Tarantool AUTHORS, please see AUTHORS file.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "on_shutdown.h"
+
+#include "box/box.h"
+#include "say.h"
+
+#include <stdlib.h>
+#include <small/rlist.h>
+#include <errno.h>
+#include <trigger.h>
+
+struct on_shutdown_trigger {
+	struct trigger trigger;
+	/** Shutdown trigger function */
+	int (*handler)(void *);
+	/** Trigger function argument */
+	void *arg;
+	/** link for on_shutdown_trigger_list */
+	struct rlist link;
+};
+
+static struct rlist on_shutdown_trigger_list =
+	RLIST_HEAD_INITIALIZER(on_shutdown_trigger_list);
+
+static int
+trigger_commom_f(struct trigger *trigger, MAYBE_UNUSED void *event)
+{
+	struct on_shutdown_trigger *on_shutdown_trigger =
+		container_of(trigger, struct on_shutdown_trigger, trigger);
+	return on_shutdown_trigger->handler(on_shutdown_trigger->arg);
+}
+
+static int
+on_shutdown_trigger_create(int (*handler)(void *), void *arg)
+{
+	struct on_shutdown_trigger *trigger = (struct on_shutdown_trigger *)
+		malloc(sizeof(struct on_shutdown_trigger));
+	if (trigger == NULL)
+		return -1;
+	trigger_create(&trigger->trigger, trigger_commom_f, NULL, NULL);
+	trigger->handler = handler;
+	trigger->arg = arg;
+	trigger_add(&box_on_shutdown_trigger_list, &trigger->trigger);
+	rlist_add_entry(&on_shutdown_trigger_list, trigger, link);
+	return 0;
+}
+
+API_EXPORT int
+box_on_shutdown(void *arg, int (*new_handler)(void *),
+		int (*old_handler)(void *))
+{
+	struct on_shutdown_trigger *trigger;
+	if (old_handler == NULL) {
+		if (new_handler == NULL) {
+			/*
+			 * Invalid function params, old_handler or new_handler
+			 * must be set
+			 */
+			say_error("Invalid function argument: old_handler and "
+				  "new_handler cannot be equal to zero at the "
+				  "same time.");
+			errno = EINVAL;
+			return -1;
+		}
+		return on_shutdown_trigger_create(new_handler, arg);
+	}
+
+	rlist_foreach_entry(trigger, &on_shutdown_trigger_list, link) {
+		if (trigger->handler == old_handler) {
+			if (new_handler != NULL) {
+				/*
+				 * Change on_shutdown trigger handler, and arg
+				 */
+				trigger->handler = new_handler;
+				trigger->arg = arg;
+			} else {
+				/*
+				 * In case new_handler == NULL
+				 * Remove old on_shutdown trigger and
+				 * destroy it
+				 */
+				trigger_clear(&trigger->trigger);
+				rlist_del_entry(trigger, link);
+				free(trigger);
+			}
+			return 0;
+		}
+	}
+
+	/*
+	 * Here we are in case when we not find on_shutdown trigger,
+	 * which we want to destroy return -1.
+	 */
+	say_error("Invalid function argument: previously registered trigger "
+		  "with handler == old_handler not found.");
+	errno = EINVAL;
+	return -1;
+}
+
+
diff --git a/src/on_shutdown.h b/src/on_shutdown.h
new file mode 100644
index 0000000000000000000000000000000000000000..3afb5767cda48cdc69c201965826d4ba3b1af285
--- /dev/null
+++ b/src/on_shutdown.h
@@ -0,0 +1,66 @@
+#pragma once
+/*
+ * Copyright 2010-2021, Tarantool AUTHORS, please see AUTHORS file.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include <trivia/util.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif /* defined(__cplusplus) */
+
+/** \cond public */
+
+/**
+ * Function, which registers or deletes on_shutdown handler.
+ * @param[in] arg on_shutdown function's argument.
+ * @param[in] new_handler New on_shutdown handler, in
+ *            case this argument is NULL, function finds
+ *            and destroys old on_shutdown handler.
+ * @param[in] old_handler Old on_shutdown handler.
+ * @retval return 0 if success othrewise return -1 and sets
+ *                  errno. There are three cases when
+ *                  function fails:
+ *                  - both old_handler and new_handler are equal to
+ *                    zero (sets errno to EINVAL).
+ *                  - old_handler != NULL, but there is no trigger
+ *                    with such function (sets errno to EINVAL).
+ *                  - malloc for some internal struct memory allocation
+ *                    return NULL (errno sets by malloc to ENOMEM).
+ */
+API_EXPORT int
+box_on_shutdown(void *arg, int (*new_hadler)(void *),
+		int (*old_handler)(void *));
+
+/** \endcond public */
+
+#if defined(__cplusplus)
+} /* extern "C" */
+#endif /* defined(__cplusplus) */
+
diff --git a/test/app/CMakeLists.txt b/test/app/CMakeLists.txt
index 059ee8f3d63b9dd401339760d592efe22a537bcd..7e509bc4c420847933491a388fddd96684e138d7 100644
--- a/test/app/CMakeLists.txt
+++ b/test/app/CMakeLists.txt
@@ -1 +1,2 @@
 build_module(loaderslib loaderslib.c)
+build_module(on_shutdownlib on_shutdownlib.c)
diff --git a/test/app/on_shutdown.lua b/test/app/on_shutdown.lua
new file mode 100644
index 0000000000000000000000000000000000000000..72063552c3d46a4491700ed92273b609102c7375
--- /dev/null
+++ b/test/app/on_shutdown.lua
@@ -0,0 +1,7 @@
+#!/usr/bin/env tarantool
+
+require('console').listen(os.getenv('ADMIN'))
+
+box.cfg({
+    listen = os.getenv("LISTEN"),
+})
diff --git a/test/app/on_shutdown.result b/test/app/on_shutdown.result
new file mode 100644
index 0000000000000000000000000000000000000000..c3f86cdeda9e1a61826d1d4b1553ed1f53c160e1
--- /dev/null
+++ b/test/app/on_shutdown.result
@@ -0,0 +1,136 @@
+-- test-run result file version 2
+env = require('test_run')
+ | ---
+ | ...
+test_run = env.new()
+ | ---
+ | ...
+build_dir = os.getenv("BUILDDIR") .. "/test/app/"
+ | ---
+ | ...
+soext = (jit.os == "OSX" and "dylib" or "so")
+ | ---
+ | ...
+on_shutdownlib = "on_shutdownlib."..soext
+ | ---
+ | ...
+os.execute(string.format("cp %s/%s .", build_dir, on_shutdownlib))
+ | ---
+ | - 0
+ | ...
+
+test_run:cmd('create server test with script="app/on_shutdown.lua"')
+ | ---
+ | - true
+ | ...
+test_run:cmd('start server test')
+ | ---
+ | - true
+ | ...
+test_run:cmd('switch test')
+ | ---
+ | - true
+ | ...
+
+-- Default on_shutdown triggers timeout == 3, but we sets it
+-- here directly to make test clear
+box.ctl.set_on_shutdown_timeout(3)
+ | ---
+ | ...
+module = require('on_shutdownlib')
+ | ---
+ | ...
+-- Setting shutdown timeout for main module fiber function.
+-- This timeout < on_shutdown triggers timeout, so on_shutdown
+-- trigger completes successfully.
+shutdown_timeout = 1
+ | ---
+ | ...
+module.cfg(shutdown_timeout)
+ | ---
+ | ...
+
+test_run:cmd('switch default')
+ | ---
+ | - true
+ | ...
+test_run:cmd('stop server test')
+ | ---
+ | - true
+ | ...
+os.execute(string.format("grep -r \"stop module fiber\" on_shutdown.log"))
+ | ---
+ | - 0
+ | ...
+os.execute(string.format("grep -r \"join module fiber\" on_shutdown.log"))
+ | ---
+ | - 0
+ | ...
+os.execute(string.format("grep -r \"module_fiber_f finished\" \
+			 on_shutdown.log"))
+ | ---
+ | - 0
+ | ...
+os.execute(string.format("grep -r \"join module fiber finished\" \
+			 on_shutdown.log"))
+ | ---
+ | - 0
+ | ...
+test_run:cmd('start server test')
+ | ---
+ | - true
+ | ...
+test_run:cmd('switch test')
+ | ---
+ | - true
+ | ...
+
+box.ctl.set_on_shutdown_timeout(1)
+ | ---
+ | ...
+module = require('on_shutdownlib')
+ | ---
+ | ...
+-- Setting shutdown timeout for main module fiber function.
+-- This timeout > on_shutdown triggers timeout, so on_shutdown
+-- trigger does not have time to complete.
+shutdown_timeout = 10
+ | ---
+ | ...
+module.cfg(shutdown_timeout)
+ | ---
+ | ...
+
+test_run:cmd('switch default')
+ | ---
+ | - true
+ | ...
+test_run:cmd('stop server test')
+ | ---
+ | - true
+ | ...
+os.execute(string.format("rm %s", on_shutdownlib))
+ | ---
+ | - 0
+ | ...
+os.execute(string.format("grep -r \"stop module fiber\" on_shutdown.log"))
+ | ---
+ | - 0
+ | ...
+os.execute(string.format("grep -r \"join module fiber\" on_shutdown.log"))
+ | ---
+ | - 0
+ | ...
+os.execute(string.format("grep -r \"SystemError timed out\" on_shutdown.log"))
+ | ---
+ | - 0
+ | ...
+
+test_run:cmd('cleanup server test')
+ | ---
+ | - true
+ | ...
+test_run:cmd('delete server test')
+ | ---
+ | - true
+ | ...
diff --git a/test/app/on_shutdown.test.lua b/test/app/on_shutdown.test.lua
new file mode 100644
index 0000000000000000000000000000000000000000..c133209b2d661d7d9b9aa21a02b6939e9533c07a
--- /dev/null
+++ b/test/app/on_shutdown.test.lua
@@ -0,0 +1,49 @@
+env = require('test_run')
+test_run = env.new()
+build_dir = os.getenv("BUILDDIR") .. "/test/app/"
+soext = (jit.os == "OSX" and "dylib" or "so")
+on_shutdownlib = "on_shutdownlib."..soext
+os.execute(string.format("cp %s/%s .", build_dir, on_shutdownlib))
+
+test_run:cmd('create server test with script="app/on_shutdown.lua"')
+test_run:cmd('start server test')
+test_run:cmd('switch test')
+
+-- Default on_shutdown triggers timeout == 3, but we sets it
+-- here directly to make test clear
+box.ctl.set_on_shutdown_timeout(3)
+module = require('on_shutdownlib')
+-- Setting shutdown timeout for main module fiber function.
+-- This timeout < on_shutdown triggers timeout, so on_shutdown
+-- trigger completes successfully.
+shutdown_timeout = 1
+module.cfg(shutdown_timeout)
+
+test_run:cmd('switch default')
+test_run:cmd('stop server test')
+os.execute(string.format("grep -r \"stop module fiber\" on_shutdown.log"))
+os.execute(string.format("grep -r \"join module fiber\" on_shutdown.log"))
+os.execute(string.format("grep -r \"module_fiber_f finished\" \
+			 on_shutdown.log"))
+os.execute(string.format("grep -r \"join module fiber finished\" \
+			 on_shutdown.log"))
+test_run:cmd('start server test')
+test_run:cmd('switch test')
+
+box.ctl.set_on_shutdown_timeout(1)
+module = require('on_shutdownlib')
+-- Setting shutdown timeout for main module fiber function.
+-- This timeout > on_shutdown triggers timeout, so on_shutdown
+-- trigger does not have time to complete.
+shutdown_timeout = 10
+module.cfg(shutdown_timeout)
+
+test_run:cmd('switch default')
+test_run:cmd('stop server test')
+os.execute(string.format("rm %s", on_shutdownlib))
+os.execute(string.format("grep -r \"stop module fiber\" on_shutdown.log"))
+os.execute(string.format("grep -r \"join module fiber\" on_shutdown.log"))
+os.execute(string.format("grep -r \"SystemError timed out\" on_shutdown.log"))
+
+test_run:cmd('cleanup server test')
+test_run:cmd('delete server test')
diff --git a/test/app/on_shutdownlib.c b/test/app/on_shutdownlib.c
new file mode 100644
index 0000000000000000000000000000000000000000..1569bffdcb5f895780d729687855330e61f2686a
--- /dev/null
+++ b/test/app/on_shutdownlib.c
@@ -0,0 +1,126 @@
+#include <lua.h>
+#include <lauxlib.h>
+#include <lualib.h>
+#include <luaconf.h>
+
+#include <module.h>
+#include "../unit/unit.h"
+
+struct module {
+	/**
+	 * Flag of the module state, May be -1, 0, 1
+	 * -1 means that module thread failed to start
+	 * 0 means that module currently stop
+	 * 1 menas that module currently running
+	 */
+	int is_running;
+	/**
+	 * Module fiber
+	 */
+	struct fiber *fiber;
+	/**
+	 * Time to sleep, before module fiber finished
+	 */
+	double timeout;
+};
+
+static struct module module;
+
+/** Shutdown function to test on_shutdown API */
+static int
+on_shutdown_module_bad_func(void *arg)
+{
+	fail_unless(0);
+	return 0;
+}
+
+/** Shutdown function to stop the main fiber of the module */
+static int
+on_shutdown_module_stop_func(void *arg)
+{
+	fprintf(stderr, "stop module fiber\n");
+	fiber_wakeup(module.fiber);
+	fprintf(stderr, "join module fiber\n");
+	fiber_join(module.fiber);
+	fprintf(stderr, "join module fiber finished\n");
+	return 0;
+}
+
+/** Main module thread function. */
+static int
+module_fiber_f(va_list arg)
+{
+
+	__atomic_store_n(&module.is_running, 1, __ATOMIC_SEQ_CST);
+	/*
+	 * In first yield we return control until the
+	 * on_shutdown_module_stop_func function is called.
+	 * Sleep required for test purpose: if sleep time >
+	 * on_shutdown_wait_time then timeout in trigger_fiber_run
+	 * will expire and tarantool will shutdown without waiting for the
+	 * module to finish working.
+	 */
+	fiber_yield();
+	fiber_sleep(module.timeout);
+	__atomic_store_n(&module.is_running, 0, __ATOMIC_SEQ_CST);
+	fprintf(stderr, "module_fiber_f finished\n");
+	return 0;
+}
+
+static int
+cfg(lua_State *L)
+{
+	int index = lua_gettop(L);
+	if (index != 1) {
+		lua_pushstring(L, "fuction expected one argument");
+		lua_error(L);
+	}
+	double timeout = luaL_checknumber(L, 1);
+	if (timeout < 0) {
+		lua_pushstring(L, "module shutdown sleep time must be greater "
+			       "then or equal zero");
+		lua_error(L);
+	}
+
+	/** In case module already started do nothing */
+	if (__atomic_load_n(&module.is_running, __ATOMIC_SEQ_CST) == 1)
+		return 0;
+
+	module.timeout = timeout;
+
+	int save_errno = errno;
+	/*
+	 * Invalid function arguments: old_handle and new_hanlder can't
+	 * be equal to zero at the same time.
+	 */
+	fail_unless(box_on_shutdown(&module,
+				    NULL,
+				    NULL) == -1);
+	fail_unless(errno == EINVAL);
+	errno = save_errno;
+	/** Registering the module stop function */
+	fail_unless(box_on_shutdown(&module,
+				    on_shutdown_module_bad_func,
+				    NULL) == 0);
+	/** Changing the module stop function */
+	fail_unless(box_on_shutdown(&module,
+				    on_shutdown_module_stop_func,
+				    on_shutdown_module_bad_func) == 0);
+	module.fiber = fiber_new("fiber", module_fiber_f);
+	fail_unless(module.fiber != NULL);
+	fiber_set_joinable(module.fiber, true);
+	fiber_start(module.fiber);
+	return 0;
+}
+
+static const struct luaL_Reg on_shutdownlib[] = {
+	{"cfg", cfg},
+	{NULL, NULL}
+};
+
+LUALIB_API int
+luaopen_on_shutdownlib(lua_State *L)
+{
+	luaL_openlib(L, "on_shutdownlib", on_shutdownlib, 0);
+	return 0;
+}