diff --git a/CMakeLists.txt b/CMakeLists.txt
index 777083e1fecd0c4ecdf867a9b81437ce90aa9a3f..f9af8888b8ee3b4627ba34fce1407f5b62e7cf7e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -64,6 +64,11 @@ include(cmake/os.cmake)
 include(cmake/compiler.cmake)
 include(cmake/simd.cmake)
 include(cmake/profile.cmake)
+include(cmake/FindReadline.cmake)
+
+if (NOT READLINE_FOUND)
+    message(FATAL_ERROR "readline library not found.")
+endif()
 
 check_symbol_exists(MAP_ANON sys/mman.h HAVE_MAP_ANON)
 check_symbol_exists(MAP_ANONYMOUS sys/mman.h HAVE_MAP_ANONYMOUS)
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 2a8220a154ebfe045f0e87c10ca6b50d1f9c96c1..4da5d26b80a267cd486a46d0d29d9692395fd2f2 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -11,6 +11,7 @@ include_directories(${LIBEV_INCLUDE_DIR})
 include_directories(${LIBEIO_INCLUDE_DIR})
 include_directories(${LIBCORO_INCLUDE_DIR})
 include_directories(${LIBGOPT_INCLUDE_DIR})
+include_directories(${READLINE_INCLUDE_DIR})
 
 # Require pthread globally if compiling with GCC
 if (CMAKE_COMPILER_IS_GNUCC)
@@ -23,6 +24,7 @@ set(lua_sources)
 lua_source(lua_sources lua/uuid.lua)
 lua_source(lua_sources lua/session.lua)
 lua_source(lua_sources lua/msgpackffi.lua)
+lua_source(lua_sources lua/interactive.lua)
 file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/third_party/luafun)
 lua_source(lua_sources ../third_party/luafun/fun.lua)
 set(bin_sources)
@@ -113,6 +115,7 @@ list(APPEND common_libraries
     ${LIBGOPT_LIBRARIES}
     ${LIBCJSON_LIBRARIES}
     ${LIBYAML_LIBRARIES}
+    ${READLINE_LIBRARIES}
     ${LUAJIT_LIB}
     misc
 )
diff --git a/src/ffisyms.cc b/src/ffisyms.cc
index 6b3dc5d202276815036cd2f604012d4e242876ae..b82e23bd4a7d8cf14f3c841300501444f8aa143b 100644
--- a/src/ffisyms.cc
+++ b/src/ffisyms.cc
@@ -4,6 +4,8 @@
 #include <box/tuple.h>
 #include <box/lua/index.h>
 #include <box/lua/call.h>
+#include <readline/readline.h>
+#include <lua/init.h>
 
 /*
  * A special hack to cc/ld to keep symbols in an optimized binary.
@@ -25,5 +27,7 @@ void *ffi_symbols[] = {
 	(void *) port_ffi_create,
 	(void *) port_ffi_destroy,
 	(void *) boxffi_select,
-	(void *) password_prepare
+	(void *) password_prepare,
+	(void *) readline,
+	(void *) tarantool_lua_interactive
 };
diff --git a/src/lua/init.cc b/src/lua/init.cc
index f0307a6fa96ae31504dfdff4ada71ba7c4fb65a9..853061e659beebbf5b49ee4383fa432b1f7b83ee 100644
--- a/src/lua/init.cc
+++ b/src/lua/init.cc
@@ -67,8 +67,10 @@ extern "C" {
 struct lua_State *tarantool_L;
 
 /* contents of src/lua/ files */
-extern char uuid_lua[], session_lua[], msgpackffi_lua[], fun_lua[];
-static const char *lua_sources[] = { uuid_lua, session_lua, NULL };
+extern char uuid_lua[], session_lua[], msgpackffi_lua[], fun_lua[],
+       interactive_lua[];
+static const char *lua_sources[] = { uuid_lua, session_lua, interactive_lua,
+	NULL };
 static const char *lua_modules[] = { "msgpackffi", msgpackffi_lua,
 	"fun", fun_lua, NULL };
 /*
@@ -354,16 +356,6 @@ tarantool_lua_dostring(struct lua_State *L, const char *str)
 	return 0;
 }
 
-static int
-tarantool_lua_dofile(struct lua_State *L, const char *filename)
-{
-	lua_getglobal(L, "dofile");
-	lua_pushstring(L, filename);
-	lbox_pcall(L);
-	bool result = lua_toboolean(L, 1);
-	return result ? 0 : 1;
-}
-
 extern "C" {
 	int yamlL_encode(lua_State*);
 };
@@ -432,6 +424,17 @@ tarantool_lua(struct lua_State *L,
 	}
 }
 
+extern "C" void
+tarantool_lua_interactive(char *line)
+{
+	struct tbuf *out = tbuf_new(&fiber()->gc);
+	struct lua_State *L = lua_newthread(tarantool_L);
+	tarantool_lua(L, out, line);
+	lua_pop(tarantool_L, 1);
+	printf("%.*s", out->size, out->data);
+	fiber_gc();
+}
+
 /**
  * Check if the given literal is a number/boolean or string
  * literal. A string literal needs quotes.
@@ -569,12 +572,17 @@ run_script(va_list ap)
 	if (access(path, F_OK) == 0) {
 		say_info("loading %s", path);
 		/* Execute the init file. */
