diff --git a/src/box/box.cc b/src/box/box.cc
index 139796c8cd6fc3355d74ac340706d9f3daa2ff5a..7fb44b1946d53acbab6f8afd26ab4a620ae46ec6 100644
--- a/src/box/box.cc
+++ b/src/box/box.cc
@@ -50,12 +50,15 @@
 #include "user.h"
 #include "cfg.h"
 #include "iobuf.h"
+#include "evio.h"
 
 static void process_ro(struct request *request, struct port *port);
 box_process_func box_process = process_ro;
 
 struct recovery_state *recovery;
 
+static struct evio_service binary; /* iproto binary listener */
+
 int snapshot_pid = 0; /* snapshot processes pid */
 static void
 process_ro(struct request *request, struct port *port)
@@ -94,8 +97,8 @@ recover_row(struct recovery_state *r, void *param, struct xrow_header *row)
 
 /* {{{ configuration bindings */
 
-void
-box_check_replication_source(const char *source)
+static void
+box_check_uri(const char *source, const char *what)
 {
 	if (source == NULL)
 		return;
@@ -103,7 +106,7 @@ box_check_replication_source(const char *source)
 
 	/* URI format is [host:]service */
 	if (uri_parse(&uri, source) || !uri.service) {
-		tnt_raise(ClientError, ER_CFG, "replication source, "
+		tnt_raise(ClientError, ER_CFG, what,
 			  "expected host:service or /unix.socket");
 	}
 }
@@ -113,29 +116,28 @@ box_check_wal_mode(const char *mode_name)
 {
 	assert(mode_name != NULL); /* checked in Lua */
 	int mode = strindex(wal_mode_STRS, mode_name, WAL_MODE_MAX);
-	if (mode == WAL_MODE_MAX) {
-		tnt_raise(ClientError, ER_CFG,
-			  "wal_mode is not recognized");
-	}
+	if (mode == WAL_MODE_MAX)
+		tnt_raise(ClientError, ER_CFG, "wal_mode", mode_name);
 }
 
 void
 box_check_config()
 {
 	box_check_wal_mode(cfg_gets("wal_mode"));
-	box_check_replication_source(cfg_gets("replication_source"));
+	box_check_uri(cfg_gets("listen"), "listen");
+	box_check_uri(cfg_gets("replication_source"), "replication_source");
 
 	/* check rows_per_wal configuration */
 	if (cfg_geti("rows_per_wal") <= 1) {
-		tnt_raise(ClientError, ER_CFG,
-			  "rows_per_wal must be greater than one");
+		tnt_raise(ClientError, ER_CFG, "rows_per_wal",
+			  "must be greater than one");
 	}
 }
 
 extern "C" void
 box_set_replication_source(const char *source)
 {
-	box_check_replication_source(source);
+	box_check_uri(source, "replication_source");
 	bool old_is_replica = recovery->remote.reader;
 	bool new_is_replica = source != NULL;
 
@@ -162,6 +164,20 @@ box_set_replication_source(const char *source)
 	}
 }
 
+extern "C" void
+box_set_listen(const char *uri)
+{
+	box_check_uri(uri, "listen");
+	if (evio_service_is_active(&binary))
+		evio_service_stop(&binary);
+
+	if (uri != NULL) {
+		evio_service_start(&binary, uri);
+	} else {
+		box_leave_local_standby_mode(NULL);
+	}
+}
+
 extern "C" void
 box_set_wal_mode(const char *mode_name)
 {
@@ -170,8 +186,8 @@ box_set_wal_mode(const char *mode_name)
 		strindex(wal_mode_STRS, mode_name, WAL_MODE_MAX);
 	if (mode != recovery->wal_mode &&
 	    (mode == WAL_FSYNC || recovery->wal_mode == WAL_FSYNC)) {
-		tnt_raise(ClientError, ER_CFG,
-			  "wal_mode cannot switch to/from fsync");
+		tnt_raise(ClientError, ER_CFG, "wal_mode",
+			  "cannot switch to/from fsync");
 	}
 	recovery_update_mode(recovery, mode);
 }
