diff --git a/changelogs/unreleased/box-info-config.md b/changelogs/unreleased/box-info-config.md
new file mode 100644
index 0000000000000000000000000000000000000000..ed078dc57042f9e3f19036d948007bca8a617a82
--- /dev/null
+++ b/changelogs/unreleased/box-info-config.md
@@ -0,0 +1,3 @@
+## feature/config
+
+* Expose configuration status from `box.info.config` (gh-10044).
diff --git a/src/box/lua/info.c b/src/box/lua/info.c
index 97b637b3b328207d779187a63e48f6ea12461833..44d28a1725ee6ea6bfc87ea32194d42b672e015b 100644
--- a/src/box/lua/info.c
+++ b/src/box/lua/info.c
@@ -759,6 +759,42 @@ lbox_info_hostname(struct lua_State *L)
 	return 1;
 }
 
+static int
+lbox_info_config(struct lua_State *L)
+{
+	/* require('config'):info('v2') */
+	lua_getglobal(L, "require");
+	lua_pushliteral(L, "config");
+	if (lua_pcall(L, 1, 1, 0) != 0)
+		goto error;
+	/* Stack: config. */
+	lua_getfield(L, -1, "info");
+	/* Stack: config, config.info. */
+	lua_insert(L, -2);
+	/* Stack: config.info, config. */
+	lua_pushliteral(L, "v2");
+	/* Stack: config.info, config, 'v2'. */
+	if (lua_pcall(L, 2, 1, 0) != 0)
+		goto error;
+	return 1;
+
+error:
+	/*
+	 * An error shouldn't occur by construction.
+	 *
+	 * However, box.info() is an important call and we
+	 * shouldn't fail it in any circumstances, including a
+	 * problem in the config:info() implementation.
+	 *
+	 * So, we don't raise an error here and place it to the
+	 * result instead.
+	 */
+	lua_newtable(L);
+	lua_insert(L, -2);
+	lua_setfield(L, -2, "error");
+	return 1;
+}
+
 static const struct luaL_Reg lbox_info_dynamic_meta[] = {
 	{"id", lbox_info_id},
 	{"uuid", lbox_info_uuid},
@@ -784,6 +820,7 @@ static const struct luaL_Reg lbox_info_dynamic_meta[] = {
 	{"synchro", lbox_info_synchro},
 	{"schema_version", lbox_schema_version},
 	{"hostname", lbox_info_hostname},
+	{"config", lbox_info_config},
 	{NULL, NULL}
 };
 
diff --git a/test/box-luatest/box_info_config_test.lua b/test/box-luatest/box_info_config_test.lua
new file mode 100644
index 0000000000000000000000000000000000000000..d429d7acaafad3904ee778aa3135d1edd51e109d
--- /dev/null
+++ b/test/box-luatest/box_info_config_test.lua
@@ -0,0 +1,117 @@
+local t = require('luatest')
+local server = require('luatest.server')
+local cbuilder = require('test.config-luatest.cbuilder')
+local cluster = require('test.config-luatest.cluster')
+
+local g = t.group()
+
+g.before_all(cluster.init)
+g.after_each(cluster.drop)
+g.after_all(cluster.clean)
+
+g.after_each(function(g)
+    if g.server ~= nil then
+        g.server:drop()
+        g.server = nil
+    end
+end)
+
+-- Verify box.info.config when tarantool is started from a script.
+g.test_with_script = function(g)
+    g.server = server:new()
+    g.server:start()
+
+    g.server:exec(function()
+        local function verify_config(res)
+            t.assert_equals(res, {
+                status = 'uninitialized',
+                alerts = {},
+                meta = {
+                    -- No 'active' field, because there is no
+                    -- active configuration.
+                    last = {},
+                },
+            })
+        end
+
+        verify_config(box.info.config)
+        verify_config(box.info().config)
+    end)
+end
+
+-- Verify box.info.config when tarantool is started from a config.
+g.test_with_config = function(g)
+    local config = cbuilder.new()
+        :add_instance('i-001', {})
+        :config()
+    local cluster = cluster.new(g, config)
+    cluster:start()
+
+    cluster['i-001']:exec(function()
+        local function verify_config(res)
+            t.assert_equals(res, {
+                status = 'ready',
+                alerts = {},
+                meta = {
+                    active = {},
+                    last = {},
+                },
+            })
+        end
+
+        verify_config(box.info.config)
+        verify_config(box.info().config)
+    end)
+end
+
+-- box.info() should work always, even if config:info() is broken.
+g.test_broken_config_info = function(g)
+    local config = cbuilder.new()
+        :add_instance('i-001', {})
+        :config()
+    local cluster = cluster.new(g, config)
+    cluster:start()
+
+    cluster['i-001']:exec(function()
+        local config = require('config')
+
+        local function verify_config(res)
+            t.assert_equals(res, {
+                error = 'config:info() is broken',
+            })
+        end
+
+        -- Break the method.
+        config.info = function(_self, _version)
+            error('config:info() is broken', 0)
+        end
+
+        verify_config(box.info.config)
+        verify_config(box.info().config)
+    end)
+end
+
+-- box.info() should work always, even if config module is broken.
+g.test_broken_config_module = function(g)
+    local config = cbuilder.new()
+        :add_instance('i-001', {})
+        :config()
+    local cluster = cluster.new(g, config)
+    cluster:start()
+
+    cluster['i-001']:exec(function()
+        local loaders = require('internal.loaders')
+
+        local function verify_config(res)
+            t.assert_type(res, 'table')
+            t.assert_str_contains(res.error, "module 'config' not found")
+        end
+
+        -- Unload the module and break the next require('config').
+        package.loaded.config = nil
+        loaders.builtin.config = nil
+
+        verify_config(box.info.config)
+        verify_config(box.info().config)
+    end)
+end
diff --git a/test/box/info.result b/test/box/info.result
index 19bb50a328a2a29daec995f54ddd1c4c3d249311..c1e00d825a9de44cebe7c7ba32f89e184f07ad6b 100644
--- a/test/box/info.result
+++ b/test/box/info.result
@@ -79,6 +79,7 @@ table.sort(t)
 t
 ---
 - - cluster
+  - config
   - election
   - gc
   - hostname