diff --git a/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto b/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto
index 5be0bcd853da..f3c509fe11b4 100644
--- a/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto
+++ b/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto
@@ -69,6 +69,22 @@ message TcpProxy {
         "envoy.config.filter.network.tcp_proxy.v2.TcpProxy.TunnelingConfig";
 
     // The hostname to send in the synthesized CONNECT headers to the upstream proxy.
+    // This field evaluates command operators if set, otherwise returns hostname as is.
+    //
+    // Example: dynamically set hostname using downstream SNI
+    //
+    // .. code-block:: yaml
+    //
+    //    tunneling_config:
+    //      hostname: "%REQUESTED_SERVER_NAME%:443"
+    //
+    // Example: dynamically set hostname using dynamic metadata
+    //
+    // .. code-block: yaml
+    //
+    //    tunneling_config:
+    //      hostname: "%DYNAMIC_METADATA(tunnel:address)%"
+    //
     string hostname = 1 [(validate.rules).string = {min_len: 1}];
 
     // Use POST method instead of CONNECT method to tunnel the TCP stream.
diff --git a/changelogs/current.yaml b/changelogs/current.yaml
index e2567e8e9424..37ce7815218b 100644
--- a/changelogs/current.yaml
+++ b/changelogs/current.yaml
@@ -32,6 +32,9 @@ behavior_changes:
     Fixed metric tag extraction so that :ref:`stat_prefix <envoy_v3_api_field_extensions.filters.network.redis_proxy.v3.RedisProxy.stat_prefix>`
     is properly extracted. This changes the Prometheus name from
     envoy_redis_myprefix_command_pttl_latency_sum{} to envoy_redis_command_pttl_latency_sum{envoy_redis_prefix="myprefix"}.
+- area: tcp_proxy
+  change: |
+    added support for command operators in :ref:`TunnelingConfig hostname <envoy_v3_api_field_extensions.filters.network.tcp_proxy.v3.TcpProxy.TunnelingConfig.hostname>` to dynamically set upstream hostname.
 
 minor_behavior_changes:
 - area: thrift
diff --git a/envoy/tcp/upstream.h b/envoy/tcp/upstream.h
index 80c385e96aa5..be6170c8ae7d 100644
--- a/envoy/tcp/upstream.h
+++ b/envoy/tcp/upstream.h
@@ -25,8 +25,10 @@ class GenericUpstream;
 class TunnelingConfigHelper {
 public:
   virtual ~TunnelingConfigHelper() = default;
+
   // The host name of the tunneling upstream HTTP request.
-  virtual const std::string& hostname() const PURE;
+  // This function evaluates command operators if specified. Otherwise it returns host name as is.
+  virtual std::string host(const StreamInfo::StreamInfo& stream_info) const PURE;
 
   // The method of the upstream HTTP request. True if using POST method, CONNECT otherwise.
   virtual bool usePost() const PURE;
diff --git a/source/common/tcp_proxy/BUILD b/source/common/tcp_proxy/BUILD
index bd2a496c4eef..01743ff3082b 100644
--- a/source/common/tcp_proxy/BUILD
+++ b/source/common/tcp_proxy/BUILD
@@ -60,6 +60,7 @@ envoy_cc_library(
         "//source/common/common:empty_string",
         "//source/common/common:macros",
         "//source/common/common:minimal_logger_lib",
+        "//source/common/formatter:substitution_format_string_lib",
         "//source/common/http:codec_client_lib",
         "//source/common/network:application_protocol_lib",
         "//source/common/network:cidr_range_lib",
@@ -71,6 +72,7 @@ envoy_cc_library(
         "//source/common/network:upstream_server_name_lib",
         "//source/common/network:upstream_socket_options_filter_state_lib",
         "//source/common/network:utility_lib",
+        "//source/common/protobuf:utility_lib",
         "//source/common/router:metadatamatchcriteria_lib",
         "//source/common/stream_info:stream_info_lib",
         "//source/common/upstream:load_balancer_lib",
diff --git a/source/common/tcp_proxy/tcp_proxy.cc b/source/common/tcp_proxy/tcp_proxy.cc
index b388c17c2096..9bcff5c13123 100644
--- a/source/common/tcp_proxy/tcp_proxy.cc
+++ b/source/common/tcp_proxy/tcp_proxy.cc
@@ -9,6 +9,7 @@
 #include "envoy/event/dispatcher.h"
 #include "envoy/event/timer.h"
 #include "envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.pb.h"
+#include "envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.pb.validate.h"
 #include "envoy/stats/scope.h"
 #include "envoy/upstream/cluster_manager.h"
 #include "envoy/upstream/upstream.h"
@@ -78,7 +79,7 @@ Config::SharedConfig::SharedConfig(
   }
   if (config.has_tunneling_config()) {
     tunneling_config_helper_ =
-        std::make_unique<TunnelingConfigHelperImpl>(config.tunneling_config());
+        std::make_unique<TunnelingConfigHelperImpl>(config.tunneling_config(), context);
   }
   if (config.has_max_downstream_connection_duration()) {
     const uint64_t connection_duration =
@@ -539,6 +540,26 @@ const Router::MetadataMatchCriteria* Filter::metadataMatchCriteria() {
   }
 }
 
+TunnelingConfigHelperImpl::TunnelingConfigHelperImpl(
+    const envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy_TunnelingConfig&
+        config_message,
+    Server::Configuration::FactoryContext& context)
+    : use_post_(config_message.use_post()),
+      header_parser_(Envoy::Router::HeaderParser::configure(config_message.headers_to_add())) {
+  envoy::config::core::v3::SubstitutionFormatString substitution_format_config;
+  substitution_format_config.mutable_text_format_source()->set_inline_string(
+      config_message.hostname());
+  hostname_fmt_ = Formatter::SubstitutionFormatStringUtils::fromProtoConfig(
+      substitution_format_config, context);
+}
+
+std::string TunnelingConfigHelperImpl::host(const StreamInfo::StreamInfo& stream_info) const {
+  return hostname_fmt_->format(*Http::StaticEmptyHeaders::get().request_headers,
+                               *Http::StaticEmptyHeaders::get().response_headers,
+                               *Http::StaticEmptyHeaders::get().response_trailers, stream_info,
+                               absl::string_view());
+}
+
 void Filter::onConnectTimeout() {
   ENVOY_CONN_LOG(debug, "connect timeout", read_callbacks_->connection());
   read_callbacks_->upstreamHost()->outlierDetector().putResult(
diff --git a/source/common/tcp_proxy/tcp_proxy.h b/source/common/tcp_proxy/tcp_proxy.h
index 74406a610c09..48d4bc55ae0f 100644
--- a/source/common/tcp_proxy/tcp_proxy.h
+++ b/source/common/tcp_proxy/tcp_proxy.h
@@ -22,6 +22,8 @@
 #include "envoy/upstream/upstream.h"
 
 #include "source/common/common/logger.h"
+#include "source/common/formatter/substitution_format_string.h"
+#include "source/common/http/header_map_impl.h"
 #include "source/common/network/cidr_range.h"
 #include "source/common/network/filter_impl.h"
 #include "source/common/network/hash_policy.h"
@@ -114,17 +116,16 @@ class TunnelingConfigHelperImpl : public TunnelingConfigHelper {
 public:
   TunnelingConfigHelperImpl(
       const envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy_TunnelingConfig&
-          config_message)
-      : hostname_(config_message.hostname()), use_post_(config_message.use_post()),
-        header_parser_(Envoy::Router::HeaderParser::configure(config_message.headers_to_add())) {}
-  const std::string& hostname() const override { return hostname_; }
+          config_message,
+      Server::Configuration::FactoryContext& context);
+  std::string host(const StreamInfo::StreamInfo& stream_info) const override;
   bool usePost() const override { return use_post_; }
   Envoy::Http::HeaderEvaluator& headerEvaluator() const override { return *header_parser_; }
 
 private:
-  const std::string hostname_;
   const bool use_post_;
   std::unique_ptr<Envoy::Router::HeaderParser> header_parser_;
+  Formatter::FormatterPtr hostname_fmt_;
 };
 
 /**
diff --git a/source/common/tcp_proxy/upstream.cc b/source/common/tcp_proxy/upstream.cc
index fe9579234ae2..53dbff2a2e59 100644
--- a/source/common/tcp_proxy/upstream.cc
+++ b/source/common/tcp_proxy/upstream.cc
@@ -277,7 +277,7 @@ void Http2Upstream::setRequestEncoder(Http::RequestEncoder& request_encoder, boo
       is_ssl ? Http::Headers::get().SchemeValues.Https : Http::Headers::get().SchemeValues.Http;
   auto headers = Http::createHeaderMap<Http::RequestHeaderMapImpl>({
       {Http::Headers::get().Method, config_.usePost() ? "POST" : "CONNECT"},
-      {Http::Headers::get().Host, config_.hostname()},
+      {Http::Headers::get().Host, config_.host(downstream_info_)},
   });
 
   if (config_.usePost()) {
@@ -309,7 +309,7 @@ void Http1Upstream::setRequestEncoder(Http::RequestEncoder& request_encoder, boo
 
   auto headers = Http::createHeaderMap<Http::RequestHeaderMapImpl>({
       {Http::Headers::get().Method, config_.usePost() ? "POST" : "CONNECT"},
-      {Http::Headers::get().Host, config_.hostname()},
+      {Http::Headers::get().Host, config_.host(downstream_info_)},
   });
 
   if (config_.usePost()) {
diff --git a/test/common/tcp_proxy/BUILD b/test/common/tcp_proxy/BUILD
index 45be486491e1..0630b106fd5c 100644
--- a/test/common/tcp_proxy/BUILD
+++ b/test/common/tcp_proxy/BUILD
@@ -79,6 +79,7 @@ envoy_cc_test(
     deps = [
         "//source/common/tcp_proxy",
         "//test/mocks/http:http_mocks",
+        "//test/mocks/server:factory_context_mocks",
         "//test/mocks/tcp:tcp_mocks",
         "//test/mocks/upstream:cluster_manager_mocks",
         "//test/test_common:test_runtime_lib",
diff --git a/test/common/tcp_proxy/upstream_test.cc b/test/common/tcp_proxy/upstream_test.cc
index b42a43cb5cf1..fa3e5f4a956a 100644
--- a/test/common/tcp_proxy/upstream_test.cc
+++ b/test/common/tcp_proxy/upstream_test.cc
@@ -6,6 +6,7 @@
 #include "test/mocks/buffer/mocks.h"
 #include "test/mocks/http/mocks.h"
 #include "test/mocks/http/stream_encoder.h"
+#include "test/mocks/server/factory_context.h"
 #include "test/mocks/tcp/mocks.h"
 #include "test/test_common/environment.h"
 #include "test/test_common/network_utility.h"
@@ -40,10 +41,11 @@ template <typename T> class HttpUpstreamTest : public testing::Test {
   }
 
   void setupUpstream() {
-    config_ = std::make_unique<TunnelingConfigHelperImpl>(config_message_);
+    config_ = std::make_unique<TunnelingConfigHelperImpl>(config_message_, context_);
     upstream_ = std::make_unique<T>(callbacks_, *this->config_, downstream_stream_info_);
     upstream_->setRequestEncoder(encoder_, true);
   }
+
   NiceMock<StreamInfo::MockStreamInfo> downstream_stream_info_;
   Http::MockRequestEncoder encoder_;
   Http::MockHttp1StreamEncoderOptions stream_encoder_options_;
@@ -51,6 +53,7 @@ template <typename T> class HttpUpstreamTest : public testing::Test {
   TcpProxy_TunnelingConfig config_message_;
   std::unique_ptr<TunnelingConfigHelper> config_;
   std::unique_ptr<HttpUpstream> upstream_;
+  NiceMock<Server::Configuration::MockFactoryContext> context_;
 };
 
 using testing::Types;
@@ -207,14 +210,23 @@ template <typename T> class HttpUpstreamRequestEncoderTest : public testing::Tes
   }
 
   void setupUpstream() {
-    config_ = std::make_unique<TunnelingConfigHelperImpl>(config_message_);
+    config_ = std::make_unique<TunnelingConfigHelperImpl>(config_message_, context_);
     upstream_ = std::make_unique<T>(callbacks_, *this->config_, this->downstream_stream_info_);
   }
 
+  void populateMetadata(envoy::config::core::v3::Metadata& metadata, const std::string& ns,
+                        const std::string& key, const std::string& value) {
+    ProtobufWkt::Struct struct_obj;
+    auto& fields_map = *struct_obj.mutable_fields();
+    fields_map[key] = ValueUtil::stringValue(value);
+    (*metadata.mutable_filter_metadata())[ns] = struct_obj;
+  }
+
   NiceMock<StreamInfo::MockStreamInfo> downstream_stream_info_;
   Http::MockRequestEncoder encoder_;
   Http::MockHttp1StreamEncoderOptions stream_encoder_options_;
   NiceMock<Tcp::ConnectionPool::MockUpstreamCallbacks> callbacks_;
+  NiceMock<Server::Configuration::MockFactoryContext> context_;
 
   std::unique_ptr<HttpUpstream> upstream_;
   TcpProxy_TunnelingConfig config_message_;
@@ -232,7 +244,7 @@ TYPED_TEST(HttpUpstreamRequestEncoderTest, RequestEncoderOld) {
   std::unique_ptr<Http::RequestHeaderMapImpl> expected_headers;
   expected_headers = Http::createHeaderMap<Http::RequestHeaderMapImpl>({
       {Http::Headers::get().Method, "CONNECT"},
-      {Http::Headers::get().Host, this->config_->hostname()},
+      {Http::Headers::get().Host, this->config_->host(this->downstream_stream_info_)},
   });
 
   if (this->is_http2_) {
@@ -252,7 +264,7 @@ TYPED_TEST(HttpUpstreamRequestEncoderTest, RequestEncoder) {
   std::unique_ptr<Http::RequestHeaderMapImpl> expected_headers;
   expected_headers = Http::createHeaderMap<Http::RequestHeaderMapImpl>({
       {Http::Headers::get().Method, "CONNECT"},
-      {Http::Headers::get().Host, this->config_->hostname()},
+      {Http::Headers::get().Host, this->config_->host(this->downstream_stream_info_)},
   });
 
   EXPECT_CALL(this->encoder_, encodeHeaders(HeaderMapEqualRef(expected_headers.get()), false));
@@ -265,7 +277,7 @@ TYPED_TEST(HttpUpstreamRequestEncoderTest, RequestEncoderUsePost) {
   std::unique_ptr<Http::RequestHeaderMapImpl> expected_headers;
   expected_headers = Http::createHeaderMap<Http::RequestHeaderMapImpl>({
       {Http::Headers::get().Method, "POST"},
-      {Http::Headers::get().Host, this->config_->hostname()},
+      {Http::Headers::get().Host, this->config_->host(this->downstream_stream_info_)},
       {Http::Headers::get().Path, "/"},
   });
 
@@ -300,7 +312,7 @@ TYPED_TEST(HttpUpstreamRequestEncoderTest, RequestEncoderHeaders) {
   std::unique_ptr<Http::RequestHeaderMapImpl> expected_headers;
   expected_headers = Http::createHeaderMap<Http::RequestHeaderMapImpl>({
       {Http::Headers::get().Method, "CONNECT"},
-      {Http::Headers::get().Host, this->config_->hostname()},
+      {Http::Headers::get().Host, this->config_->host(this->downstream_stream_info_)},
   });
 
   expected_headers->setCopy(Http::LowerCaseString("header0"), "value0");
@@ -328,7 +340,7 @@ TYPED_TEST(HttpUpstreamRequestEncoderTest, ConfigReuse) {
   std::unique_ptr<Http::RequestHeaderMapImpl> expected_headers;
   expected_headers = Http::createHeaderMap<Http::RequestHeaderMapImpl>({
       {Http::Headers::get().Method, "CONNECT"},
-      {Http::Headers::get().Host, this->config_->hostname()},
+      {Http::Headers::get().Host, this->config_->host(this->downstream_stream_info_)},
   });
 
   expected_headers->setCopy(Http::LowerCaseString("key"), "value1");
@@ -371,7 +383,7 @@ TYPED_TEST(HttpUpstreamRequestEncoderTest, RequestEncoderHeadersWithDownstreamIn
   std::unique_ptr<Http::RequestHeaderMapImpl> expected_headers;
   expected_headers = Http::createHeaderMap<Http::RequestHeaderMapImpl>({
       {Http::Headers::get().Method, "CONNECT"},
-      {Http::Headers::get().Host, this->config_->hostname()},
+      {Http::Headers::get().Host, this->config_->host(this->downstream_stream_info_)},
   });
 
   expected_headers->setCopy(Http::LowerCaseString("header0"), "value0");
@@ -383,7 +395,55 @@ TYPED_TEST(HttpUpstreamRequestEncoderTest, RequestEncoderHeadersWithDownstreamIn
       *Network::Test::getCanonicalLoopbackAddress(ip_versions[0]), 80);
   Network::ConnectionInfoSetterImpl connection_info(ip_port, ip_port);
   EXPECT_CALL(this->downstream_stream_info_, downstreamAddressProvider)
-      .WillOnce(testing::ReturnRef(connection_info));
+      .WillRepeatedly(testing::ReturnRef(connection_info));
+  EXPECT_CALL(this->encoder_, encodeHeaders(HeaderMapEqualRef(expected_headers.get()), false));
+  this->upstream_->setRequestEncoder(this->encoder_, false);
+}
+
+TYPED_TEST(HttpUpstreamRequestEncoderTest,
+           RequestEncoderHostnameWithDownstreamInfoRequestedServerName) {
+  this->config_message_.set_hostname("%REQUESTED_SERVER_NAME%:443");
+  this->setupUpstream();
+
+  std::unique_ptr<Http::RequestHeaderMapImpl> expected_headers;
+  expected_headers = Http::createHeaderMap<Http::RequestHeaderMapImpl>({
+      {Http::Headers::get().Method, "CONNECT"},
+      {Http::Headers::get().Host, "www.google.com:443"},
+  });
+
+  auto ip_versions = TestEnvironment::getIpVersionsForTest();
+  ASSERT_FALSE(ip_versions.empty());
+
+  auto ip_port = Network::Utility::getAddressWithPort(
+      *Network::Test::getCanonicalLoopbackAddress(ip_versions[0]), 80);
+  Network::ConnectionInfoSetterImpl connection_info(ip_port, ip_port);
+  connection_info.setRequestedServerName("www.google.com");
+  EXPECT_CALL(this->downstream_stream_info_, downstreamAddressProvider)
+      .Times(2)
+      .WillRepeatedly(testing::ReturnRef(connection_info));
+  EXPECT_CALL(this->encoder_, encodeHeaders(HeaderMapEqualRef(expected_headers.get()), false));
+  this->upstream_->setRequestEncoder(this->encoder_, false);
+}
+
+TYPED_TEST(HttpUpstreamRequestEncoderTest,
+           RequestEncoderHostnameWithDownstreamInfoDynamicMetadata) {
+  this->config_message_.set_hostname("%DYNAMIC_METADATA(tunnel:address)%:443");
+  this->setupUpstream();
+
+  std::unique_ptr<Http::RequestHeaderMapImpl> expected_headers;
+  expected_headers = Http::createHeaderMap<Http::RequestHeaderMapImpl>({
+      {Http::Headers::get().Method, "CONNECT"},
+      {Http::Headers::get().Host, "www.google.com:443"},
+  });
+
+  auto ip_versions = TestEnvironment::getIpVersionsForTest();
+  ASSERT_FALSE(ip_versions.empty());
+
+  envoy::config::core::v3::Metadata metadata;
+  this->populateMetadata(metadata, "tunnel", "address", "www.google.com");
+
+  EXPECT_CALL(testing::Const(this->downstream_stream_info_), dynamicMetadata())
+      .WillRepeatedly(testing::ReturnRef(metadata));
   EXPECT_CALL(this->encoder_, encodeHeaders(HeaderMapEqualRef(expected_headers.get()), false));
   this->upstream_->setRequestEncoder(this->encoder_, false);
 }
diff --git a/test/extensions/upstreams/tcp/generic/BUILD b/test/extensions/upstreams/tcp/generic/BUILD
index f4a37a797d2e..46881ead2768 100644
--- a/test/extensions/upstreams/tcp/generic/BUILD
+++ b/test/extensions/upstreams/tcp/generic/BUILD
@@ -14,6 +14,7 @@ envoy_cc_test(
     deps = [
         "//source/common/tcp_proxy",
         "//source/extensions/upstreams/tcp/generic:config",
+        "//test/mocks/server:factory_context_mocks",
         "//test/mocks/upstream:upstream_mocks",
     ],
 )
diff --git a/test/extensions/upstreams/tcp/generic/config_test.cc b/test/extensions/upstreams/tcp/generic/config_test.cc
index 5bbbbe5fd862..864f898b5bb8 100644
--- a/test/extensions/upstreams/tcp/generic/config_test.cc
+++ b/test/extensions/upstreams/tcp/generic/config_test.cc
@@ -1,6 +1,7 @@
 #include "source/common/tcp_proxy/tcp_proxy.h"
 #include "source/extensions/upstreams/tcp/generic/config.h"
 
+#include "test/mocks/server/factory_context.h"
 #include "test/mocks/tcp/mocks.h"
 #include "test/mocks/upstream/cluster_manager.h"
 #include "test/mocks/upstream/load_balancer_context.h"
@@ -31,12 +32,13 @@ class TcpConnPoolTest : public ::testing::Test {
   NiceMock<StreamInfo::MockStreamInfo> downstream_stream_info_;
   NiceMock<Network::MockConnection> connection_;
   Upstream::MockLoadBalancerContext lb_context_;
+  NiceMock<Server::Configuration::MockFactoryContext> context_;
 };
 
 TEST_F(TcpConnPoolTest, TestNoConnPool) {
   envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy_TunnelingConfig config_proto;
   config_proto.set_hostname("host");
-  const TcpProxy::TunnelingConfigHelperImpl config(config_proto);
+  const TcpProxy::TunnelingConfigHelperImpl config(config_proto, context_);
   EXPECT_CALL(thread_local_cluster_, httpConnPool(_, _, _)).WillOnce(Return(absl::nullopt));
   EXPECT_EQ(nullptr, factory_.createGenericConnPool(
                          thread_local_cluster_, TcpProxy::TunnelingConfigHelperOptConstRef(config),
@@ -49,7 +51,7 @@ TEST_F(TcpConnPoolTest, Http2Config) {
   EXPECT_CALL(thread_local_cluster_, info).WillOnce(Return(info));
   envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy_TunnelingConfig config_proto;
   config_proto.set_hostname("host");
-  const TcpProxy::TunnelingConfigHelperImpl config(config_proto);
+  const TcpProxy::TunnelingConfigHelperImpl config(config_proto, context_);
 
   EXPECT_CALL(thread_local_cluster_, httpConnPool(_, _, _)).WillOnce(Return(absl::nullopt));
   EXPECT_EQ(nullptr, factory_.createGenericConnPool(
@@ -65,7 +67,7 @@ TEST_F(TcpConnPoolTest, Http3Config) {
   EXPECT_CALL(thread_local_cluster_, info).Times(AnyNumber()).WillRepeatedly(Return(info));
   envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy_TunnelingConfig config_proto;
   config_proto.set_hostname("host");
-  const TcpProxy::TunnelingConfigHelperImpl config(config_proto);
+  const TcpProxy::TunnelingConfigHelperImpl config(config_proto, context_);
   EXPECT_CALL(thread_local_cluster_, httpConnPool(_, _, _)).WillOnce(Return(absl::nullopt));
   EXPECT_EQ(nullptr, factory_.createGenericConnPool(
                          thread_local_cluster_, TcpProxy::TunnelingConfigHelperOptConstRef(config),