-		if (tarantool_lua_dofile(L, path))
-			panic("%s", lua_tostring(L, -1));
-
-		/* clear the stack from return values. */
-		lua_settop(L, 0);
+		lua_getglobal(L, "dofile");
+		lua_pushstring(L, path);
+	} else {
+		lua_getglobal(L, "interactive");
 	}
+	lbox_pcall(L);
+	if (! lua_toboolean(L, 1))
+		panic("%s", lua_tostring(L, -1));
+
+	/* clear the stack from return values. */
+	lua_settop(L, 0);
 	/*
 	 * The file doesn't exist. It's OK, tarantool may
 	 * have no init file.
@@ -590,8 +598,7 @@ run_script(va_list ap)
 void
 tarantool_lua_run_script(char *path)
 {
-	if (path == NULL)
-		return;
+	const char *title = path ? basename(path) : "interactive";
 	/*
 	 * init script can call box.fiber.yield (including implicitly via
 	 * box.insert, box.update, etc...), but box.fiber.yield() today,
@@ -599,7 +606,7 @@ tarantool_lua_run_script(char *path)
 	 * To work this problem around we must run init script in
 	 * a separate fiber.
 	 */
-	struct fiber *loader = fiber_new(basename(path), run_script);
+	struct fiber *loader = fiber_new(title, run_script);
 	fiber_call(loader, tarantool_L, path);
 
 	/*
diff --git a/src/lua/init.h b/src/lua/init.h
index 00944ac013f5e0f7ade25e49c544a733779607ba..34d2fb3b7091c13cafb213096d415b9794596b63 100644
--- a/src/lua/init.h
+++ b/src/lua/init.h
@@ -87,4 +87,10 @@ void
 tarantool_lua(struct lua_State *L,
 	      struct tbuf *out, const char *str);
 
+/**
+ * Eval line and print output.
+ */
+extern "C" void
+tarantool_lua_interactive(char *line);
+
 #endif /* INCLUDES_TARANTOOL_LUA_H */
diff --git a/src/lua/interactive.lua b/src/lua/interactive.lua
new file mode 100644
index 0000000000000000000000000000000000000000..5cf219356b2a612aa3599ee9a8a375e6b553c8b0
--- /dev/null
+++ b/src/lua/interactive.lua
@@ -0,0 +1,18 @@
+local ffi = require('ffi')
+ffi.cdef([[
+    char *readline(const char *prompt);
+    void tarantool_lua_interactive(char *);
+    void free(void *ptr);
+]])
+
+function interactive()
+    while true do
+        line = ffi.C.readline("tarantool> ")
+        if line then
+            ffi.C.tarantool_lua_interactive(line)
+            ffi.C.free(line)
+        else
+            break;
+        end
+    end
+end
diff --git a/src/tarantool.cc b/src/tarantool.cc
index 255f8770ed0763e5584b813dc27e42612caca1bd..4e85176ecd2b72efe992b54e9d49ca2ae084e36b 100644
--- a/src/tarantool.cc
+++ b/src/tarantool.cc
@@ -570,7 +570,7 @@ main(int argc, char **argv)
 	__libc_stack_end = (void*) &argv;
 #endif
 
-	if (argc <= 1 || access(argv[1], R_OK) != 0) {
+	if (argc > 1 && access(argv[1], R_OK) != 0) {
 		void *opt = gopt_sort(&argc, (const char **)argv, opt_def);
 		if (gopt(opt, 'V')) {
 			printf("Tarantool %s\n", tarantool_version());
@@ -612,9 +612,11 @@ main(int argc, char **argv)
 	 * - in case one uses #!/usr/bin/env tarantool
 	 *   such options (in script line) don't work
 	 */
-	argv++;
-	argc--;
-	script = abspath(argv[0]);
+	if (argc > 1) {
+		argv++;
+		argc--;
+		script = abspath(argv[0]);
+	}
 
 	random_init();
 	say_init(argv[0]);