diff --git a/package/gluon-client-bridge/luasrc/lib/gluon/upgrade/300-gluon-client-bridge-network b/package/gluon-client-bridge/luasrc/lib/gluon/upgrade/300-gluon-client-bridge-network
index 490325cfd841c6588cf05098b0ceb9c5a8ccc8ba..b344b2b322976233fcca39d7615f2de007abd918 100755
--- a/package/gluon-client-bridge/luasrc/lib/gluon/upgrade/300-gluon-client-bridge-network
+++ b/package/gluon-client-bridge/luasrc/lib/gluon/upgrade/300-gluon-client-bridge-network
@@ -6,26 +6,13 @@ local util = require 'gluon.util'
 local uci = require('simple-uci').cursor()
 
 
-local interfaces = uci:get('network', 'client', 'ifname') or {}
-
-if type(interfaces) == 'string' then
-	local ifname = interfaces
-	interfaces = {}
-	for iface in ifname:gmatch('%S+') do
-		util.add_to_set(interfaces, iface)
-	end
-end
-
+local interfaces = { 'local-port' }
 if sysconfig.lan_ifname and uci:get_bool('network', 'mesh_lan', 'disabled') then
 	for lanif in sysconfig.lan_ifname:gmatch('%S+') do
 		util.add_to_set(interfaces, lanif)
 	end
 end
 
