diff --git a/interop-tests/src/arch.rs b/interop-tests/src/arch.rs
index a7755b95977..df36f8e5baf 100644
--- a/interop-tests/src/arch.rs
+++ b/interop-tests/src/arch.rs
@@ -245,7 +245,7 @@ pub(crate) mod wasm {
                     .with_behaviour(behaviour_constructor)?
                     .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(5)))
                     .build(),
-                format!("/ip4/{ip}/tcp/0/wss"),
+                format!("/ip4/{ip}/tcp/0/tls/ws"),
             ),
             (Transport::Ws, Some(SecProtocol::Noise), Some(Muxer::Yamux)) => (
                 libp2p::SwarmBuilder::with_new_identity()
@@ -262,7 +262,7 @@ pub(crate) mod wasm {
                     .with_behaviour(behaviour_constructor)?
                     .with_swarm_config(|c| c.with_idle_connection_timeout(Duration::from_secs(5)))
                     .build(),
-                format!("/ip4/{ip}/tcp/0/wss"),
+                format!("/ip4/{ip}/tcp/0/tls/ws"),
             ),
             (Transport::WebRtcDirect, None, None) => (
                 libp2p::SwarmBuilder::with_new_identity()
diff --git a/transports/websocket-websys/CHANGELOG.md b/transports/websocket-websys/CHANGELOG.md
index 70d866e6141..d0aeb509823 100644
--- a/transports/websocket-websys/CHANGELOG.md
+++ b/transports/websocket-websys/CHANGELOG.md
@@ -2,6 +2,9 @@
 
 - Implement refactored `Transport`.
   See [PR 4568](https://github.com/libp2p/rust-libp2p/pull/4568)
+- Add support for `/tls/ws` and keep `/wss` backward compatible.
+  See [PR 5523](https://github.com/libp2p/rust-libp2p/pull/5523).
+
 ## 0.3.3
 
 - Fix use-after-free handler invocation from JS side.
diff --git a/transports/websocket-websys/src/lib.rs b/transports/websocket-websys/src/lib.rs
index d2589715bbb..f353d92b204 100644
--- a/transports/websocket-websys/src/lib.rs
+++ b/transports/websocket-websys/src/lib.rs
@@ -137,9 +137,10 @@ fn extract_websocket_url(addr: &Multiaddr) -> Option<String> {
         _ => return None,
     };
 
-    let (scheme, wspath) = match protocols.next() {
-        Some(Protocol::Ws(path)) => ("ws", path.into_owned()),
-        Some(Protocol::Wss(path)) => ("wss", path.into_owned()),
+    let (scheme, wspath) = match (protocols.next(), protocols.next()) {
+        (Some(Protocol::Tls), Some(Protocol::Ws(path))) => ("wss", path.into_owned()),
+        (Some(Protocol::Ws(path)), _) => ("ws", path.into_owned()),
+        (Some(Protocol::Wss(path)), _) => ("wss", path.into_owned()),
         _ => return None,
     };
 
@@ -453,3 +454,103 @@ impl Drop for Connection {
             .clear_interval_with_handle(self.inner.buffered_amount_low_interval);
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use libp2p_identity::PeerId;
+
+    #[test]
+    fn extract_url() {
+        let peer_id = PeerId::random();
+
+        // Check `/tls/ws`
+        let addr = "/dns4/example.com/tcp/2222/tls/ws"
+            .parse::<Multiaddr>()
+            .unwrap();
+        let url = extract_websocket_url(&addr).unwrap();
+        assert_eq!(url, "wss://example.com:2222/");
+
+        // Check `/tls/ws` with `/p2p`
+        let addr = format!("/dns4/example.com/tcp/2222/tls/ws/p2p/{peer_id}")
+            .parse()
+            .unwrap();
+        let url = extract_websocket_url(&addr).unwrap();
+        assert_eq!(url, "wss://example.com:2222/");
+
+        // Check `/tls/ws` with `/ip4`
+        let addr = "/ip4/127.0.0.1/tcp/2222/tls/ws"
+            .parse::<Multiaddr>()
+            .unwrap();
+        let url = extract_websocket_url(&addr).unwrap();
+        assert_eq!(url, "wss://127.0.0.1:2222/");
+
+        // Check `/tls/ws` with `/ip6`
+        let addr = "/ip6/::1/tcp/2222/tls/ws".parse::<Multiaddr>().unwrap();
+        let url = extract_websocket_url(&addr).unwrap();
+        assert_eq!(url, "wss://[::1]:2222/");
+
+        // Check `/wss`
+        let addr = "/dns4/example.com/tcp/2222/wss"
+            .parse::<Multiaddr>()
+            .unwrap();
+        let url = extract_websocket_url(&addr).unwrap();
+        assert_eq!(url, "wss://example.com:2222/");
+
+        // Check `/wss` with `/p2p`
+        let addr = format!("/dns4/example.com/tcp/2222/wss/p2p/{peer_id}")
+            .parse()
+            .unwrap();
+        let url = extract_websocket_url(&addr).unwrap();
+        assert_eq!(url, "wss://example.com:2222/");
+
+        // Check `/wss` with `/ip4`
+        let addr = "/ip4/127.0.0.1/tcp/2222/wss".parse::<Multiaddr>().unwrap();
+        let url = extract_websocket_url(&addr).unwrap();
+        assert_eq!(url, "wss://127.0.0.1:2222/");
+
+        // Check `/wss` with `/ip6`
+        let addr = "/ip6/::1/tcp/2222/wss".parse::<Multiaddr>().unwrap();
+        let url = extract_websocket_url(&addr).unwrap();
+        assert_eq!(url, "wss://[::1]:2222/");
+
+        // Check `/ws`
+        let addr = "/dns4/example.com/tcp/2222/ws"
+            .parse::<Multiaddr>()
+            .unwrap();
+        let url = extract_websocket_url(&addr).unwrap();
+        assert_eq!(url, "ws://example.com:2222/");
+
+        // Check `/ws` with `/p2p`
+        let addr = format!("/dns4/example.com/tcp/2222/ws/p2p/{peer_id}")
+            .parse()
+            .unwrap();
+        let url = extract_websocket_url(&addr).unwrap();
+        assert_eq!(url, "ws://example.com:2222/");
+
+        // Check `/ws` with `/ip4`
+        let addr = "/ip4/127.0.0.1/tcp/2222/ws".parse::<Multiaddr>().unwrap();
+        let url = extract_websocket_url(&addr).unwrap();
+        assert_eq!(url, "ws://127.0.0.1:2222/");
+
+        // Check `/ws` with `/ip6`
+        let addr = "/ip6/::1/tcp/2222/ws".parse::<Multiaddr>().unwrap();
+        let url = extract_websocket_url(&addr).unwrap();
+        assert_eq!(url, "ws://[::1]:2222/");
+
+        // Check `/ws` with `/ip4`
+        let addr = "/ip4/127.0.0.1/tcp/2222/ws".parse::<Multiaddr>().unwrap();
+        let url = extract_websocket_url(&addr).unwrap();
+        assert_eq!(url, "ws://127.0.0.1:2222/");
+
+        // Check that `/tls/wss` is invalid
+        let addr = "/ip4/127.0.0.1/tcp/2222/tls/wss"
+            .parse::<Multiaddr>()
+            .unwrap();
+        assert!(extract_websocket_url(&addr).is_none());
+
+        // Check non-ws address
+        let addr = "/ip4/127.0.0.1/tcp/2222".parse::<Multiaddr>().unwrap();
+        assert!(extract_websocket_url(&addr).is_none());
+    }
+}
diff --git a/transports/websocket/CHANGELOG.md b/transports/websocket/CHANGELOG.md
index df51e2c807d..cd079cfdd5a 100644
--- a/transports/websocket/CHANGELOG.md
+++ b/transports/websocket/CHANGELOG.md
@@ -4,6 +4,8 @@
   See [PR 4568](https://github.com/libp2p/rust-libp2p/pull/4568)
 - Allow wss connections on IP addresses.
   See [PR 5525](https://github.com/libp2p/rust-libp2p/pull/5525).
+- Add support for `/tls/ws` and keep `/wss` backward compatible.
+  See [PR 5523](https://github.com/libp2p/rust-libp2p/pull/5523).
 
 ## 0.43.2
 
diff --git a/transports/websocket/src/framed.rs b/transports/websocket/src/framed.rs
index 074271e672f..a547aea21ef 100644
--- a/transports/websocket/src/framed.rs
+++ b/transports/websocket/src/framed.rs
@@ -33,6 +33,7 @@ use soketto::{
     connection::{self, CloseReason},
     handshake,
 };
+use std::borrow::Cow;
 use std::net::IpAddr;
 use std::{collections::HashMap, ops::DerefMut, sync::Arc};
 use std::{fmt, io, mem, pin::Pin, task::Context, task::Poll};
@@ -51,10 +52,7 @@ pub struct WsConfig<T> {
     tls_config: tls::Config,
     max_redirects: u8,
     /// Websocket protocol of the inner listener.
-    ///
-    /// This is the suffix of the address provided in `listen_on`.
-    /// Can only be [`Protocol::Ws`] or [`Protocol::Wss`].
-    listener_protos: HashMap<ListenerId, Protocol<'static>>,
+    listener_protos: HashMap<ListenerId, WsListenProto<'static>>,
 }
 
 impl<T> WsConfig<T>
@@ -121,22 +119,19 @@ where
         id: ListenerId,
         addr: Multiaddr,
     ) -> Result<(), TransportError<Self::Error>> {
-        let mut inner_addr = addr.clone();
-        let proto = match inner_addr.pop() {
-            Some(p @ Protocol::Wss(_)) => {
-                if self.tls_config.server.is_some() {
-                    p
-                } else {
-                    tracing::debug!("/wss address but TLS server support is not configured");
-                    return Err(TransportError::MultiaddrNotSupported(addr));
-                }
-            }
-            Some(p @ Protocol::Ws(_)) => p,
-            _ => {
-                tracing::debug!(address=%addr, "Address is not a websocket multiaddr");
-                return Err(TransportError::MultiaddrNotSupported(addr));
-            }
-        };
+        let (inner_addr, proto) = parse_ws_listen_addr(&addr).ok_or_else(|| {
+            tracing::debug!(address=%addr, "Address is not a websocket multiaddr");
+            TransportError::MultiaddrNotSupported(addr.clone())
+        })?;
+
+        if proto.use_tls() && self.tls_config.server.is_none() {
+            tracing::debug!(
+                "{} address but TLS server support is not configured",
+                proto.prefix()
+            );
+            return Err(TransportError::MultiaddrNotSupported(addr));
+        }
+
         match self.transport.lock().listen_on(id, inner_addr) {
             Ok(()) => {
                 self.listener_protos.insert(id, proto);
@@ -175,11 +170,10 @@ where
                 mut listen_addr,
             } => {
                 // Append the ws / wss protocol back to the inner address.
-                let proto = self
-                    .listener_protos
+                self.listener_protos
                     .get(&listener_id)
-                    .expect("Protocol was inserted in Transport::listen_on.");
-                listen_addr.push(proto.clone());
+                    .expect("Protocol was inserted in Transport::listen_on.")
+                    .append_on_addr(&mut listen_addr);
                 tracing::debug!(address=%listen_addr, "Listening on address");
                 TransportEvent::NewAddress {
                     listener_id,
@@ -190,11 +184,10 @@ where
                 listener_id,
                 mut listen_addr,
             } => {
-                let proto = self
-                    .listener_protos
+                self.listener_protos
                     .get(&listener_id)
-                    .expect("Protocol was inserted in Transport::listen_on.");
-                listen_addr.push(proto.clone());
+                    .expect("Protocol was inserted in Transport::listen_on.")
+                    .append_on_addr(&mut listen_addr);
                 TransportEvent::AddressExpired {
                     listener_id,
                     listen_addr,
@@ -226,13 +219,9 @@ where
                     .listener_protos
                     .get(&listener_id)
                     .expect("Protocol was inserted in Transport::listen_on.");
-                let use_tls = match proto {
-                    Protocol::Wss(_) => true,
-                    Protocol::Ws(_) => false,
-                    _ => unreachable!("Map contains only ws and wss protocols."),
-                };
-                local_addr.push(proto.clone());
-                send_back_addr.push(proto.clone());
+                let use_tls = proto.use_tls();
+                proto.append_on_addr(&mut local_addr);
+                proto.append_on_addr(&mut send_back_addr);
                 let upgrade = self.map_upgrade(upgrade, send_back_addr.clone(), use_tls);
                 TransportEvent::Incoming {
                     listener_id,
@@ -446,6 +435,48 @@ where
     }
 }
 
+#[derive(Debug, PartialEq)]
+pub(crate) enum WsListenProto<'a> {
+    Ws(Cow<'a, str>),
+    Wss(Cow<'a, str>),
+    TlsWs(Cow<'a, str>),
+}
+
+impl<'a> WsListenProto<'a> {
+    pub(crate) fn append_on_addr(&self, addr: &mut Multiaddr) {
+        match self {
+            WsListenProto::Ws(path) => {
+                addr.push(Protocol::Ws(path.clone()));
+            }
+            // `/tls/ws` and `/wss` are equivalend, however we regenerate
+            // the one that user passed at `listen_on` for backward compatibility.
+            WsListenProto::Wss(path) => {
+                addr.push(Protocol::Wss(path.clone()));
+            }
+            WsListenProto::TlsWs(path) => {
+                addr.push(Protocol::Tls);
+                addr.push(Protocol::Ws(path.clone()));
+            }
+        }
+    }
+
+    pub(crate) fn use_tls(&self) -> bool {
+        match self {
+            WsListenProto::Ws(_) => false,
+            WsListenProto::Wss(_) => true,
+            WsListenProto::TlsWs(_) => true,
+        }
+    }
+
+    pub(crate) fn prefix(&self) -> &'static str {
+        match self {
+            WsListenProto::Ws(_) => "/ws",
+            WsListenProto::Wss(_) => "/wss",
+            WsListenProto::TlsWs(_) => "/tls/ws",
+        }
+    }
+}
+
 #[derive(Debug)]
 struct WsAddress {
     host_port: String,
@@ -499,7 +530,14 @@ fn parse_ws_dial_addr<T>(addr: Multiaddr) -> Result<WsAddress, Error<T>> {
     let (use_tls, path) = loop {
         match protocols.pop() {
             p @ Some(Protocol::P2p(_)) => p2p = p,
-            Some(Protocol::Ws(path)) => break (false, path.into_owned()),
+            Some(Protocol::Ws(path)) => match protocols.pop() {
+                Some(Protocol::Tls) => break (true, path.into_owned()),
+                Some(p) => {
+                    protocols.push(p);
+                    break (false, path.into_owned());
+                }
+                None => return Err(Error::InvalidMultiaddr(addr)),
+            },
             Some(Protocol::Wss(path)) => break (true, path.into_owned()),
             _ => return Err(Error::InvalidMultiaddr(addr)),
         }
@@ -521,6 +559,22 @@ fn parse_ws_dial_addr<T>(addr: Multiaddr) -> Result<WsAddress, Error<T>> {
     })
 }
 
+fn parse_ws_listen_addr(addr: &Multiaddr) -> Option<(Multiaddr, WsListenProto<'static>)> {
+    let mut inner_addr = addr.clone();
+
+    match inner_addr.pop()? {
+        Protocol::Wss(path) => Some((inner_addr, WsListenProto::Wss(path))),
+        Protocol::Ws(path) => match inner_addr.pop()? {
+            Protocol::Tls => Some((inner_addr, WsListenProto::TlsWs(path))),
+            p => {
+                inner_addr.push(p);
+                Some((inner_addr, WsListenProto::Ws(path)))
+            }
+        },
+        _ => None,
+    }
+}
+
 // Given a location URL, build a new websocket [`Multiaddr`].
 fn location_to_multiaddr<T>(location: &str) -> Result<Multiaddr, Error<T>> {
     match Url::parse(location) {
@@ -537,7 +591,8 @@ fn location_to_multiaddr<T>(location: &str) -> Result<Multiaddr, Error<T>> {
             }
             let s = url.scheme();
             if s.eq_ignore_ascii_case("https") | s.eq_ignore_ascii_case("wss") {
-                a.push(Protocol::Wss(url.path().into()))
+                a.push(Protocol::Tls);
+                a.push(Protocol::Ws(url.path().into()));
             } else if s.eq_ignore_ascii_case("http") | s.eq_ignore_ascii_case("ws") {
                 a.push(Protocol::Ws(url.path().into()))
             } else {
@@ -759,10 +814,95 @@ mod tests {
     use libp2p_identity::PeerId;
     use std::io;
 
+    #[test]
+    fn listen_addr() {
+        let tcp_addr = "/ip4/0.0.0.0/tcp/2222".parse::<Multiaddr>().unwrap();
+
+        // Check `/tls/ws`
+        let addr = tcp_addr
+            .clone()
+            .with(Protocol::Tls)
+            .with(Protocol::Ws("/".into()));
+        let (inner_addr, proto) = parse_ws_listen_addr(&addr).unwrap();
+        assert_eq!(&inner_addr, &tcp_addr);
+        assert_eq!(proto, WsListenProto::TlsWs("/".into()));
+
+        let mut listen_addr = tcp_addr.clone();
+        proto.append_on_addr(&mut listen_addr);
+        assert_eq!(listen_addr, addr);
+
+        // Check `/wss`
+        let addr = tcp_addr.clone().with(Protocol::Wss("/".into()));
+        let (inner_addr, proto) = parse_ws_listen_addr(&addr).unwrap();
+        assert_eq!(&inner_addr, &tcp_addr);
+        assert_eq!(proto, WsListenProto::Wss("/".into()));
+
+        let mut listen_addr = tcp_addr.clone();
+        proto.append_on_addr(&mut listen_addr);
+        assert_eq!(listen_addr, addr);
+
+        // Check `/ws`
+        let addr = tcp_addr.clone().with(Protocol::Ws("/".into()));
+        let (inner_addr, proto) = parse_ws_listen_addr(&addr).unwrap();
+        assert_eq!(&inner_addr, &tcp_addr);
+        assert_eq!(proto, WsListenProto::Ws("/".into()));
+
+        let mut listen_addr = tcp_addr.clone();
+        proto.append_on_addr(&mut listen_addr);
+        assert_eq!(listen_addr, addr);
+    }
+
     #[test]
     fn dial_addr() {
         let peer_id = PeerId::random();
 
+        // Check `/tls/ws`
+        let addr = "/dns4/example.com/tcp/2222/tls/ws"
+            .parse::<Multiaddr>()
+            .unwrap();
+        let info = parse_ws_dial_addr::<io::Error>(addr).unwrap();
+        assert_eq!(info.host_port, "example.com:2222");
+        assert_eq!(info.path, "/");
+        assert!(info.use_tls);
+        assert_eq!(info.server_name, "example.com".try_into().unwrap());
+        assert_eq!(info.tcp_addr, "/dns4/example.com/tcp/2222".parse().unwrap());
+
+        // Check `/tls/ws` with `/p2p`
+        let addr = format!("/dns4/example.com/tcp/2222/tls/ws/p2p/{peer_id}")
+            .parse()
+            .unwrap();
+        let info = parse_ws_dial_addr::<io::Error>(addr).unwrap();
+        assert_eq!(info.host_port, "example.com:2222");
+        assert_eq!(info.path, "/");
+        assert!(info.use_tls);
+        assert_eq!(info.server_name, "example.com".try_into().unwrap());
+        assert_eq!(
+            info.tcp_addr,
+            format!("/dns4/example.com/tcp/2222/p2p/{peer_id}")
+                .parse()
+                .unwrap()
+        );
+
+        // Check `/tls/ws` with `/ip4`
+        let addr = "/ip4/127.0.0.1/tcp/2222/tls/ws"
+            .parse::<Multiaddr>()
+            .unwrap();
+        let info = parse_ws_dial_addr::<io::Error>(addr).unwrap();
+        assert_eq!(info.host_port, "127.0.0.1:2222");
+        assert_eq!(info.path, "/");
+        assert!(info.use_tls);
+        assert_eq!(info.server_name, "127.0.0.1".try_into().unwrap());
+        assert_eq!(info.tcp_addr, "/ip4/127.0.0.1/tcp/2222".parse().unwrap());
+
+        // Check `/tls/ws` with `/ip6`
+        let addr = "/ip6/::1/tcp/2222/tls/ws".parse::<Multiaddr>().unwrap();
+        let info = parse_ws_dial_addr::<io::Error>(addr).unwrap();
+        assert_eq!(info.host_port, "[::1]:2222");
+        assert_eq!(info.path, "/");
+        assert!(info.use_tls);
+        assert_eq!(info.server_name, "::1".try_into().unwrap());
+        assert_eq!(info.tcp_addr, "/ip6/::1/tcp/2222".parse().unwrap());
+
         // Check `/wss`
         let addr = "/dns4/example.com/tcp/2222/wss"
             .parse::<Multiaddr>()
diff --git a/transports/websocket/src/lib.rs b/transports/websocket/src/lib.rs
index 40d6db44471..cbc923613dd 100644
--- a/transports/websocket/src/lib.rs
+++ b/transports/websocket/src/lib.rs
@@ -84,7 +84,7 @@ use std::{
 /// let cert = websocket::tls::Certificate::new(rcgen_cert.serialize_der().unwrap());
 /// transport.set_tls_config(websocket::tls::Config::new(priv_key, vec![cert]).unwrap());
 ///
-/// let id = transport.listen_on(ListenerId::next(), "/ip4/127.0.0.1/tcp/0/wss".parse().unwrap()).unwrap();
+/// let id = transport.listen_on(ListenerId::next(), "/ip4/127.0.0.1/tcp/0/tls/ws".parse().unwrap()).unwrap();
 ///
 /// let addr = future::poll_fn(|cx| Pin::new(&mut transport).poll(cx)).await.into_new_address().unwrap();
 /// println!("Listening on {addr}");