diff --git a/docs/features/multidomain.rst b/docs/features/multidomain.rst
index 1f9a729e5d106fa45948c7ed5b2e76eb400b9344..7abd59a80fe0360bbc1161104b229867a763968f 100644
--- a/docs/features/multidomain.rst
+++ b/docs/features/multidomain.rst
@@ -88,18 +88,25 @@ domain of a router, if and only if one of the above conditions matches.
 Switching the domain
 --------------------
 
-**via commandline**:
+Via commandline
+^^^^^^^^^^^^^^^
 
 ::
 
-    uci set gluon.core.domain="newdomaincode"
-    gluon-reconfigure
-    reboot
+    gluon-switch-domain 'newdomaincode'
 
-**via config mode:**
+When the node is not in config mode, ``gluon-switch-domain`` will automatically
+reboot the node by default. This can be suppressed by passing ``--no-reboot``::
 
-To allow switching the domain via config mode, ``config-mode-domain-select``
-has to be added to GLUON_FEATURES in the site.mk.
+    gluon-switch-domain --no-reboot 'newdomaincode'
+
+Switching the domain without reboot is currently **experimental**.
+
+Via config mode
+^^^^^^^^^^^^^^^
+
+To allow switching the domain via config mode, add ``config-mode-domain-select``
+to GLUON_FEATURES in site.mk.
 
 |image0|
 
diff --git a/package/gluon-core/files/etc/init.d/gluon-core-reconfigure b/package/gluon-core/files/etc/init.d/gluon-core-reconfigure
new file mode 100755
index 0000000000000000000000000000000000000000..0a56357182b77045123cd38f7c05d4ebe037710d
--- /dev/null
+++ b/package/gluon-core/files/etc/init.d/gluon-core-reconfigure
@@ -0,0 +1,12 @@
+#!/bin/sh /etc/rc.common
+
+# Start right after S10boot
+START=10
+
+start() {
+	config_load gluon
+	config_get_bool reconfigure core reconfigure 0
+	if [ "$reconfigure" = 1 ]; then
+		gluon-reconfigure
+	fi
+}
diff --git a/package/gluon-core/files/lib/gluon/reload.d/710-gluon-core-reconfigure-start b/package/gluon-core/files/lib/gluon/reload.d/710-gluon-core-reconfigure-start
new file mode 100755
index 0000000000000000000000000000000000000000..758bb913437ab2eb2633613a9efea63445ba2d96
--- /dev/null
+++ b/package/gluon-core/files/lib/gluon/reload.d/710-gluon-core-reconfigure-start
@@ -0,0 +1,2 @@
+#!/bin/sh
+/etc/init.d/gluon-core-reconfigure start
diff --git a/package/gluon-core/files/lib/gluon/upgrade/998-commit b/package/gluon-core/files/lib/gluon/upgrade/998-commit
index db578a78ec6af4e11963a62152a241979d6d0a9c..8b4be6a9d4dfafe7831d309a8d40fdd211696311 100755
--- a/package/gluon-core/files/lib/gluon/upgrade/998-commit
+++ b/package/gluon-core/files/lib/gluon/upgrade/998-commit
@@ -1,3 +1,6 @@
 #!/bin/sh
 
