{
  "slug": "Tunneling-TCP-Connections-Over-SSH-Using-socat-and-nc-fb142a2d6034",
  "title": "Tunneling TCP Connections Over SSH Using socat and nc",
  "subtitle": "The Problem",
  "excerpt": "The Problem",
  "date": "2026-06-08",
  "tags": [],
  "readingTime": "2 min",
  "url": "https://medium.com/@mobinshaterian/tunneling-tcp-connections-over-ssh-using-socat-and-nc-fb142a2d6034",
  "hero": "https://cdn-images-1.medium.com/max/800/1*2FWBNb3ILSzGpRdRXeeWfw.png",
  "content": [
    {
      "type": "heading",
      "level": 2,
      "text": "Tunneling TCP Connections Over SSH Using socat and nc"
    },
    {
      "type": "heading",
      "level": 2,
      "text": "The Problem"
    },
    {
      "type": "paragraph",
      "html": "Sometimes you need to reach a service on a remote host that isn’t directly accessible from your machine, maybe it’s behind a firewall, on a private network segment, or only reachable through a jump host. In this post I’ll show a lightweight, dependency-free way to tunnel TCP traffic using tools you almost certainly already have: <code>ssh</code>, <code>nc</code> (netcat), and <code>socat</code>."
    },
    {
      "type": "image",
      "src": "https://cdn-images-1.medium.com/max/800/1*2FWBNb3ILSzGpRdRXeeWfw.png",
      "alt": "Tunneling TCP Connections Over SSH Using socat and nc",
      "caption": "",
      "width": 2752,
      "height": 1536
    },
    {
      "type": "heading",
      "level": 2,
      "text": "The Setup"
    },
    {
      "type": "paragraph",
      "html": "We have three actors:"
    },
    {
      "type": "list",
      "ordered": false,
      "items": [
        "<strong>Local machine</strong> — your workstation",
        "<strong>Jump host</strong> — an SSH-accessible server that can reach the target",
        "<strong>Target service</strong> — a TCP service (e.g. a database, API, or metrics endpoint) running on a host only reachable from the jump host"
      ]
    },
    {
      "type": "paragraph",
      "html": "The goal: make the target service available on a local port as if it were running on your own machine."
    },
    {
      "type": "heading",
      "level": 2,
      "text": "The Command"
    },
    {
      "type": "code",
      "lang": "text",
      "code": "socat TCP-LISTEN:8990,fork,reuseaddr EXEC:\"ssh user@jump-host nc target-host 9000\""
    },
    {
      "type": "paragraph",
      "html": "Let’s break it down:"
    },
    {
      "type": "list",
      "ordered": false,
      "items": [
        "<code><strong>socat TCP-LISTEN:8990,fork,reuseaddr</strong></code> — listens on local port <code>8990</code>. <code>fork</code> handles multiple connections concurrently; <code>reuseaddr</code> allows quick restarts without \"address already in use\" errors.",
        "<code><strong>EXEC:\"...\"</strong></code> — for each incoming connection, socat spawns the given command and pipes the TCP stream through its stdin/stdout.",
        "<code><strong>ssh user@jump-host</strong></code> — opens an SSH session to the jump host.",
        "<code><strong>nc target-host 9000</strong></code> — on the jump host, netcat connects to the target service and streams raw TCP."
      ]
    },
    {
      "type": "paragraph",
      "html": "The result: <code>localhost:8990</code> → SSH → jump host → netcat → target service. A full TCP tunnel with no extra software on any host."
    },
    {
      "type": "heading",
      "level": 2,
      "text": "SH Config for Cleaner Usage"
    },
    {
      "type": "paragraph",
      "html": "Rather than hardcoding the jump host address every time, define it in <code>~/.ssh/config</code>:"
    },
    {
      "type": "code",
      "lang": "text",
      "code": "Host jump-tunnel    HostName <jump-host-ip>    User youruser    PasswordAuthentication yes\nPubkeyAuthentication no    LocalForward 8990 <target-host>:9000"
    },
    {
      "type": "paragraph",
      "html": "Now the socat command becomes:"
    },
    {
      "type": "code",
      "lang": "text",
      "code": "socat TCP-LISTEN:8990,fork,reuseaddr EXEC:\"ssh jump-tunnel nc <target-host> 9000\""
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Alternative: Pure SSH Port Forwarding"
    },
    {
      "type": "paragraph",
      "html": "If you don’t need <code>fork</code> (i.e. only one consumer at a time), you can skip socat entirely and use SSH's built-in <code>LocalForward</code>:"
    },
    {
      "type": "code",
      "lang": "bash",
      "code": "ssh -N -L 8990:<target-host>:9000 user@jump-host"
    },
    {
      "type": "paragraph",
      "html": "Or with the config entry above, simply:"
    },
    {
      "type": "code",
      "lang": "bash",
      "code": "ssh -N jump-tunnel"
    },
    {
      "type": "paragraph",
      "html": "<code>-N</code> tells SSH not to execute a remote command — just hold the tunnel open."
    },
    {
      "type": "heading",
      "level": 2,
      "text": "When to Use socat vs SSH -L"
    },
    {
      "type": "image",
      "src": "https://cdn-images-1.medium.com/max/800/1*wQhn0cCkjmHNR2CvpGvF2Q.png",
      "alt": "Tunneling TCP Connections Over SSH Using socat and nc",
      "caption": "",
      "width": 732,
      "height": 277
    },
    {
      "type": "paragraph",
      "html": "For most use cases, <code>ssh -L</code> is simpler. The <code>socat</code> approach shines when you need to compose it into a larger pipeline or script."
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Conclusion"
    },
    {
      "type": "paragraph",
      "html": "With just <code>ssh</code>, <code>nc</code>, and <code>socat</code> you can punch through network boundaries and expose remote TCP services locally — no VPN, no extra firewall rules, no installed agents. It's a handy trick to have in your toolkit when working across segmented networks."
    }
  ]
}
