codingstairs
NotesEDULifeContact
⌕Search⌘K
koen

Navigation

  • Intro
  • Blog
  • Life

Get in touch

Send without signing in. Add your email if you'd like a reply.

  • Leave a message anonymously →
  • ✉ warragon112@gmail.com
  • KakaoTalk Open Chat ↗

© 2026 codingstairs

  • Notes
  • EDU
  • Search
  • Life
  • Contact
  • Legal
  • RSS
  • GitHub
Notes›infra

Loopback Binding and SSH Tunnel

Published 2026-04-28· Updated 2026-05-18·0 views

Loopback Binding and SSH Tunnel

Some ports need to be reachable from the internet, others should only be used inside the same host. The simplest tool to separate the two is the bind address. When a path that only outsiders can take is needed, an SSH tunnel is the lightweight answer. This piece covers the difference between 0.0.0.0 and 127.0.0.1, Docker port mapping, SSH port forwarding (-L, -R, -D), VPN tool comparisons, and an operational model for minimizing external exposure.

1. About Bind Addresses

Address Meaning
0.0.0.0 Bind to every interface (externally reachable).
127.0.0.1 Loopback. Only reachable from the same host.
:: / ::1 IPv6 every interface / loopback.
<specific IP> Only that interface.

The bind address of a listening socket determines external reachability. If the firewall is the first line of defense, the bind address is the decision before that.

2. Docker Port Mapping

Port notation in Compose and docker run:

ports:
  - "8080:8080"             # exposed on the host's 0.0.0.0:8080
  - "127.0.0.1:8080:8080"   # exposed only on host loopback
  - "8080"                  # arbitrary host port → container 8080

In production, a common model is to open only services that need external exposure (web, proxy) on 0.0.0.0, and keep DBs, caches, and admin UIs on loopback. When external access is needed, an SSH tunnel temporarily passes through.

3. SSH Port Forwarding

The three forwarding options of ssh:

Option Meaning
-L Local port → some port on a remote host (local forwarding).
-R Remote port → some port on the local machine (remote forwarding).
-D A SOCKS proxy on the local machine (dynamic forwarding).

-L — production DB access:

ssh -L 5432:127.0.0.1:5432 user@server

Connecting to local 5432 on the PC routes through the SSH tunnel to the server's loopback 5432 (the DB). The server's DB port doesn't need to be open to the internet.

-R — temporarily exposing an internal dev server:

ssh -R 8080:127.0.0.1:3000 user@server

Connections coming into the server's 8080 land on the local PC's 3000. The server may need GatewayPorts yes.

-D — SOCKS proxy:

ssh -D 1080 user@server

A SOCKS5 proxy on the local PC's 1080. Browsers and tools route through the server.

4. VPN Tools

Tool Note
WireGuard Started in 2016. Linux kernel integration (2020). A simple, fast, modern VPN.
Tailscale 2019. A mesh VPN built on WireGuard. Direct device-to-device connections plus coordination.
OpenVPN 2001. An older SSL / TLS-based VPN.

If an SSH tunnel is light enough for one or two ports of temporary exposure, a VPN is the place to stably stitch together many ports across many hosts. Tailscale comes up frequently for NAT traversal and zero-config setup.

5. Minimizing External Exposure

When several layers of defense overlap, the innermost decision is the bind address.

  • Cloud security groups / Network ACLs.
  • Host-level ufw (Linux) or Windows Defender Firewall.
  • Container network isolation.

Even if a cloud security group is open to 0.0.0.0/0, a service bound to 127.0.0.1 is unreachable from outside (and vice versa).

6. bastion / jump host

Funnel access to multiple internal hosts through a single bastion:

# ~/.ssh/config
Host db1
    Hostname 10.0.1.5
    ProxyJump bastion

Host bastion
    Hostname bastion.example.com

ssh db1 alone goes through bastion to db1. SSH's ProxyJump (-J) is standard from OpenSSH 7.3+.

7. A Typical Production Server Layout

  • 80 / 443 — externally exposed (Caddy or nginx).
  • 22 — externally exposed (SSH). Prefer key-based auth, fail2ban, and a non-default port.
  • Every other port — 127.0.0.1 binding plus access only via SSH tunnel.
services:
  app:
    ports:
      - "127.0.0.1:8080:8080"
  db:
    ports:
      - "127.0.0.1:5432:5432"
  caddy:
    ports:
      - "80:80"
      - "443:443"

Only the Caddy container reaches outside; the rest are only accessible through host loopback. Call them through an SSH tunnel or directly inside the host shell.

8. Reaching the Production DB from a Dev PC

ssh -L 15432:127.0.0.1:5432 user@server
psql -h 127.0.0.1 -p 15432 -U app appdb

Using local 15432 instead of 5432 avoids collisions even if a local DB is running on 5432.

9. Common Pitfalls

Unintended exposure on 0.0.0.0 — Compose's "PORT:PORT" notation defaults to 0.0.0.0. Copy-paste without review accumulates external exposure.

Permission limits of -R — the default SSH config binds remote forwards to loopback. Opening external access requires GatewayPorts yes on the server, which carries its own security review.

Tunnels going silent — when the network drops, SSH sessions can die quietly. ServerAliveInterval or autossh help.

Confusing responsibility between firewall, security group, and bind address — hard to track which layer is blocking. Inspect one layer at a time when changing things.

User-defined Docker networks vs host exposure — container-to-container communication is on the user network; external exposure is on explicit ports. Don't mix the two.

Closing thoughts

Loopback binding plus SSH tunnels together compress the production server's security surface to three ports — 80 / 443 / 22. A single carelessly written "5432:5432" line in compose can be the start of a production incident. Worth defaulting every new container to a 127.0.0.1 prefix.

Next

  • single-server-philosophy
  • local-https-mkcert

Refer to the OpenSSH manual, the Docker networking overview, Tailscale, WireGuard, the Mozilla SSH guidelines, and ProxyJump (OpenSSH).

More in infra

All in this category →
  • Cloud Emulator Stack — Designing a 4th Environment
  • Local HTTPS — mkcert and a Self CA
  • The Place of Single-server Operations
  • Caddy and nginx — A Comparison
  • Docker Compose Patterns
  • Docker Basics