-exec uci commit
+uci -q batch <<-EOF
+	delete gluon.core.reconfigure
+	commit
+EOF
diff --git a/package/gluon-core/luasrc/lib/gluon/upgrade/005-set-domain b/package/gluon-core/luasrc/lib/gluon/upgrade/005-set-domain
new file mode 100755
index 0000000000000000000000000000000000000000..680eab23c63a6b223baedf0b64baff0fb1114216
--- /dev/null
+++ b/package/gluon-core/luasrc/lib/gluon/upgrade/005-set-domain
@@ -0,0 +1,48 @@
+#!/usr/bin/lua
+
+local unistd = require 'posix.unistd'
+
+
+if not unistd.access('/lib/gluon/domains/') then
+	return
+end
+
+
+local function domain_exists(domain)
+	return unistd.access('/lib/gluon/domains/' .. domain .. '.json') == 0
+end
+
+
+local uci = require('simple-uci').cursor()
+
+local domain = uci:get('gluon', 'core', 'switch_domain')
+if domain and not domain_exists(domain) then
+	io.stderr:write(
+		string.format("Warning: invalid mesh domain switch to '%s' configured, not switching\n", domain)
+	)
+	domain = nil
+end
+
+if not domain then
+	domain = uci:get('gluon', 'core', 'domain')
+end
+if domain and not domain_exists(domain) then
+	io.stderr:write(
+		string.format("Warning: invalid mesh domain '%s' configured, resetting to default...\n", domain)
+	)
+	domain = nil
+end
+
+if not domain then
+
+	-- We can't use gluon.site yet, as it depends on gluon.core.domain to be set
+	local json = require 'jsonc'
+	local site = assert(json.load('/lib/gluon/site.json'))
+
+	domain = site.default_domain
+
+end
+
+uci:set('gluon', 'core', 'domain', domain)
+uci:delete('gluon', 'core', 'switch_domain')
+uci:save('gluon')
diff --git a/package/gluon-core/luasrc/lib/gluon/upgrade/005-site-domain b/package/gluon-core/luasrc/lib/gluon/upgrade/005-site-domain
deleted file mode 100755
index 2014dce63e1167ec1a7abcf2c0fc470fb251afaf..0000000000000000000000000000000000000000
--- a/package/gluon-core/luasrc/lib/gluon/upgrade/005-site-domain
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/lua
-
-local unistd = require 'posix.unistd'
-
-
-if not unistd.access('/lib/gluon/domains/') then
-	return
-end
-
-
-local uci = require('simple-uci').cursor()
-
-local domain = uci:get('gluon', 'core', 'domain')
-if domain and not unistd.access('/lib/gluon/domains/' .. domain .. '.json') then
-	io.stderr:write(string.format("Warning: invalid mesh domain '%s' configured, resetting to default...\n", domain))
-	domain = nil
-end
-
-if domain then return end
-
-
--- We can't use gluon.site yet, as it depends on gluon.core.domain to be set
-local json = require 'jsonc'
-local site = assert(json.load('/lib/gluon/site.json'))
-
-uci:set('gluon', 'core', 'domain', site.default_domain)
-uci:commit('gluon')
diff --git a/package/gluon-core/luasrc/usr/bin/gluon-switch-domain b/package/gluon-core/luasrc/usr/bin/gluon-switch-domain
new file mode 100755
index 0000000000000000000000000000000000000000..a964bde9611e7db872b72d08df7dcc754679d304
--- /dev/null
+++ b/package/gluon-core/luasrc/usr/bin/gluon-switch-domain
@@ -0,0 +1,56 @@
+#!/usr/bin/lua
+
+local unistd = require 'posix.unistd'
+
+
+local function shift()
+	table.remove(arg, 1)
+end
+
+local reboot = true
+if arg[1] == '--no-reboot' then
+	reboot = false
+	shift()
+end
+
+local setup_mode = unistd.access('/var/gluon/setup-mode') == 0
+
+if #arg ~= 1 then
+	io.stderr:write('Usage: gluon-switch-domain [--no-reboot] <domain>\n')
+	os.exit(1)
+end
+local domain = arg[1]
+
+
+if not unistd.access('/lib/gluon/domains/') then
+	io.stderr:write('This Gluon firmware does not support multiple mesh domains.\n')
+	os.exit(1)
+end
+
+
+local function domain_exists(dom)
+	return unistd.access('/lib/gluon/domains/' .. dom .. '.json') == 0
+end
+
+if not domain_exists(domain) then
+	io.stderr:write(string.format("Error: invalid mesh domain '%s'\n", domain))
+	os.exit(1)
+end
+
+
+local uci = require('simple-uci').cursor()
+uci:set('gluon', 'core', 'switch_domain', domain)
+uci:set('gluon', 'core', 'reconfigure', true)
+uci:save('gluon')
+
+local cmd
+if setup_mode then
+	cmd = 'gluon-reconfigure'
+elseif reboot then
+	uci:commit('gluon')
+	cmd = 'reboot'
+else
+	cmd = 'gluon-reload'
+end
+
+unistd.execp(cmd, {[0] = cmd})
diff --git a/package/gluon-hoodselector/luasrc/usr/lib/lua/hoodselector/util.lua b/package/gluon-hoodselector/luasrc/usr/lib/lua/hoodselector/util.lua
index b93097a66bd637f43effcf71d1d4852260ec91e4..af079f368b559410db3399eebb674facb2650225 100644
--- a/package/gluon-hoodselector/luasrc/usr/lib/lua/hoodselector/util.lua
+++ b/package/gluon-hoodselector/luasrc/usr/lib/lua/hoodselector/util.lua
@@ -67,9 +67,7 @@ end
 
 function M.set_domain_config(domain)
 	if uci:get('gluon', 'core', 'domain') ~= domain.domain_code then
