diff --git a/src/lua/init.cc b/src/lua/init.cc
index 02fa51d7e6f357c9db52882451932aa0264b0a3b..17d3cc78f5b90381bcd840056cee92a83c3d8d68 100644
--- a/src/lua/init.cc
+++ b/src/lua/init.cc
@@ -192,6 +192,15 @@ readline_cb(va_list ap)
 {
 	const char **line = va_arg(ap, const char **);
 	const char *prompt = va_arg(ap, const char *);
+	/*
+	 * libeio threads blocks all signals by default. Therefore, nobody
+	 * can interrupt read(2) syscall inside readline() to correctly
+	 * cleanup resources and restore terminal state. In case of signal
+	 * a signal_cb(), a ev watcher in tarantool.cc will stop event
+	 * loop and and stop entire process by exiting the main thread.
+	 * rl_cleanup_after_signal() is called from tarantool_lua_free()
+	 * in order to restore terminal state.
+	 */
 	*line = readline(prompt);
 	return 0;
 }
@@ -303,6 +312,12 @@ tarantool_lua_init(const char *tarantool_bin, int argc, char **argv)
 	luaopen_json(L);
 	lua_pop(L, 1);
 
+	/*
+	 * Disable libreadline signals handlers. All signals are handled in
+	 * main thread by libev watchers.
+	 */
+	rl_catch_signals = 0;
+	rl_catch_sigwinch = 0;
 	static const struct luaL_reg consolelib[] = {
 		{"readline", tarantool_console_readline},
 		{"add_history", tarantool_console_add_history},
@@ -443,5 +458,10 @@ tarantool_lua_free()
 		lua_close(tarantool_L);
 	}
 	tarantool_L = NULL;
+
+	if (isatty(STDIN_FILENO)) {
+		/* See comments in readline_cb() */
+		rl_cleanup_after_signal();
+	}
 }
 
diff --git a/src/tarantool.cc b/src/tarantool.cc
index c7c4830871c438ddc61351da2d4ce48f5b52749a..138e66611e2b46cca47ed7b3e9a1d90a0daed2bc 100644
--- a/src/tarantool.cc
+++ b/src/tarantool.cc
@@ -75,6 +75,7 @@ int main_argc;
 /** Signals handled after start as part of the event loop. */
 static ev_signal ev_sigs[4];
 static const int ev_sig_count = sizeof(ev_sigs)/sizeof(*ev_sigs);
+static bool start_loop = true;
 
 extern const void *opt_def;
 
@@ -153,6 +154,7 @@ signal_cb(ev_loop *loop, struct ev_signal *w, int revents)
 	(void) w;
 	(void) revents;
 
+	start_loop = false;
 	/* Terminate the main event loop */
 	ev_break(loop, EVBREAK_ALL);
 }
@@ -234,13 +236,6 @@ signal_free(void)
 		ev_signal_stop(loop(), &ev_sigs[i]);
 }
 
-static void
-signal_start(void)
-{
-	for (int i = 0; i < ev_sig_count; i++)
-		ev_signal_start(loop(), &ev_sigs[i]);
-}
-
 /** Make sure the child has a default signal disposition. */
 static void
 signal_reset()
@@ -260,6 +255,9 @@ signal_reset()
 	    sigaction(SIGFPE, &sa, NULL) == -1)
 		say_syserror("sigaction");
 
+	for (int i = 0; i < ev_sig_count; i++)
+		ev_signal_stop(loop(), &ev_sigs[i]);
+
 	/* Unblock any signals blocked by libev. */
 	sigset_t sigset;
 	sigfillset(&sigset);
@@ -302,6 +300,8 @@ signal_init(void)
 	ev_signal_init(&ev_sigs[1], signal_cb, SIGINT);
 	ev_signal_init(&ev_sigs[2], signal_cb, SIGTERM);
 	ev_signal_init(&ev_sigs[3], signal_cb, SIGHUP);
+	for (int i = 0; i < ev_sig_count; i++)
+		ev_signal_start(loop(), &ev_sigs[i]);
 
 	(void) tt_pthread_atfork(NULL, NULL, signal_reset);
 }
@@ -367,6 +367,12 @@ background()
 	if (setsid() == -1)
 		goto error;
 
+	/*
+	 * reinit signals after fork, because fork() implicitly calls
+	 * signal_reset() via pthread_atfork() hook installed by signal_init().
+	 */
+	signal_init();
+
 	/* reinit coeio after fork (because libeio required it) */
 	coeio_reinit();
 	/*
@@ -610,9 +616,6 @@ main(int argc, char **argv)
 	main_argc = argc;
 	main_argv = argv;
 
-	/* main core cleanup routine */
-	atexit(tarantool_free);
-
 	fiber_init();
 	/* Init iobuf library with default readahead */
 	iobuf_init();
@@ -620,7 +623,9 @@ main(int argc, char **argv)
 	signal_init();
 	tarantool_lua_init(tarantool_bin, main_argc, main_argv);
 
-	bool start_loop = false;
+	/* main core cleanup routine */
+	atexit(tarantool_free);
+
 	try {
 		int events = ev_activecnt(loop());
 		/*
@@ -630,12 +635,16 @@ main(int argc, char **argv)
 		 * initialized.
 		 */
 		tarantool_lua_run_script(script);
-		start_loop = ev_activecnt(loop()) > events;
+		/*
+		 * Start event loop after executing Lua script if signal_cb()
+		 * wasn't triggered and there is some new events. Initial value
+		 * of start_loop can be set to false by signal_cb().
+		 */
+		start_loop = start_loop && ev_activecnt(loop()) > events;
 		region_free(&fiber()->gc);
 		if (start_loop) {
 			say_crit("entering the event loop");
 			ev_now_update(loop());
-			signal_start();
 			ev_run(loop(), 0);
 		}
 	} catch (Exception *e) {