-util.add_to_set(interfaces, 'local-port')
-
-
-uci:delete('network', 'client')
 uci:section('network', 'interface', 'client', {
 	type = 'bridge',
 	ifname = interfaces,
diff --git a/package/gluon-client-bridge/luasrc/lib/gluon/upgrade/310-gluon-client-bridge-local-node b/package/gluon-client-bridge/luasrc/lib/gluon/upgrade/310-gluon-client-bridge-local-node
index 824d8d40e15a6d8b26bbae66bc0f5f64f549661a..e67dc7d52b2128ec7afc521412cb09c9d68162c0 100755
--- a/package/gluon-client-bridge/luasrc/lib/gluon/upgrade/310-gluon-client-bridge-local-node
+++ b/package/gluon-client-bridge/luasrc/lib/gluon/upgrade/310-gluon-client-bridge-local-node
@@ -10,7 +10,6 @@ local uci = require('simple-uci').cursor()
 local next_node = site.next_node({})
 
 
-uci:delete('network', 'local_node_dev')
 uci:section('network', 'device', 'local_node_dev', {
 	type = 'veth',
 	name = 'local-node',
@@ -31,7 +30,6 @@ if next_node.ip6 then
 	ip6 = next_node.ip6 .. '/128'
 end
 
-uci:delete('network', 'local_node')
 uci:section('network', 'interface', 'local_node', {
 	ifname = 'local-node',
 	proto = 'static',
diff --git a/package/gluon-core/files/lib/gluon/upgrade/001-reset-uci b/package/gluon-core/files/lib/gluon/upgrade/001-reset-uci
new file mode 100755
index 0000000000000000000000000000000000000000..4981390d74fbbddd75c1248cb9d04d886b1dac70
--- /dev/null
+++ b/package/gluon-core/files/lib/gluon/upgrade/001-reset-uci
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+NETWORK_CFG='/etc/config/network'
+NETWORK_SAVED="${NETWORK_CFG}_gluon-old"
+
+SYSTEM_CFG='/etc/config/system'
+SYSTEM_SAVED="${SYSTEM_CFG}_gluon-old"
+
+# Make sure everything is saved before we move away the config files
+uci commit
+
+# Save old configs (unless there is already a saved config,
+# which means that the previous upgrade was interrupted)
+if [ -s "$NETWORK_CFG" ] && ! [ -s "$NETWORK_SAVED" ]; then
+	mv -f "$NETWORK_CFG" "$NETWORK_SAVED"
+fi
+if [ -s "$SYSTEM_CFG" ] && ! [ -s "$SYSTEM_SAVED" ]; then
+	mv -f "$SYSTEM_CFG" "$SYSTEM_SAVED"
+fi
+
+# Generate a new network config
+rm -f /etc/board.json "$NETWORK_CFG" "$SYSTEM_CFG"
+config_generate
diff --git a/package/gluon-core/files/lib/gluon/upgrade/998-commit b/package/gluon-core/files/lib/gluon/upgrade/998-commit
index 8b4be6a9d4dfafe7831d309a8d40fdd211696311..9e781f3490b2065d6a8d27ea6eeb8c8827b66045 100755
--- a/package/gluon-core/files/lib/gluon/upgrade/998-commit
+++ b/package/gluon-core/files/lib/gluon/upgrade/998-commit
@@ -4,3 +4,6 @@ uci -q batch <<-EOF
 	delete gluon.core.reconfigure
 	commit
 EOF
+
+# New config is saved, we can delete the old one
+rm -f /etc/config/*_gluon-old
diff --git a/package/gluon-core/luasrc/lib/gluon/upgrade/002-migrate-system b/package/gluon-core/luasrc/lib/gluon/upgrade/002-migrate-system
new file mode 100755
index 0000000000000000000000000000000000000000..b08dc8a3b25efabc73ea279ae7247f257469343c
--- /dev/null
+++ b/package/gluon-core/luasrc/lib/gluon/upgrade/002-migrate-system
@@ -0,0 +1,31 @@
+#!/usr/bin/lua
+
+local uci = require('simple-uci').cursor()
+
+-- Migrate system section
+local system = uci:get_all('system_gluon-old', '@system[0]')
+if system then
+	uci:tset('system', '@system[0]', system)
+end
+
+-- Migrate ntp section
+local ntp = uci:get_all('system_gluon-old', 'ntp')
+if ntp then
+	uci:tset('system', 'ntp', ntp)
+end
+
+-- Migrate gpio_switch sections
+--
+-- Only the value is copied from the old config, so updates to names and
+-- pins are preserved
+uci:foreach('system', 'gpio_switch', function(s)
+	local name = s['.name']
+	local value = uci:get('system_gluon-old', name, 'value')
+	if value then
+		uci:set('system', name, 'value', value)
+	end
+end)
+
+-- No other sections are migrated, so updated LED and RSSI configs can take effect
+
+uci:save('system')
diff --git a/package/gluon-core/luasrc/lib/gluon/upgrade/020-interfaces b/package/gluon-core/luasrc/lib/gluon/upgrade/020-interfaces
index 6371cc6103d242a2a4d98d78f229a0c2fadd1a54..56799616684a201d9afc2fef30a8c0a143bc8497 100755
--- a/package/gluon-core/luasrc/lib/gluon/upgrade/020-interfaces
+++ b/package/gluon-core/luasrc/lib/gluon/upgrade/020-interfaces
@@ -79,9 +79,7 @@ else
 end
 
 
-uci:delete('network', 'lan')
-uci:delete('network', 'wan')
-
 uci:delete_all('network', 'device')
+uci:delete_all('network', 'interface')
 
 uci:save('network')
diff --git a/package/gluon-core/luasrc/lib/gluon/upgrade/110-network b/package/gluon-core/luasrc/lib/gluon/upgrade/110-network
index 79c50d279ec5d722f3bda4b3983d1b56f92d04d0..8903c126a7a257bca5a39624b2c2290cb75db818 100755
--- a/package/gluon-core/luasrc/lib/gluon/upgrade/110-network
+++ b/package/gluon-core/luasrc/lib/gluon/upgrade/110-network
@@ -3,8 +3,21 @@
 local uci = require('simple-uci').cursor()
 local sysconfig = require 'gluon.sysconfig'
 
+local wan = uci:get_all('network_gluon-old', 'wan') or {}
+local wan6 = uci:get_all('network_gluon-old', 'wan6') or {}
+
+uci:section('network', 'interface', 'loopback', {
+	ifname = 'lo',
+	proto = 'static',
+	ipaddr = '127.0.0.1',
+	netmask = '255.0.0.0',
+})
 
 uci:section('network', 'interface', 'wan', {
+	proto = wan.proto or 'dhcp',
+	ipaddr = wan.ipaddr,
+	netmask = wan.netmask,
+	gateway = wan.gateway,
 	ifname = sysconfig.wan_ifname,
 	type = 'bridge',
 	igmp_snooping = true,
@@ -13,12 +26,10 @@ uci:section('network', 'interface', 'wan', {
 	auto = true,
 })
 
-if not uci:get('network', 'wan', 'proto') then
-	uci:set('network', 'wan', 'proto', 'dhcp')
-end
-
-
 uci:section('network', 'interface', 'wan6', {
+	proto = wan6.proto or 'dhcpv6',
+	ip6addr = wan6.ip6addr,
+	ip6gw = wan6.ip6gw,
 	ifname = 'br-wan',
 	peerdns = false,
 	ip6table = 1,
@@ -26,11 +37,6 @@ uci:section('network', 'interface', 'wan6', {
 	reqprefix = 'no',
 })
 
-if not uci:get('network', 'wan6', 'proto') then
-	uci:set('network', 'wan6', 'proto', 'dhcpv6')
-end
-
-
 uci:section('network', 'rule6', 'wan6_lookup', {
 	mark = '0x01/0x01',
 	lookup = 1,
diff --git a/package/gluon-core/luasrc/lib/gluon/upgrade/200-wireless b/package/gluon-core/luasrc/lib/gluon/upgrade/200-wireless
index b0a5485fd3580f42d45ba13d183629bc35301721..604e33a8f9d080a7a01d92f9db8b92e705167391 100755
--- a/package/gluon-core/luasrc/lib/gluon/upgrade/200-wireless
+++ b/package/gluon-core/luasrc/lib/gluon/upgrade/200-wireless
@@ -106,8 +106,6 @@ end
 local function delete_ibss(radio_name)
 	local name = 'ibss_' .. radio_name
 
-	uci:delete('network', name)
-	uci:delete('network', name .. '_vlan')
 	uci:delete('wireless', name)
 end
 
@@ -118,8 +116,6 @@ local function configure_mesh(config, radio, index, suffix, disabled)
 	local macfilter = uci:get('wireless', name, 'macfilter')
 	local maclist = uci:get('wireless', name, 'maclist')
 
-	uci:delete('network', name)
-	uci:delete('network', name .. '_vlan')
 	uci:delete('wireless', name)
 
 	if not config then
diff --git a/package/gluon-core/luasrc/lib/gluon/upgrade/210-interface-wan b/package/gluon-core/luasrc/lib/gluon/upgrade/210-interface-wan
index cf6eff711282b85cc9914b60a4b5d95d7888b839..4f24cbc439d928fe89ce762244661508296113ec 100755
--- a/package/gluon-core/luasrc/lib/gluon/upgrade/210-interface-wan
+++ b/package/gluon-core/luasrc/lib/gluon/upgrade/210-interface-wan
@@ -3,23 +3,23 @@
 local site = require 'gluon.site'
 local uci = require('simple-uci').cursor()
 
-uci:section('network', 'interface', 'mesh_wan', {
-	ifname = 'br-wan',
-	proto  = 'gluon_wired',
-	index  = 0,
-	vxlan  = site.mesh.vxlan(true),
-})
-
-local enable = site.mesh_on_wan(false)
-local old_auto = uci:get('network', 'mesh_wan', 'auto')
-local old_disabled = uci:get('network', 'mesh_wan', 'disabled')
-if old_auto ~= nil or old_disabled ~= nil then
-	enable = old_auto ~= '0' and old_disabled ~= '1'
+local disabled = uci:get('network_gluon-old', 'mesh_wan', 'disabled')
+if disabled == nil then
+	disabled = not site.mesh_on_wan(false)
 end
-uci:set('network', 'mesh_wan', 'disabled', not enable)
 
-if uci:get('network', 'mesh_wan', 'transitive') == nil then
-	uci:set('network', 'mesh_wan', 'transitive', true)
+local transitive = uci:get('network_gluon-old', 'mesh_wan', 'transitive')
+if transitive == nil then
+	transitive = true
 end
 
+uci:section('network', 'interface', 'mesh_wan', {
+	ifname = 'br-wan',
+	proto = 'gluon_wired',
+	index = 0,
+	vxlan = site.mesh.vxlan(true),
+	disabled = disabled,
+	transitive = transitive,
+})
+
 uci:save('network')
diff --git a/package/gluon-core/luasrc/lib/gluon/upgrade/220-interface-lan b/package/gluon-core/luasrc/lib/gluon/upgrade/220-interface-lan
index 55586435f8756c70b99c52ff9815d7ee9f8833ff..cd38f804aa8f6a4530fda2d4cdc6150ac474f7a2 100755
--- a/package/gluon-core/luasrc/lib/gluon/upgrade/220-interface-lan
+++ b/package/gluon-core/luasrc/lib/gluon/upgrade/220-interface-lan
@@ -1,7 +1,6 @@
 #!/usr/bin/lua
 
 local site = require 'gluon.site'
-local util = require 'gluon.util'
 local sysconfig = require 'gluon.sysconfig'
 
 local uci = require('simple-uci').cursor()
@@ -10,44 +9,30 @@ if not sysconfig.lan_ifname then
 	os.exit(0)
 end
 
-uci:section('network', 'interface', 'mesh_lan', {
-	ifname        = sysconfig.lan_ifname,
-	igmp_snooping = false,
-	proto         = 'gluon_wired',
-	index         = 4,
-	vxlan         = site.mesh.vxlan(true),
-})
-
+local type
 if sysconfig.lan_ifname:match(' ') then
-	uci:set('network', 'mesh_lan', 'type', 'bridge')
-else
-	uci:delete('network', 'mesh_lan', 'type')
+	type = 'bridge'
 end
 
-local enable = site.mesh_on_lan(false)
-local old_auto = uci:get('network', 'mesh_lan', 'auto')
-local old_disabled = uci:get('network', 'mesh_lan', 'disabled')
-if old_auto ~= nil or old_disabled ~= nil then
-	enable = old_auto ~= '0' and old_disabled ~= '1'
+local disabled = uci:get('network_gluon-old', 'mesh_lan', 'disabled')
+if disabled == nil then
+	disabled = not site.mesh_on_lan(false)
 end
 
-if enable then
-	local interfaces = uci:get_list('network', 'client', 'ifname')
-
-	if interfaces then
-		for lanif in sysconfig.lan_ifname:gmatch('%S+') do
-			if util.contains(interfaces, lanif) then
-				enable = false
-				break
-			end
-		end
-	end
+local transitive = uci:get('network_gluon-old', 'mesh_lan', 'transitive')
+if transitive == nil then
+	transitive = true
 end
 
-uci:set('network', 'mesh_lan', 'disabled', not enable)
-
-if uci:get('network', 'mesh_lan', 'transitive') == nil then
-	uci:set('network', 'mesh_lan', 'transitive', true)
-end
+uci:section('network', 'interface', 'mesh_lan', {
+	ifname = sysconfig.lan_ifname,
+	type = type,
+	igmp_snooping = false,
+	proto = 'gluon_wired',
+	index = 4,
+	vxlan = site.mesh.vxlan(true),
+	disabled = disabled,
+	transitive = transitive,
+})
 
 uci:save('network')
diff --git a/package/gluon-mesh-babel/luasrc/lib/gluon/upgrade/430-gluon-mesh-babel-add-mmfd-interface b/package/gluon-mesh-babel/luasrc/lib/gluon/upgrade/430-gluon-mesh-babel-add-mmfd-interface
index 47807d954b0b9af49b57bb3d5a123e7f52aed01a..073d288e8f6375abbe037699258a517d5345a938 100755
--- a/package/gluon-mesh-babel/luasrc/lib/gluon/upgrade/430-gluon-mesh-babel-add-mmfd-interface
+++ b/package/gluon-mesh-babel/luasrc/lib/gluon/upgrade/430-gluon-mesh-babel-add-mmfd-interface
@@ -2,7 +2,6 @@
 
 local uci = require('simple-uci').cursor()
 
-uci:delete('network', 'mmfd')
 uci:section('network', 'interface', 'mmfd', {
 	proto = 'static',
 	ifname = 'mmfd0',
diff --git a/package/gluon-mesh-batman-adv/luasrc/lib/gluon/upgrade/310-gluon-mesh-batman-adv-mesh b/package/gluon-mesh-batman-adv/luasrc/lib/gluon/upgrade/310-gluon-mesh-batman-adv-mesh
index 40943207fc8f093d160d5134a9629335f5b08715..977b44d157ff18451c4c4524dc23fe3c85c68ffa 100755
--- a/package/gluon-mesh-batman-adv/luasrc/lib/gluon/upgrade/310-gluon-mesh-batman-adv-mesh
+++ b/package/gluon-mesh-batman-adv/luasrc/lib/gluon/upgrade/310-gluon-mesh-batman-adv-mesh
@@ -9,14 +9,12 @@ local uci = require('simple-uci').cursor()
 uci:delete('batman-adv', 'bat0')
 uci:save('batman-adv')
 
-local gw_mode = uci:get('network', 'gluon_bat0', 'gw_mode') or 'client'
-uci:delete('network', 'gluon_bat0')
+local gw_mode = uci:get('network_gluon-old', 'gluon_bat0', 'gw_mode') or 'client'
 uci:section('network', 'interface', 'gluon_bat0', {
 	proto = 'gluon_bat0',
 	gw_mode = gw_mode,
 })
 
-uci:delete('network', 'bat0')
 uci:section('network', 'interface', 'bat0', {
 	ifname = 'bat0',
 	proto = 'none',
diff --git a/package/gluon-mesh-batman-adv/luasrc/lib/gluon/upgrade/320-gluon-mesh-batman-adv-client-bridge b/package/gluon-mesh-batman-adv/luasrc/lib/gluon/upgrade/320-gluon-mesh-batman-adv-client-bridge
index fc14a6e60c365514ade73186478bf477749a94b8..1ea64049bf9a9b0e18c8f89117c8cd76eeeba88e 100755
--- a/package/gluon-mesh-batman-adv/luasrc/lib/gluon/upgrade/320-gluon-mesh-batman-adv-client-bridge
+++ b/package/gluon-mesh-batman-adv/luasrc/lib/gluon/upgrade/320-gluon-mesh-batman-adv-client-bridge
@@ -21,7 +21,6 @@ uci:section('network', 'interface', 'client', {
 	query_response_interval = 500,
 })
 
-uci:delete('network', 'local_node_route6')
 uci:section('network', 'route6', 'local_node_route6', {
 	interface = 'client',
 	target = site.prefix6(),
diff --git a/package/gluon-mesh-batman-adv/luasrc/lib/gluon/upgrade/330-gluon-mesh-batman-adv-mac-addresses b/package/gluon-mesh-batman-adv/luasrc/lib/gluon/upgrade/330-gluon-mesh-batman-adv-mac-addresses
index d323c86337be01834512ce9fe4638d1083d8fc79..61153e103082be5522b6ceda06b26fbcf4ac4551 100755
--- a/package/gluon-mesh-batman-adv/luasrc/lib/gluon/upgrade/330-gluon-mesh-batman-adv-mac-addresses
+++ b/package/gluon-mesh-batman-adv/luasrc/lib/gluon/upgrade/330-gluon-mesh-batman-adv-mac-addresses
@@ -8,8 +8,6 @@ local uci = require('simple-uci').cursor()
 -- fix up potentially duplicate MAC addresses (for meshing)
 if not site.mesh.vxlan(true) then
 	uci:set('network', 'wan', 'macaddr', util.generate_mac(0))
-else
-	uci:delete('network', 'wan', 'macaddr')
 end
 uci:set('network', 'mesh_lan', 'macaddr', util.generate_mac(4))
 uci:save('network')
diff --git a/package/gluon-mesh-vpn-wireguard/luasrc/lib/gluon/upgrade/400-mesh-vpn-wireguard b/package/gluon-mesh-vpn-wireguard/luasrc/lib/gluon/upgrade/400-mesh-vpn-wireguard
index dfadc95bd5b0b45245ec3da3287f5ec4edc078e4..05e1d9e95a6b63cf0e4138d41cf5663c529a4c62 100755
--- a/package/gluon-mesh-vpn-wireguard/luasrc/lib/gluon/upgrade/400-mesh-vpn-wireguard
+++ b/package/gluon-mesh-vpn-wireguard/luasrc/lib/gluon/upgrade/400-mesh-vpn-wireguard
@@ -3,7 +3,7 @@
 local uci = require('simple-uci').cursor()
 local site = require 'gluon.site'
 
-local private_key = uci:get("network", 'wg_mesh', "private_key")
+local private_key = uci:get("network_gluon-old", 'wg_mesh', "private_key")
 
 if not private_key or not private_key:match("^" .. ("[%a%d+/]"):rep(42) .. "[AEIMQUYcgkosw480]=$") then
   private_key = "generate"