diff --git a/docs/user/site.rst b/docs/user/site.rst
index 2d68ed92fe441761be25ff75a3b9b325f81e52af..1f56b3b714406adc127de85d59b13e80575c2611 100644
--- a/docs/user/site.rst
+++ b/docs/user/site.rst
@@ -302,6 +302,12 @@ mesh_vpn
 
     You can set syslog_level from verbose (default) to warn to reduce syslog output.
 
+    fastd allows to configure a tree of peer groups and peers. By default, the
+    list of groups and peers configured in the *fastd* UCI config is completely
+    replaced by the list from site.conf on upgrades. To allow custom modifications
+    to the peer list, removal and modification of peers can be prevented by
+    setting the *preserve* option of a peer to ``1`` in UCI.
+
     The `tunneldigger` section is used to define the *tunneldigger* broker list.
 
     **Note:** It doesn't make sense to include both `fastd` and `tunneldigger`
diff --git a/package/gluon-mesh-vpn-fastd/luasrc/lib/gluon/upgrade/400-mesh-vpn-fastd b/package/gluon-mesh-vpn-fastd/luasrc/lib/gluon/upgrade/400-mesh-vpn-fastd
index 83eff0c7f817dd5fd2a77f6f9bb643cc0c9e2e1e..0312b29c120743d64b6bcd144908ef4a2214cd86 100755
--- a/package/gluon-mesh-vpn-fastd/luasrc/lib/gluon/upgrade/400-mesh-vpn-fastd
+++ b/package/gluon-mesh-vpn-fastd/luasrc/lib/gluon/upgrade/400-mesh-vpn-fastd
@@ -48,10 +48,43 @@ uci:section('fastd', 'fastd', 'mesh_vpn', {
 uci:delete('fastd', 'mesh_vpn', 'user')
 
 
+-- Collect list of groups that have peers with 'preserve' flag
+local preserve_groups = {}
+
+local function preserve_group(name)
+	if not name or preserve_groups[name] then
+		return
+	end
+	preserve_groups[name] = true
+
+	local parent = uci:get('fastd', name, 'group')
+	preserve_group(parent)
+end
+
+uci:foreach('fastd', 'peer', function(peer)
+	if peer.net == 'mesh_vpn' and peer.preserve == '1' then
+		preserve_group(peer.group)
+	end
+end)
+
+
+-- Clean up previous configuration
+uci:delete_all('fastd', 'peer', function(peer)
+	return (peer.net == 'mesh_vpn' and peer.preserve ~= '1')
+end)
+uci:delete_all('fastd', 'peer_group', function(group)
+	return (group.net == 'mesh_vpn' and not preserve_groups[group['.name']])
+end)
+
+
 local add_groups
 
 local function add_peer(group, name, config)
-	uci:section('fastd', 'peer', group .. '_peer_' .. name, {
+	local uci_name = group .. '_peer_' .. name
+	if uci:get_bool('fastd', uci_name, 'preserve') then
+		return
+	end
+	uci:section('fastd', 'peer', uci_name, {
 		enabled = true,
 		net = 'mesh_vpn',
 		group = group,
@@ -61,12 +94,6 @@ local function add_peer(group, name, config)
 end
 
 local function add_group(name, config, parent)
-	uci:delete('fastd', name)
-	uci:delete_all('fastd', 'peer',	function(peer)
-		return (peer.net == 'mesh_vpn' and peer.group == name)
-	end)
-
-
 	uci:section('fastd', 'peer_group', name, {
 		enabled = true,
 		net = 'mesh_vpn',
@@ -74,10 +101,8 @@ local function add_group(name, config, parent)
 		peer_limit = config.limit,
 	})
 
-	if config.peers then
-		for peername, peerconfig in pairs(config.peers) do
-			add_peer(name, peername, peerconfig)
-		end
+	for peername, peerconfig in pairs(config.peers or {}) do
+		add_peer(name, peername, peerconfig)
 	end
 
 	add_groups(name, config.groups, name)
@@ -85,10 +110,8 @@ end
 
 -- declared local above
 function add_groups(prefix, groups, parent)
-	if groups then
-		for name, group in pairs(groups) do
-			add_group(prefix .. '_' .. name, group, parent)
-		end
+	for name, group in pairs(groups or {}) do
+		add_group(prefix .. '_' .. name, group, parent)
 	end
 end