diff --git a/src/box/box.cc b/src/box/box.cc
index a4af9caa02bd1d899767e99a25a3db0a49dca52c..8c9f0b41c64e73de8d6a812b3c3bb5d456e4e713 100644
--- a/src/box/box.cc
+++ b/src/box/box.cc
@@ -90,10 +90,22 @@ static void
 process_ro(struct port *port, struct request *request)
 {
 	if (!iproto_type_is_select(request->type))
-		tnt_raise(LoggedError, ER_SECONDARY);
+		tnt_raise(LoggedError, ER_READONLY);
 	return process_rw(port, request);
 }
 
+void
+box_set_ro(bool ro)
+{
+	box_process = ro ? process_ro : process_rw;
+}
+
+bool
+box_is_ro(void)
+{
+	return box_process == process_ro;
+}
+
 static void
 recover_row(void *param __attribute__((unused)), struct xrow_header *row)
 {
@@ -237,7 +249,6 @@ box_leave_local_standby_mode(void *data __attribute__((unused)))
 	stat_cleanup(stat_base, IPROTO_TYPE_DML_MAX);
 	box_set_wal_mode(cfg_gets("wal_mode"));
 
-	box_process = process_rw;
 	if (recovery_has_remote(recovery))
 		recovery_follow_remote(recovery);
 
diff --git a/src/box/box.h b/src/box/box.h
index 8bb7f8751759d976a8ceb68bf10399583b88fe08..970dad6d78e101a27876755f1fe77989e5535e66 100644
--- a/src/box/box.h
+++ b/src/box/box.h
@@ -62,7 +62,12 @@ void box_free(void);
 typedef void (*box_process_func)(struct port *port, struct request *request);
 /** For read-write operations. */
 extern box_process_func box_process;
-/** For read-only port. */
+
+void
+box_set_ro(bool ro);
+
+bool
+box_is_ro(void);
 
 /** Non zero if snapshot is in progress. */
 extern int snapshot_pid;
diff --git a/src/box/cluster.cc b/src/box/cluster.cc
index 5f142605a6ae0a8086165eb6824972c92bf60e2c..356f49ff313f4fa86dfe74d4322b4fbe4edf9c21 100644
--- a/src/box/cluster.cc
+++ b/src/box/cluster.cc
@@ -26,6 +26,7 @@
  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
+#include "box.h"
 #include "cluster.h"
 #include "recovery.h"
 #include "exception.h"
@@ -65,5 +66,6 @@ cluster_set_server(const tt_uuid *server_uuid, uint32_t server_id)
 		/* Assign local server id */
 		assert(r->server_id == 0);
 		r->server_id = server_id;
+		box_set_ro(false);
 	}
 }