-		uci:set('gluon', 'core', 'domain', domain.domain_code)
-		uci:commit('gluon')
-		os.execute('gluon-reconfigure')
+		os.execute(string.format("exec gluon-switch-domain --no-reboot '%s'", domain.domain_code))
 		M.log('Set domain "'..domain.domain.domain_names[domain.domain_code]..'"')
 		return true
 	end
diff --git a/package/gluon-hoodselector/luasrc/usr/sbin/hoodselector b/package/gluon-hoodselector/luasrc/usr/sbin/hoodselector
index 12125da761e7181f809e904de617cd341a141896..03852272ec0d1a0f8369649ea26e665993d0230b 100755
--- a/package/gluon-hoodselector/luasrc/usr/sbin/hoodselector
+++ b/package/gluon-hoodselector/luasrc/usr/sbin/hoodselector
@@ -40,7 +40,6 @@ if geo.lat ~= nil and geo.lon ~= nil then
 	local geo_base_domain = hoodutil.get_domain_by_geo(jdomains, geo)
 	if geo_base_domain ~= nil then
 		if hoodutil.set_domain_config(geo_base_domain) then
-			os.execute("gluon-reload")
 			hoodutil.log('Domain set by geolocation mode.\n')
 		end
 		return
@@ -51,6 +50,4 @@ else
 end
 
 -- default domain mode
-if hoodutil.set_domain_config(hoodutil.get_default_domain(hoodutil.get_domains())) then
-	os.execute("gluon-reload")
-end
+hoodutil.set_domain_config(hoodutil.get_default_domain(hoodutil.get_domains()))
diff --git a/package/gluon-scheduled-domain-switch/luasrc/usr/bin/gluon-switch-domain b/package/gluon-scheduled-domain-switch/luasrc/lib/gluon/scheduled-domain-switch/switch-domain
similarity index 92%
rename from package/gluon-scheduled-domain-switch/luasrc/usr/bin/gluon-switch-domain
rename to package/gluon-scheduled-domain-switch/luasrc/lib/gluon/scheduled-domain-switch/switch-domain
index 31fcea363752db6feb17c7d63d1369b925a3a8e7..1d7e58984568f10d7292520bc764fe8b8889baa2 100755
--- a/package/gluon-scheduled-domain-switch/luasrc/usr/bin/gluon-switch-domain
+++ b/package/gluon-scheduled-domain-switch/luasrc/lib/gluon/scheduled-domain-switch/switch-domain
@@ -60,8 +60,5 @@ if not switch_after_min_reached() and not switch_time_passed() then
 	os.exit(0)
 end
 
-uci:set("gluon", "core", "domain", target_domain)
-uci:commit("gluon")
-
-os.execute("gluon-reconfigure")
-os.execute("reboot")
+local cmd = {[0] = 'gluon-switch-domain', target_domain}
+unistd.execp(cmd[0], cmd)
diff --git a/package/gluon-scheduled-domain-switch/luasrc/lib/gluon/upgrade/950-gluon-scheduled-domain-switch b/package/gluon-scheduled-domain-switch/luasrc/lib/gluon/upgrade/950-gluon-scheduled-domain-switch
index c221eeda64d9a03b744cfc288404647a010fcc6d..77803eeec886fda53b2562cb3b03f668a244f7b0 100755
--- a/package/gluon-scheduled-domain-switch/luasrc/lib/gluon/upgrade/950-gluon-scheduled-domain-switch
+++ b/package/gluon-scheduled-domain-switch/luasrc/lib/gluon/upgrade/950-gluon-scheduled-domain-switch
@@ -14,5 +14,5 @@ end
 -- Only in case domain switch is scheduled
 local f = io.open(cronfile, "w")
 f:write("* * * * *  /usr/bin/gluon-check-connection\n")
-f:write("*/5 * * * *  /usr/bin/gluon-switch-domain\n")
+f:write("*/5 * * * *  /lib/gluon/scheduled-domain-switch/switch-domain\n")
 f:close()
diff --git a/package/gluon-setup-mode/Makefile b/package/gluon-setup-mode/Makefile
index ff1c730b3344dabb53ce3e0448acf3bb17cbf756..dfc1717a65ebe6b96c40927252d11b921d23de0f 100644
--- a/package/gluon-setup-mode/Makefile
+++ b/package/gluon-setup-mode/Makefile
@@ -17,4 +17,26 @@ define Package/gluon-setup-mode/description
 	Offline mode to perform basic setup in a secure manner.
 endef
 
