diff --git a/bridges/relays/lib-substrate-relay/src/cli/relay_parachains.rs b/bridges/relays/lib-substrate-relay/src/cli/relay_parachains.rs
index 1425233add1ee3a58b1729a10ed2bb2d2fbc8667..00f8cf79ef1fb54577954cf198e7296819591a43 100644
--- a/bridges/relays/lib-substrate-relay/src/cli/relay_parachains.rs
+++ b/bridges/relays/lib-substrate-relay/src/cli/relay_parachains.rs
@@ -18,6 +18,8 @@
 
 use async_std::sync::Mutex;
 use async_trait::async_trait;
+use bp_polkadot_core::BlockNumber as RelayBlockNumber;
+use bp_runtime::HeaderIdProvider;
 use parachains_relay::parachains_loop::{AvailableHeader, SourceClient, TargetClient};
 use relay_substrate_client::Parachain;
 use relay_utils::metrics::{GlobalMetrics, StandaloneMetric};
@@ -51,6 +53,21 @@ pub struct RelayParachainsParams {
 	prometheus_params: PrometheusParams,
 }
 
+/// Single parachains head relaying params.
+#[derive(StructOpt)]
+pub struct RelayParachainHeadParams {
+	#[structopt(flatten)]
+	source: SourceConnectionParams,
+	#[structopt(flatten)]
+	target: TargetConnectionParams,
+	#[structopt(flatten)]
+	target_sign: TargetSigningParams,
+	/// Prove parachain head at that relay block number. This relay header must be previously
+	/// proved to the target chain.
+	#[structopt(long)]
+	at_relay_block: RelayBlockNumber,
+}
+
 /// Trait used for relaying parachains finality between 2 chains.
 #[async_trait]
 pub trait ParachainsRelayer: ParachainToRelayHeadersCliBridge
@@ -94,4 +111,38 @@ where
 		.await
 		.map_err(|e| anyhow::format_err!("{}", e))
 	}
+
+	/// Relay single parachain head. No checks are made to ensure that transaction will succeed.
+	async fn relay_parachain_head(data: RelayParachainHeadParams) -> anyhow::Result<()> {
+		let source_chain_client = data.source.into_client::<Self::SourceRelay>().await?;
+		let at_relay_block = source_chain_client
+			.header_by_number(data.at_relay_block)
+			.await
+			.map_err(|e| anyhow::format_err!("{}", e))?
+			.id();
+
+		let source_client = ParachainsSource::<Self::ParachainFinality>::new(
+			source_chain_client.clone(),
+			Arc::new(Mutex::new(AvailableHeader::Missing)),
+		);
+
+		let target_transaction_params = TransactionParams {
+			signer: data.target_sign.to_keypair::<Self::Target>()?,
+			mortality: data.target_sign.target_transactions_mortality,
+		};
+		let target_chain_client = data.target.into_client::<Self::Target>().await?;
+		let target_client = ParachainsTarget::<Self::ParachainFinality>::new(
+			source_chain_client,
+			target_chain_client,
+			target_transaction_params,
+		);
+
+		parachains_relay::parachains_loop::relay_single_head(
+			source_client,
+			target_client,
+			at_relay_block,
+		)
+		.await
+		.map_err(|_| anyhow::format_err!("The command has failed"))
+	}
 }
diff --git a/bridges/relays/parachains/src/parachains_loop.rs b/bridges/relays/parachains/src/parachains_loop.rs
index 55f236eeac1d842ff9ed0f337cf1ea2c725dc763..fd73ca2d46c00f8e05bb05a14a7fa4104ef898c4 100644
--- a/bridges/relays/parachains/src/parachains_loop.rs
+++ b/bridges/relays/parachains/src/parachains_loop.rs
@@ -139,6 +139,33 @@ pub fn metrics_prefix<P: ParachainsPipeline>() -> String {
 	)
 }
 