@@ -455,15 +471,13 @@ box_init()
 			      cfg_getd("wal_dir_rescan_delay"));
 	title("hot_standby", NULL);
 
+	iproto_init(&binary);
 	const char *listen = cfg_gets("listen");
 	/*
 	 * application server configuration).
 	 */
-	if (listen) {
-		iproto_init(listen);
-	} else {
+	if (listen == NULL)
 		box_leave_local_standby_mode(NULL);
-	}
 	iobuf_set_readahead(cfg_geti("readahead"));
 }
 
diff --git a/src/box/box.h b/src/box/box.h
index 4729c99d10ed36f7f5d13affaf51c53b5431b102..a7a121db5a684f1f74932cc76aa43dc773be1f63 100644
--- a/src/box/box.h
+++ b/src/box/box.h
@@ -116,6 +116,9 @@ box_process_subscribe(int fd, struct xrow_header *header);
 void
 box_check_config();
 
+void
+box_set_listen(const char *uri);
+
 void
 box_set_replication_source(const char *source);
 
diff --git a/src/box/errcode.h b/src/box/errcode.h
index 7d0f973c1a280d46e0aa7be23013e69cffa1ba7c..3ab47f57545253428f774993f65d986ad7aaf473 100644
--- a/src/box/errcode.h
+++ b/src/box/errcode.h
@@ -110,7 +110,7 @@ struct errcode_record {
 	/* 56 */_(ER_USER_MAX,			2, "A limit on the total number of users has been reached: %u") \
 	/* 57 */_(ER_NO_SUCH_ENGINE,		2, "Space engine '%s' does not exist") \
 	/* 58 */_(ER_RELOAD_CFG,		2, "Can't set option '%s' dynamically") \
-	/* 59 */_(ER_CFG,			2, "Incorrect option value: %s") \
+	/* 59 */_(ER_CFG,			2, "Incorrect '%s' option value: %s") \
 	/* 60 */_(ER_SOPHIA,			2, "%s") \
 	/* 61 */_(ER_LOCAL_SERVER_IS_NOT_ACTIVE,2, "Local server is not active") \
 	/* 62 */_(ER_UNKNOWN_SERVER,		2, "Server %u is not registered with the cluster") \
diff --git a/src/box/iproto.cc b/src/box/iproto.cc
index c9987c62c2c048c0c11526390ae3ee94a9206011..48d000965f5bbec2db9fe0e6ffd4885e61e1e6ec 100644
--- a/src/box/iproto.cc
+++ b/src/box/iproto.cc
@@ -821,24 +821,17 @@ static void on_bind(void *arg __attribute__((unused)))
 
 /** Initialize a read-write port. */
 void
-iproto_init(const char *uri)
+iproto_init(struct evio_service *service)
 {
-	/* Run a primary server. */
-	if (!uri)
-		return;
-
-	static struct evio_service primary;
-	evio_service_init(loop(), &primary, "primary",
-			  uri,
-			  iproto_on_accept, NULL);
-	evio_service_on_bind(&primary, on_bind, NULL);
-	evio_service_start(&primary);
-
 	mempool_create(&iproto_request_pool, &cord()->slabc,
 		       sizeof(struct iproto_request));
 	iproto_queue_init(&request_queue);
 	mempool_create(&iproto_connection_pool, &cord()->slabc,
 		       sizeof(struct iproto_connection));
+
+	evio_service_init(loop(), service, "binary",
+			  iproto_on_accept, NULL);
+	evio_service_on_bind(service, on_bind, NULL);
 }
 
 /* vim: set foldmethod=marker */
diff --git a/src/box/iproto.h b/src/box/iproto.h
index 9df10ceaa9cd0d9752f190e2e2b037cec2c2243c..fe7d5db3cf1fe9043548ba18551a3d5a43cb73ec 100644
--- a/src/box/iproto.h
+++ b/src/box/iproto.h
@@ -29,5 +29,5 @@
  * SUCH DAMAGE.
  */
 void
-iproto_init(const char *uri);
+iproto_init(struct evio_service *service);
 #endif
diff --git a/src/box/lua/load_cfg.lua b/src/box/lua/load_cfg.lua
index ba8d14e7f7f3a41a97692315169fdf83bb0f7b58..2ad9abc38982392df9b652b622de9c2d0dc24e46 100644
--- a/src/box/lua/load_cfg.lua
+++ b/src/box/lua/load_cfg.lua
@@ -5,6 +5,7 @@ ffi.cdef([[
 void check_cfg();
 void load_cfg();
 void box_set_wal_mode(const char *mode);
+void box_set_listen(const char *uri);
 void box_set_replication_source(const char *source);
 void box_set_log_level(int level);
 void box_set_io_collect_interval(double interval);
@@ -114,6 +115,7 @@ local modify_cfg = {
 -- dynamically settable options
 local dynamic_cfg = {
     wal_mode                = ffi.C.box_set_wal_mode,
+    listen                  = ffi.C.box_set_listen,
     replication_source      = ffi.C.box_set_replication_source,
     log_level               = ffi.C.box_set_log_level,
     io_collect_interval     = ffi.C.box_set_io_collect_interval,
@@ -185,14 +187,16 @@ local function apply_default_cfg(cfg, default_cfg)
     end
 end
 
-local function reload_cfg(oldcfg, newcfg)
-    newcfg = prepare_cfg(newcfg, default_cfg, template_cfg, modify_cfg)
-    for key, val in pairs(newcfg) do
+local function reload_cfg(oldcfg, cfg)
+    local newcfg = prepare_cfg(cfg, default_cfg, template_cfg, modify_cfg)
+    -- iterate over original table because prepare_cfg() may store NILs
+    for key in pairs(cfg) do
         if dynamic_cfg[key] == nil then
             box.error(box.error.RELOAD_CFG, key);
         end
     end
-    for key, val in pairs(newcfg) do
+    for key in pairs(cfg) do
+        local val = newcfg[key]
         if oldcfg[key] ~= val then
             dynamic_cfg[key](val)
             rawset(oldcfg, key, val)
diff --git a/src/box/lua/snapshot_daemon.lua b/src/box/lua/snapshot_daemon.lua
index 397069b2d3c1b204d0f9f908d35dd28d733931ad..5e281e33712561cfd09ab6f22faa7178469d760f 100644
--- a/src/box/lua/snapshot_daemon.lua
+++ b/src/box/lua/snapshot_daemon.lua
@@ -227,8 +227,7 @@ do
 
             set_snapshot_count = function(snapshot_count)
                 if math.floor(snapshot_count) ~= snapshot_count then
-                    box.error(box.error.CFG,
-                        "snapshot_count must be integer")
+                    box.error(box.error.CFG, "snapshot_count", "must be integer")
                 end
                 daemon.snapshot_count = snapshot_count
                 reload(daemon)
diff --git a/src/coio.cc b/src/coio.cc
index 81d7c4dbabf6c5a8db058e99630779ffaad4ac14..8e4f107fdc1899e672fdc9f574ac5060ef0e5ed4 100644
--- a/src/coio.cc
+++ b/src/coio.cc
@@ -657,10 +657,9 @@ coio_service_on_accept(struct evio_service *evio_service,
 
 void
 coio_service_init(struct coio_service *service, const char *name,
-		  const char *uri,
 		  void (*handler)(va_list ap), void *handler_param)
 {
-	evio_service_init(loop(), &service->evio_service, name, uri,
+	evio_service_init(loop(), &service->evio_service, name,
 			  coio_service_on_accept, service);
 	service->handler = handler;
 	service->handler_param = handler_param;
diff --git a/src/coio.h b/src/coio.h
index 715a841f42f10fe6c5431bc2fa0c35e2f1499bc7..417f05911f0bc785c41c60ede6fa5ac70644d18c 100644
--- a/src/coio.h
+++ b/src/coio.h
@@ -151,7 +151,6 @@ coio_recvfrom_timeout(struct ev_io *coio, void *buf, size_t sz, int flags,
 
 void
 coio_service_init(struct coio_service *service, const char *name,
-		  const char *uri,
 		  void (*handler)(va_list ap), void *handler_param);
 
 
diff --git a/src/evio.cc b/src/evio.cc
index 1d3cbead4a9809642e2d4b7255687c67215ab44d..211d473329c071836e100ef65e40fa3d9d9f586d 100644
--- a/src/evio.cc
+++ b/src/evio.cc
@@ -285,7 +285,6 @@ evio_service_timer_cb(ev_loop *loop, ev_timer *watcher, int /* revents */)
 void
 evio_service_init(ev_loop *loop,
 		  struct evio_service *service, const char *name,
-		  const char *uri,
 		  void (*on_accept)(struct evio_service *, int,
 				    struct sockaddr *, socklen_t),
 		  void *on_accept_param)
@@ -295,17 +294,6 @@ evio_service_init(ev_loop *loop,
 
 	service->loop = loop;
 
-	struct uri u;
-	if (uri_parse(&u, uri) || u.service == NULL)
-		tnt_raise(SocketError, -1, "invalid uri for bind: %s", uri);
-
-	snprintf(service->serv, sizeof(service->serv), "%.*s",
-		 (int) u.service_len, u.service);
-	if (u.host != NULL && strncmp(u.host, "*", u.host_len) != 0) {
-		snprintf(service->host, sizeof(service->host), "%.*s",
-			(int) u.host_len, u.host);
-	} /* else { service->host[0] = '\0'; } */
-
 	service->on_accept = on_accept;
 	service->on_accept_param = on_accept_param;
 	/*
@@ -323,10 +311,23 @@ evio_service_init(ev_loop *loop,
  * binding periodically.
  */
 void
-evio_service_start(struct evio_service *service)
+evio_service_start(struct evio_service *service, const char *uri)
 {
+	struct uri u;
+	if (uri_parse(&u, uri) || u.service == NULL)
+		tnt_raise(SocketError, -1, "invalid uri for bind: %s", uri);
+
+	snprintf(service->serv, sizeof(service->serv), "%.*s",
+		 (int) u.service_len, u.service);
+	if (u.host != NULL && strncmp(u.host, "*", u.host_len) != 0) {
+		snprintf(service->host, sizeof(service->host), "%.*s",
+			(int) u.host_len, u.host);
+	} /* else { service->host[0] = '\0'; } */
+
 	assert(! ev_is_active(&service->ev));
 
+	say_info("%s: started", evio_service_name(service));
+
 	if (evio_service_bind_and_listen(service)) {
 		/* Try again after a delay. */
 		say_warn("%s: %s is already in use, will "
@@ -345,6 +346,8 @@ evio_service_start(struct evio_service *service)
 void
 evio_service_stop(struct evio_service *service)
 {
+	say_info("%s: stopped", evio_service_name(service));
+
 	if (! ev_is_active(&service->ev)) {
 		ev_timer_stop(service->loop, &service->timer);
 	} else {
diff --git a/src/evio.h b/src/evio.h
index 906b968ca8c4ca56f1ec7c21d893649d5504e63c..f0ee5be47dd707810cb7fb768402de2967f95548 100644
--- a/src/evio.h
+++ b/src/evio.h
@@ -101,7 +101,6 @@ struct evio_service
 void
 evio_service_init(ev_loop *loop,
 		  struct evio_service *service, const char *name,
-		  const char *uri,
 		  void (*on_accept)(struct evio_service *,
 				    int, struct sockaddr *, socklen_t),
 		  void *on_accept_param);
@@ -118,7 +117,7 @@ evio_service_on_bind(struct evio_service *service,
 
 /** Bind to the port and begin listening. */
 void
-evio_service_start(struct evio_service *service);
+evio_service_start(struct evio_service *service, const char *uri);
 
 /** If started, stop event flow and close the acceptor socket. */
 void
@@ -130,6 +129,12 @@ evio_socket(struct ev_io *coio, int domain, int type, int protocol);
 void
 evio_close(ev_loop *loop, struct ev_io *evio);
 
+static inline bool
+evio_service_is_active(struct evio_service *service)
+{
+	return ev_is_active(&service->ev) || ev_is_active(&service->timer);
+}
+
 static inline bool
 evio_is_active(struct ev_io *ev)
 {
diff --git a/src/ffisyms.cc b/src/ffisyms.cc
index 3e9f0f65b1791ff1db19fbca06b0f99938bb99ce..7915561f3def013c87769dd4200e265acdb9ee1b 100644
--- a/src/ffisyms.cc
+++ b/src/ffisyms.cc
@@ -41,6 +41,7 @@ void *ffi_symbols[] = {
 	(void *) password_prepare,
 	(void *) tarantool_error_message,
 	(void *) load_cfg,
+	(void *) box_set_listen,
 	(void *) box_set_replication_source,
 	(void *) box_set_wal_mode,
 	(void *) box_set_log_level,
diff --git a/test/app/cfg.result b/test/app/cfg.result
index 1b10cb8f4dbb76a40fd96bfcdf3b30f015c09f0b..86808fe7f6ca7e8bb2f1cbf87844ef1316cc2802 100644
--- a/test/app/cfg.result
+++ b/test/app/cfg.result
@@ -1,9 +1,10 @@
 TAP version 13
-1..18
+1..21
 ok - box is not started
 ok - invalid replication_source
 ok - invalid wal_mode
 ok - invalid rows_per_wal
+ok - invalid listen
 ok - box is not started
 ok - exception on unconfigured box
 ok - sophia_dir is not auto-created
@@ -18,3 +19,5 @@ ok - work_dir is invalid
 ok - sophia_dir is invalid
 ok - snap_dir is invalid
 ok - wal_dir is invalid
+ok - dynamic listen
+ok - dynamic listen
diff --git a/test/app/cfg.test.lua b/test/app/cfg.test.lua
index 286318fdcaf2abe95f5f88782a17e25fdb1f8e4f..1b6d2c79698422b2f3602d99f3ebadf7c4572864 100755
--- a/test/app/cfg.test.lua
+++ b/test/app/cfg.test.lua
@@ -2,7 +2,8 @@
 
 local tap = require('tap')
 local test = tap.test('cfg')
-test:plan(18)
+local socket = require('socket')
+test:plan(21)
 
 --------------------------------------------------------------------------------
 -- Invalid values
@@ -12,12 +13,13 @@ test:is(type(box.cfg), 'function', 'box is not started')
 
 local function invalid(name, val)
     local status, result = pcall(box.cfg, {[name]=val})
-    test:ok(not status and result:match('Incorrect option'), 'invalid '..name)
+    test:ok(not status and result:match('Incorrect'), 'invalid '..name)
 end
 
 invalid('replication_source', '//guest@localhost:3301')
 invalid('wal_mode', 'invalid')
 invalid('rows_per_wal', -1)
+invalid('listen', '//!')
 
 test:is(type(box.cfg), 'function', 'box is not started')
 
@@ -86,5 +88,18 @@ script:write([[ box.cfg{ logger="tarantool.log", wal_dir='invalid' } ]])
 script:close()
 test:isnt(os.execute("/bin/sh -c 'tarantool ./script.lua 2> /dev/null'"), 0, 'wal_dir is invalid')
 
+-- box.cfg { listen = xx }
+local path = './tarantool.sock'
+os.remove(path)
+box.cfg{ listen = 'unix/:'..path }
+s = socket.tcp_connect('unix/', path)
+test:isnt(s, nil, "dynamic listen")
+if s then s:close() end
+box.cfg{ listen = '' }
+s = socket.tcp_connect('unix/', path)
+test:isnil(s, 'dynamic listen')
+if s then s:close() end
+os.remove(path)
+
 test:check()
 os.exit(0)
diff --git a/test/box/cfg.result b/test/box/cfg.result
index fa118a555eecadfae23eed7f733d351efa719017..dccf7e612cfb4cbb11b7438690e0eabd85431a8b 100644
--- a/test/box/cfg.result
+++ b/test/box/cfg.result
@@ -2,7 +2,7 @@
 --# push filter 'admin: .*' to 'admin: <uri>'
 box.cfg.nosuchoption = 1
 ---
-- error: '[string "-- load_cfg.lua - internal file..."]:243: Attempt to modify a read-only
+- error: '[string "-- load_cfg.lua - internal file..."]:247: Attempt to modify a read-only
     table'
 ...
 t = {} for k,v in pairs(box.cfg) do if type(v) ~= 'table' and type(v) ~= 'function' then table.insert(t, k..': '..tostring(v)) end end
@@ -35,6 +35,8 @@ t
 -- must be read-only
 box.cfg()
 ---
+- error: '[string "-- load_cfg.lua - internal file..."]:193: bad argument #1 to ''pairs''
+    (table expected, got nil)'
 ...
 t = {} for k,v in pairs(box.cfg) do if type(v) ~= 'table' and type(v) ~= 'function' then table.insert(t, k..': '..tostring(v)) end end
 ---
@@ -66,23 +68,23 @@ t
 -- check that cfg with unexpected parameter fails.
 box.cfg{sherlock = 'holmes'}
 ---
-- error: '[string "-- load_cfg.lua - internal file..."]:147: Error: cfg parameter
+- error: '[string "-- load_cfg.lua - internal file..."]:149: Error: cfg parameter
     ''sherlock'' is unexpected'
 ...
 -- check that cfg with unexpected type of parameter failes
 box.cfg{listen = {}}
 ---
-- error: '[string "-- load_cfg.lua - internal file..."]:167: Error: cfg parameter
+- error: '[string "-- load_cfg.lua - internal file..."]:169: Error: cfg parameter
     ''listen'' should be one of types: string, number'
 ...
 box.cfg{wal_dir = 0}
 ---
-- error: '[string "-- load_cfg.lua - internal file..."]:161: Error: cfg parameter
+- error: '[string "-- load_cfg.lua - internal file..."]:163: Error: cfg parameter
     ''wal_dir'' should be of type string'
 ...
 box.cfg{coredump = 'true'}
 ---
-- error: '[string "-- load_cfg.lua - internal file..."]:161: Error: cfg parameter
+- error: '[string "-- load_cfg.lua - internal file..."]:163: Error: cfg parameter
     ''coredump'' should be of type boolean'
 ...
 --# clear filter
diff --git a/test/box/snapshot_daemon.result b/test/box/snapshot_daemon.result
index 8959177f47a4ddd4c543b30ebdcdfc4c3e4c22bf..47160c910087475bc19bfba24259fb19aa027652 100644
--- a/test/box/snapshot_daemon.result
+++ b/test/box/snapshot_daemon.result
@@ -114,7 +114,7 @@ PERIOD
 ...
 box.cfg{ snapshot_count = .2 }
 ---
-- error: 'Incorrect option value: snapshot_count must be integer'
+- error: 'Incorrect ''snapshot_count'' option value: must be integer'
 ...
 daemon = box.internal.snapshot_daemon
 ---