+init_links := \
+	K89log \
+	K98boot \
+	K99umount \
+	S00sysfixtime \
+	S10boot \
+	S10gluon-core-reconfigure \
+	S10system \
+	S11sysctl \
+	S12log \
+	S95done
+
+define Package/gluon-setup-mode/install
+	$(Gluon/Build/Install)
+
+	$(LN) S20network $(1)/lib/gluon/setup-mode/rc.d/K90network
+
+	for link in $(init_links); do \
+		$(LN) "/etc/init.d/$$$${link:3}" "$(1)/lib/gluon/setup-mode/rc.d/$$$${link}"; \
+	done
+endef
+
 $(eval $(call BuildPackageGluon,gluon-setup-mode))
diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K89log b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K89log
deleted file mode 120000
index 1e0c5ac02e7d9c3a8be6e85002a2ab61767c0a09..0000000000000000000000000000000000000000
--- a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K89log
+++ /dev/null
@@ -1 +0,0 @@
-/etc/init.d/log
\ No newline at end of file
diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K90network b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K90network
deleted file mode 120000
index 0a43e66b8f035ecc09b334ecf2299665203ee9e8..0000000000000000000000000000000000000000
--- a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K90network
+++ /dev/null
@@ -1 +0,0 @@
-S20network
\ No newline at end of file
diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K98boot b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K98boot
deleted file mode 120000
index 64aea5e82fac99bff0ce8961ef3cbbb208e013e8..0000000000000000000000000000000000000000
--- a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K98boot
+++ /dev/null
@@ -1 +0,0 @@
-/etc/init.d/boot
\ No newline at end of file
diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K99umount b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K99umount
deleted file mode 120000
index b02f4892fa6433b27182dc135916ebf1e720d92e..0000000000000000000000000000000000000000
--- a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/K99umount
+++ /dev/null
@@ -1 +0,0 @@
-/etc/init.d/umount
\ No newline at end of file
diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S00sysfixtime b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S00sysfixtime
deleted file mode 120000
index a4fb1d5bd88ca445c4c62882ffe50bb5ce367382..0000000000000000000000000000000000000000
--- a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S00sysfixtime
+++ /dev/null
@@ -1 +0,0 @@
-/etc/init.d/sysfixtime
\ No newline at end of file
diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S10boot b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S10boot
deleted file mode 120000
index 64aea5e82fac99bff0ce8961ef3cbbb208e013e8..0000000000000000000000000000000000000000
--- a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S10boot
+++ /dev/null
@@ -1 +0,0 @@
-/etc/init.d/boot
\ No newline at end of file
diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S10system b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S10system
deleted file mode 120000
index 81e8836ff672df99397ffb23d0cf69884268430c..0000000000000000000000000000000000000000
--- a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S10system
+++ /dev/null
@@ -1 +0,0 @@
-/etc/init.d/system
\ No newline at end of file
diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S11sysctl b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S11sysctl
deleted file mode 120000
index b4ac535e9157932ba0f60da9313d277a1c723b14..0000000000000000000000000000000000000000
--- a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S11sysctl
+++ /dev/null
@@ -1 +0,0 @@
-/etc/init.d/sysctl
\ No newline at end of file
diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S12log b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S12log
deleted file mode 120000
index 1e0c5ac02e7d9c3a8be6e85002a2ab61767c0a09..0000000000000000000000000000000000000000
--- a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S12log
+++ /dev/null
@@ -1 +0,0 @@
-/etc/init.d/log
\ No newline at end of file
diff --git a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S95done b/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S95done
deleted file mode 120000
index c9f302775486b577a2f264f46b19e4e7acef691d..0000000000000000000000000000000000000000
--- a/package/gluon-setup-mode/files/lib/gluon/setup-mode/rc.d/S95done
+++ /dev/null
@@ -1 +0,0 @@
-/etc/init.d/done
\ No newline at end of file
diff --git a/package/gluon-setup-mode/files/lib/preinit/90_setup_mode b/package/gluon-setup-mode/files/lib/preinit/90_setup_mode
index c38ac281274c72856fddda7a16a9760cee60309a..b9365be1ea27864105405dd2539bd7b1388372b2 100644
--- a/package/gluon-setup-mode/files/lib/preinit/90_setup_mode
+++ b/package/gluon-setup-mode/files/lib/preinit/90_setup_mode
@@ -7,6 +7,10 @@ setup_mode_enable() {
 
 	if [ "$enabled" = 1 ] || [ "$configured" != 1 ]; then
 		echo '/lib/gluon/setup-mode/rc.d' > /tmp/rc_d_path
+
+		# This directory is a marker for scripts to know that we're
+		# in config mode, but it is also used for temporary files
+		mkdir -p /var/gluon/setup-mode
 	fi
 }