+/// Relay single parachain head.
+pub async fn relay_single_head<P: ParachainsPipeline>(
+	source_client: impl SourceClient<P>,
+	target_client: impl TargetClient<P>,
+	at_relay_block: HeaderIdOf<P::SourceRelayChain>,
+) -> Result<(), ()>
+where
+	P::SourceRelayChain: Chain<BlockNumber = RelayBlockNumber>,
+{
+	let tx_tracker =
+		submit_selected_head::<P, _>(&source_client, &target_client, at_relay_block, false)
+			.await
+			.map_err(drop)?;
+	match tx_tracker.wait().await {
+		TrackedTransactionStatus::Finalized(_) => Ok(()),
+		TrackedTransactionStatus::Lost => {
+			log::error!(
+				"Transaction with {} header at relay header {:?} is considered lost at {}",
+				P::SourceParachain::NAME,
+				at_relay_block,
+				P::TargetChain::NAME,
+			);
+			Err(())
+		},
+	}
+}
+
 /// Run parachain heads synchronization.
 pub async fn run<P: ParachainsPipeline>(
 	source_client: impl SourceClient<P>,
@@ -361,52 +388,63 @@ where
 		);
 
 		if is_update_required {
-			let (head_proof, head_hash) =
-				source_client.prove_parachain_head(prove_at_relay_block).await.map_err(|e| {
-					log::warn!(
-						target: "bridge",
-						"Failed to prove {} parachain ParaId({}) heads: {:?}",
-						P::SourceRelayChain::NAME,
-						P::SourceParachain::PARACHAIN_ID,
-						e,
-					);
-					FailedClient::Source
-				})?;
-			log::info!(
-				target: "bridge",
-				"Submitting {} parachain ParaId({}) head update transaction to {}. Para hash at source relay {:?}: {:?}",
-				P::SourceRelayChain::NAME,
-				P::SourceParachain::PARACHAIN_ID,
-				P::TargetChain::NAME,
+			let transaction_tracker = submit_selected_head::<P, _>(
+				&source_client,
+				&target_client,
 				prove_at_relay_block,
-				head_hash,
-			);
-
-			let transaction_tracker = target_client
-				.submit_parachain_head_proof(
-					prove_at_relay_block,
-					head_hash,
-					head_proof,
-					only_free_headers,
-				)
-				.await
-				.map_err(|e| {
-					log::warn!(
-						target: "bridge",
-						"Failed to submit {} parachain ParaId({}) heads proof to {}: {:?}",
-						P::SourceRelayChain::NAME,
-						P::SourceParachain::PARACHAIN_ID,
-						P::TargetChain::NAME,
-						e,
-					);
-					FailedClient::Target
-				})?;
+				only_free_headers,
+			)
+			.await?;
 			submitted_heads_tracker =
 				Some(SubmittedHeadsTracker::<P>::new(head_at_source, transaction_tracker));
 		}
 	}
 }
 
+/// Prove and submit parachain head at given relay chain block.
+async fn submit_selected_head<P: ParachainsPipeline, TC: TargetClient<P>>(
+	source_client: &impl SourceClient<P>,
+	target_client: &TC,
+	prove_at_relay_block: HeaderIdOf<P::SourceRelayChain>,
+	only_free_headers: bool,
+) -> Result<TC::TransactionTracker, FailedClient> {
+	let (head_proof, head_hash) =
+		source_client.prove_parachain_head(prove_at_relay_block).await.map_err(|e| {
+			log::warn!(
+				target: "bridge",
+				"Failed to prove {} parachain ParaId({}) heads: {:?}",
+				P::SourceRelayChain::NAME,
+				P::SourceParachain::PARACHAIN_ID,
+				e,
+			);
+			FailedClient::Source
+		})?;
+	log::info!(
+		target: "bridge",
+		"Submitting {} parachain ParaId({}) head update transaction to {}. Para hash at source relay {:?}: {:?}",
+		P::SourceRelayChain::NAME,
+		P::SourceParachain::PARACHAIN_ID,
+		P::TargetChain::NAME,
+		prove_at_relay_block,
+		head_hash,
+	);
+
+	target_client
+		.submit_parachain_head_proof(prove_at_relay_block, head_hash, head_proof, only_free_headers)
+		.await
+		.map_err(|e| {
+			log::warn!(
+				target: "bridge",
+				"Failed to submit {} parachain ParaId({}) heads proof to {}: {:?}",
+				P::SourceRelayChain::NAME,
+				P::SourceParachain::PARACHAIN_ID,
+				P::TargetChain::NAME,
+				e,
+			);
+			FailedClient::Target
+		})
+}
+
 /// Returns `true` if we need to submit parachain-head-update transaction.
 fn is_update_required<P: ParachainsPipeline>(
 	head_at_source: AvailableHeader<HeaderIdOf<P::SourceParachain>>,