diff --git a/substrate/client/cli/src/params/network_params.rs b/substrate/client/cli/src/params/network_params.rs
index 0b53616b9ed13f9cf049c50896fcce5a67cf7f31..4a926fdce8bbf6bd65e54288137c6cc94861d00d 100644
--- a/substrate/client/cli/src/params/network_params.rs
+++ b/substrate/client/cli/src/params/network_params.rs
@@ -36,10 +36,14 @@ pub struct NetworkParams {
 	#[structopt(long = "reserved-nodes", value_name = "ADDR")]
 	pub reserved_nodes: Vec<MultiaddrWithPeerId>,
 
-	/// Whether to only allow connections to/from reserved nodes.
+	/// Whether to only synchronize the chain with reserved nodes.
 	///
-	/// If you are a validator your node might still connect to other validator
-	/// nodes regardless of whether they are defined as reserved nodes.
+	/// Also disables automatic peer discovery.
+	///
+	/// TCP connections might still be established with non-reserved nodes.
+	/// In particular, if you are a validator your node might still connect to other
+	/// validator nodes and collator nodes regardless of whether they are defined as
+	/// reserved nodes.
 	#[structopt(long = "reserved-only")]
 	pub reserved_only: bool,
 
@@ -173,6 +177,7 @@ impl NetworkParams {
 				wasm_external_transport: None,
 			},
 			max_parallel_downloads: self.max_parallel_downloads,
+			enable_dht_random_walk: !self.reserved_only,
 			allow_non_globals_in_dht,
 			kademlia_disjoint_query_paths: self.kademlia_disjoint_query_paths,
 			yamux_window_size: None,
diff --git a/substrate/client/network/src/config.rs b/substrate/client/network/src/config.rs
index 3eb53dabf045919fb19d1207a73a1a1fd77d824b..29a0128b87ea7161f729d2d2940df53841a513b5 100644
--- a/substrate/client/network/src/config.rs
+++ b/substrate/client/network/src/config.rs
@@ -408,10 +408,17 @@ pub struct NetworkConfiguration {
 	pub transport: TransportConfig,
 	/// Maximum number of peers to ask the same blocks in parallel.
 	pub max_parallel_downloads: u32,
+
+	/// True if Kademlia random discovery should be enabled.
+	///
+	/// If true, the node will automatically randomly walk the DHT in order to find new peers.
+	pub enable_dht_random_walk: bool,
+
 	/// Should we insert non-global addresses into the DHT?
 	pub allow_non_globals_in_dht: bool,
-	/// Require iterative Kademlia DHT queries to use disjoint paths for increased resiliency in the
-	/// presence of potentially adversarial nodes.
+
+	/// Require iterative Kademlia DHT queries to use disjoint paths for increased resiliency in
+	/// the presence of potentially adversarial nodes.
 	pub kademlia_disjoint_query_paths: bool,
 
 	/// Size of Yamux receive window of all substreams. `None` for the default (256kiB).
@@ -461,6 +468,7 @@ impl NetworkConfiguration {
 				wasm_external_transport: None,
 			},
 			max_parallel_downloads: 5,
+			enable_dht_random_walk: true,
 			allow_non_globals_in_dht: false,
 			kademlia_disjoint_query_paths: false,
 			yamux_window_size: None,
diff --git a/substrate/client/network/src/discovery.rs b/substrate/client/network/src/discovery.rs
index d9d28569ad30b0aa949c00db3f9163611cd95e87..87b533ef77dc14c36662949f54919274149d7f12 100644
--- a/substrate/client/network/src/discovery.rs
+++ b/substrate/client/network/src/discovery.rs
@@ -80,6 +80,7 @@ const MAX_KNOWN_EXTERNAL_ADDRESSES: usize = 32;
 pub struct DiscoveryConfig {
 	local_peer_id: PeerId,
 	user_defined: Vec<(PeerId, Multiaddr)>,
+	dht_random_walk: bool,
 	allow_private_ipv4: bool,
 	allow_non_globals_in_dht: bool,
 	discovery_only_if_under_num: u64,
@@ -94,6 +95,7 @@ impl DiscoveryConfig {
 		DiscoveryConfig {
 			local_peer_id: local_public_key.into_peer_id(),
 			user_defined: Vec::new(),
+			dht_random_walk: true,
 			allow_private_ipv4: true,
 			allow_non_globals_in_dht: false,
 			discovery_only_if_under_num: std::u64::MAX,
@@ -118,6 +120,13 @@ impl DiscoveryConfig {
 		self
 	}
 
+	/// Whether the discovery behaviour should periodically perform a random
+	/// walk on the DHT to discover peers.
+	pub fn with_dht_random_walk(&mut self, value: bool) -> &mut Self {
+		self.dht_random_walk = value;
+		self
+	}
+
 	/// Should private IPv4 addresses be reported?
 	pub fn allow_private_ipv4(&mut self, value: bool) -> &mut Self {
 		self.allow_private_ipv4 = value;
@@ -163,6 +172,7 @@ impl DiscoveryConfig {
 		let DiscoveryConfig {
 			local_peer_id,
 			user_defined,
+			dht_random_walk,
 			allow_private_ipv4,
 			allow_non_globals_in_dht,
 			discovery_only_if_under_num,
@@ -197,7 +207,11 @@ impl DiscoveryConfig {
 		DiscoveryBehaviour {
 			user_defined,
 			kademlias,
-			next_kad_random_query: Delay::new(Duration::new(0, 0)),
+			next_kad_random_query: if dht_random_walk {
+				Some(Delay::new(Duration::new(0, 0)))
+			} else {
+				None
+			},
 			duration_to_next_kad: Duration::from_secs(1),
 			pending_events: VecDeque::new(),
 			local_peer_id,
@@ -229,8 +243,9 @@ pub struct DiscoveryBehaviour {
 	/// Discovers nodes on the local network.
 	#[cfg(not(target_os = "unknown"))]
 	mdns: MdnsWrapper,
-	/// Stream that fires when we need to perform the next random Kademlia query.
-	next_kad_random_query: Delay,
+	/// Stream that fires when we need to perform the next random Kademlia query. `None` if
+	/// random walking is disabled.
+	next_kad_random_query: Option<Delay>,
 	/// After `next_kad_random_query` triggers, the next one triggers after this duration.
 	duration_to_next_kad: Duration,
 	/// Events to return in priority when polled.
@@ -434,6 +449,8 @@ pub enum DiscoveryOut {
 	ValuePutFailed(record::Key, Duration),
 
 	/// Started a random Kademlia query for each DHT identified by the given `ProtocolId`s.
+	///
+	/// Only happens if [`DiscoveryConfig::with_dht_random_walk`] has been configured to `true`.
 	RandomKademliaStarted(Vec<ProtocolId>),
 }
 
@@ -602,34 +619,36 @@ impl NetworkBehaviour for DiscoveryBehaviour {
 		}
 
 		// Poll the stream that fires when we need to start a random Kademlia query.
-		while let Poll::Ready(_) = self.next_kad_random_query.poll_unpin(cx) {
-			let actually_started = if self.num_connections < self.discovery_only_if_under_num {
-				let random_peer_id = PeerId::random();
-				debug!(target: "sub-libp2p",
-					"Libp2p <= Starting random Kademlia request for {:?}",
-					random_peer_id);
-				for k in self.kademlias.values_mut() {
-					k.get_closest_peers(random_peer_id.clone());
+		if let Some(next_kad_random_query) = self.next_kad_random_query.as_mut() {
+			while let Poll::Ready(_) = next_kad_random_query.poll_unpin(cx) {
+				let actually_started = if self.num_connections < self.discovery_only_if_under_num {
+					let random_peer_id = PeerId::random();
+					debug!(target: "sub-libp2p",
+						"Libp2p <= Starting random Kademlia request for {:?}",
+						random_peer_id);
+					for k in self.kademlias.values_mut() {
+						k.get_closest_peers(random_peer_id.clone());
+					}
+					true
+				} else {
+					debug!(
+						target: "sub-libp2p",
+						"Kademlia paused due to high number of connections ({})",
+						self.num_connections
+					);
+					false
+				};
+
+				// Schedule the next random query with exponentially increasing delay,
+				// capped at 60 seconds.
+				*next_kad_random_query = Delay::new(self.duration_to_next_kad);
+				self.duration_to_next_kad = cmp::min(self.duration_to_next_kad * 2,
+					Duration::from_secs(60));
+
+				if actually_started {
+					let ev = DiscoveryOut::RandomKademliaStarted(self.kademlias.keys().cloned().collect());
+					return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev));
 				}
-				true
-			} else {
-				debug!(
-					target: "sub-libp2p",
-					"Kademlia paused due to high number of connections ({})",
-					self.num_connections
-				);
-				false
-			};
-
-			// Schedule the next random query with exponentially increasing delay,
-			// capped at 60 seconds.
-			self.next_kad_random_query = Delay::new(self.duration_to_next_kad);
-			self.duration_to_next_kad = cmp::min(self.duration_to_next_kad * 2,
-				Duration::from_secs(60));
-
-			if actually_started {
-				let ev = DiscoveryOut::RandomKademliaStarted(self.kademlias.keys().cloned().collect());
-				return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev));
 			}
 		}
 
diff --git a/substrate/client/network/src/service.rs b/substrate/client/network/src/service.rs
index 58c623a8f5f1f6fa84f74789cbb9140d4c13655c..cb1cc4f3b77a65146c582c6865936617a815f643 100644
--- a/substrate/client/network/src/service.rs
+++ b/substrate/client/network/src/service.rs
@@ -268,6 +268,7 @@ impl<B: BlockT + 'static, H: ExHashT> NetworkWorker<B, H> {
 				config.with_user_defined(known_addresses);
 				config.discovery_limit(u64::from(params.network_config.default_peers_set.out_peers) + 15);
 				config.add_protocol(params.protocol_id.clone());
+				config.with_dht_random_walk(params.network_config.enable_dht_random_walk);
 				config.allow_non_globals_in_dht(params.network_config.allow_non_globals_in_dht);
 				config.use_kademlia_disjoint_query_paths(params.network_config.kademlia_disjoint_query_paths);