diff --git a/src/box/lua/info.cc b/src/box/lua/info.cc
index ec13926417ad5f5ae6fd001d809b03f2394dbe47..f1616df17f60b5bb94d5b3490b62e34f65a7ba72 100644
--- a/src/box/lua/info.cc
+++ b/src/box/lua/info.cc
@@ -59,11 +59,6 @@ lbox_info_recovery_last_update_tstamp(struct lua_State *L)
 static int
 lbox_info_server(struct lua_State *L)
 {
-	if (recovery->server_id == 0) {
-		lua_pushnil(L);
-		return 1;
-	}
-
 	lua_createtable(L, 0, 2);
 	lua_pushliteral(L, "id");
 	lua_pushinteger(L, recovery->server_id);
@@ -72,8 +67,11 @@ lbox_info_server(struct lua_State *L)
 	lua_pushlstring(L, tt_uuid_str(&recovery->server_uuid), UUID_STR_LEN);
 	lua_settable(L, -3);
 	lua_pushliteral(L, "lsn");
-	luaL_pushnumber64(L, vclock_get(&recovery->vclock,
-					recovery->server_id));
+	luaL_pushinumber64(L, vclock_get(&recovery->vclock,
+					 recovery->server_id));
+	lua_settable(L, -3);
+	lua_pushliteral(L, "ro");
+	lua_pushboolean(L, box_is_ro());
 	lua_settable(L, -3);
 
 	return 1;
diff --git a/src/errcode.h b/src/errcode.h
index 26cbd753527d5ea92dca2abe0a4f2fc3edeeb6c2..b1c472972e6f0d5ba789f2cb95fc281fcd07b622 100644
--- a/src/errcode.h
+++ b/src/errcode.h
@@ -56,7 +56,7 @@ enum { TNT_ERRMSG_MAX = 512 };
 	/*  4 */_(ER_TUPLE_NOT_FOUND,		2, "Tuple doesn't exist in index %u") \
 	/*  5 */_(ER_UNSUPPORTED,		2, "%s does not support %s") \
 	/*  6 */_(ER_NONMASTER,			2, "Can't modify data on a replication slave. My master is: %s") \
-	/*  7 */_(ER_SECONDARY,			2, "Can't modify data upon a request on the secondary port.") \
+	/*  7 */_(ER_READONLY,			2, "Can't modify data because this server in read-only mode.") \
 	/*  8 */_(ER_INJECTION,			2, "Error injection '%s'") \
 	/*  9 */_(ER_CREATE_SPACE,		2, "Failed to create space %u: %s") \
 	/* 10 */_(ER_SPACE_EXISTS,		2, "Space '%s' already exists") \
diff --git a/test/box/misc.result b/test/box/misc.result
index b2397d2a88830c8510156437ed4eaf6ebc307ca9..9a29bfda137c96b9fb9f4ba52f2fe8c05e0a9e0a 100644
--- a/test/box/misc.result
+++ b/test/box/misc.result
@@ -182,11 +182,11 @@ end;
 t;
 ---
 - - 'box.error.EXACT_MATCH : 19'
-  - 'box.error.SECONDARY : 7'
+  - 'box.error.NO_SUCH_TRIGGER : 34'
   - 'box.error.CLUSTER_ID_IS_RO : 65'
   - 'box.error.INDEX_TYPE : 13'
   - 'box.error.CLUSTER_ID_MISMATCH : 63'
-  - 'box.error.FIELD_TYPE : 23'
+  - 'box.error.MEMORY_ISSUE : 2'
   - 'box.error.KEY_PART_TYPE : 18'
   - 'box.error.CREATE_FUNCTION : 50'
   - 'box.error.SOPHIA : 60'
@@ -213,7 +213,7 @@ t;
   - 'box.error.ROLE_EXISTS : 83'
   - 'box.error.NO_SUCH_ROLE : 82'
   - 'box.error.NO_ACTIVE_TRANSACTION : 80'
-  - 'box.error.SPLICE : 25'
+  - 'box.error.TUPLE_FOUND : 3'
   - 'box.error.FIELD_TYPE_MISMATCH : 24'
   - 'box.error.UNSUPPORTED : 5'
   - 'box.error.INVALID_MSGPACK : 20'
@@ -221,7 +221,7 @@ t;
   - 'box.error.ALTER_SPACE : 12'
   - 'box.error.ACTIVE_TRANSACTION : 79'
   - 'box.error.NO_CONNECTION : 77'
-  - 'box.error.DROP_SPACE : 11'
+  - 'box.error.FIELD_TYPE : 23'
   - 'box.error.INVALID_XLOG_NAME : 75'
   - 'box.error.INVALID_XLOG : 74'
   - 'box.error.REPLICA_MAX : 73'
@@ -235,34 +235,34 @@ t;
   - 'box.error.INVALID_ORDER : 68'
   - 'box.error.CFG : 59'
   - 'box.error.SPACE_FIELD_COUNT : 38'
-  - 'box.error.SPACE_ACCESS_DENIED : 55'
+  - 'box.error.UNKNOWN : 0'
   - 'box.error.NO_SUCH_FIELD : 37'
   - 'box.error.LOCAL_SERVER_IS_NOT_ACTIVE : 61'
   - 'box.error.RELOAD_CFG : 58'
   - 'box.error.PROC_RET : 21'
   - 'box.error.INJECTION : 8'
-  - 'box.error.PROC_LUA : 32'
+  - 'box.error.FUNCTION_MAX : 54'
   - 'box.error.ILLEGAL_PARAMS : 1'
-  - 'box.error.TUPLE_NOT_ARRAY : 22'
   - 'box.error.TUPLE_FORMAT_LIMIT : 16'
+  - 'box.error.USER_MAX : 56'
   - 'box.error.INVALID_UUID : 64'
-  - 'box.error.UNKNOWN : 0'
+  - 'box.error.SPLICE : 25'
   - 'box.error.TIMEOUT : 78'
-  - 'box.error.TUPLE_FOUND : 3'
-  - 'box.error.MEMORY_ISSUE : 2'
-  - 'box.error.NO_SUCH_TRIGGER : 34'
+  - 'box.error.MORE_THAN_ONE_TUPLE : 41'
+  - 'box.error.NO_SUCH_SPACE : 36'
+  - 'box.error.PROC_LUA : 32'
   - 'box.error.UPDATE_FIELD : 29'
   - 'box.error.ARG_TYPE : 26'
-  - 'box.error.NO_SUCH_SPACE : 36'
   - 'box.error.INDEX_FIELD_COUNT : 39'
-  - 'box.error.MORE_THAN_ONE_TUPLE : 41'
+  - 'box.error.READONLY : 7'
   - 'box.error.DROP_PRIMARY_KEY : 17'
+  - 'box.error.DROP_SPACE : 11'
   - 'box.error.UNKNOWN_REQUEST_TYPE : 48'
   - 'box.error.INVALID_XLOG_ORDER : 76'
-  - 'box.error.FUNCTION_MAX : 54'
+  - 'box.error.SPACE_ACCESS_DENIED : 55'
   - 'box.error.NO_SUCH_USER : 45'
-  - 'box.error.USER_MAX : 56'
   - 'box.error.UNKNOWN_UPDATE_OP : 28'
+  - 'box.error.TUPLE_NOT_ARRAY : 22'
   - 'box.error.NO_SUCH_PROC : 33'
   - 'box.error.FUNCTION_ACCESS_DENIED : 53'
 ...
diff --git a/test/lib/sql_ast.py b/test/lib/sql_ast.py
index 3b0741745ef0322407fda3bda7c365f811ea66a9..f49a42ac6f8bb2ada158b1dcabb4cbfe01bcff38 100644
--- a/test/lib/sql_ast.py
+++ b/test/lib/sql_ast.py
@@ -25,7 +25,7 @@ ER = {
      4: "ER_TUPLE_NOT_FOUND"    ,
      5: "ER_UNSUPPORTED"        ,
      6: "ER_NONMASTER"          ,
-     7: "ER_SECONDARY"          ,
+     7: "ER_READONLY"           ,
      8: "ER_INJECTION"          ,
      9: "ER_CREATE_SPACE"       ,
     10: "ER_SPACE_EXISTS"       ,
diff --git a/test/replication/hot_standby.result b/test/replication/hot_standby.result
index 0f939412486f09b1b533d1173cc859b3281fafcb..d2b066d47a98c1c6fc952afcf7743f088c45fd28 100644
--- a/test/replication/hot_standby.result
+++ b/test/replication/hot_standby.result
@@ -12,7 +12,7 @@ box.schema.user.grant('guest', 'read,write,execute', 'universe')
 fiber = require('fiber');
 ---
 ...
-while box.info.server == nil do fiber.sleep(0.01) end;
+while box.info.server.id == 0 do fiber.sleep(0.01) end;
 ---
 ...
 while box.space['_priv']:len() < 1 do fiber.sleep(0.001) end;
diff --git a/test/replication/hot_standby.test.lua b/test/replication/hot_standby.test.lua
index 82c5165dfdd6a7e70fe23595a3e160e58cf77671..560207fb6b5dd5f2b9495272dc252a75a29a32f8 100644
--- a/test/replication/hot_standby.test.lua
+++ b/test/replication/hot_standby.test.lua
@@ -9,7 +9,7 @@ box.schema.user.grant('guest', 'read,write,execute', 'universe')
 --# setopt delimiter ';'
 --# set connection default, hot_standby, replica
 fiber = require('fiber');
-while box.info.server == nil do fiber.sleep(0.01) end;
+while box.info.server.id == 0 do fiber.sleep(0.01) end;
 while box.space['_priv']:len() < 1 do fiber.sleep(0.001) end;
 do
     local pri_id = ''
diff --git a/test/replication/readonly.result b/test/replication/readonly.result
new file mode 100644
index 0000000000000000000000000000000000000000..9a3b22b210fa4119b55ef7fd00086806a7e14693
--- /dev/null
+++ b/test/replication/readonly.result
@@ -0,0 +1,41 @@
+box.schema.user.grant('guest', 'read,write,execute', 'universe')
+---
+...
+box.info.server.id
+---
+- 2
+...
+box.info.server.ro
+---
+- false
+...
+box.info.server.lsn
+---
+- 0
+...
+-------------------------------------------------------------
+replica is read-only until receive self server_id in _cluster
+-------------------------------------------------------------
+box.cfg{replication_source = ""}
+---
+...
+box.info.server.id
+---
+- 0
+...
+box.info.server.ro
+---
+- true
+...
+box.info.server.lsn
+---
+- -1
+...
+space = box.schema.create_space("ro")
+---
+- error: Can't modify data because this server in read-only mode.
+...
+box.info.vclock[2]
+---
+- null
+...
diff --git a/test/replication/readonly.test.py b/test/replication/readonly.test.py
new file mode 100644
index 0000000000000000000000000000000000000000..24789c51a77e804d1b4b369e189a7d2e7bc5251c
--- /dev/null
+++ b/test/replication/readonly.test.py
@@ -0,0 +1,46 @@
+import os
+from glob import iglob as glob
+from lib.tarantool_server import TarantoolServer
+
+# master server
+master = server
+master_id = master.get_param('server')['id']
+
+master.admin("box.schema.user.grant('guest', 'read,write,execute', 'universe')")
+
+replica = TarantoolServer(server.ini)
+replica.script = 'replication/replica.lua'
+replica.vardir = os.path.join(server.vardir, 'replica')
+replica.rpl_master = master
+replica.deploy()
+replica.wait_lsn(master_id, master.get_lsn(master_id))
+replica_id = replica.get_param('server')['id']
+replica.admin('box.info.server.id')
+replica.admin('box.info.server.ro')
+replica.admin('box.info.server.lsn')
+replica.stop()
+
+print '-------------------------------------------------------------'
+print 'replica is read-only until receive self server_id in _cluster'
+print '-------------------------------------------------------------'
+
+# Remove xlog retrived by SUBSCRIBE
+filename = str(0).zfill(20) + ".xlog"
+wal = os.path.join(replica.vardir, filename)
+os.remove(wal)
+
+# Start replica without master
+server.stop()
+replica.start()
+replica.admin('box.cfg{replication_source = ""}')
+
+# Check that replica in read-only mode
+replica.admin('box.info.server.id')
+replica.admin('box.info.server.ro')
+replica.admin('box.info.server.lsn')
+replica.admin('space = box.schema.create_space("ro")')
+replica.admin('box.info.vclock[%d]' % replica_id)
+
+replica.stop()
+replica.cleanup(True)
+server.deploy()
diff --git a/test/replication/swap.result b/test/replication/swap.result
index 5badc7052d0fff463c4f1fca6ffc8953d3e48e84..7264bc985d053c0fb122f80eeda232b9e9e736eb 100644
--- a/test/replication/swap.result
+++ b/test/replication/swap.result
@@ -4,7 +4,7 @@ box.schema.user.create('test', { password = 'pass123456'})
 box.schema.user.grant('test', 'read,write,execute', 'universe')
 ---
 ...
-while box.info.server == nil do require('fiber').sleep(0.01) end
+while box.info.server.id == 0 do require('fiber').sleep(0.01) end
 ---
 ...
 while box.space['_priv']:len() < 1 do require('fiber').sleep(0.01) end
diff --git a/test/replication/swap.test.py b/test/replication/swap.test.py
index eb3021ebdad958602e7d0454f9309e03a5765cbc..74cd85a7b37e7d7042bb79fc03c6f29b04ba8c02 100644
--- a/test/replication/swap.test.py
+++ b/test/replication/swap.test.py
@@ -31,7 +31,7 @@ replica = TarantoolServer()
 replica.script = "replication/replica.lua"
 replica.vardir = os.path.join(server.vardir, 'replica')
 replica.deploy()
-replica.admin("while box.info.server == nil do require('fiber').sleep(0.01) end")
+replica.admin("while box.info.server.id == 0 do require('fiber').sleep(0.01) end")
 replica.uri = '%s:%s@%s' % (LOGIN, PASSWORD, replica.sql.uri)
 replica.admin("while box.space['_priv']:len() < 1 do require('fiber').sleep(0.01) end")
 replica.sql.py_con.authenticate(LOGIN, PASSWORD)