Introduction
SSH local and remote port forwarding create network tunnels that bind to specific ports. When the local port you want to bind to is already in use by another process, SSH fails with bind: Address already in use and channel_setup_fwd_listener_tcpip: cannot listen to port. This is common when multiple SSH tunnels are set up, when a previous tunnel did not clean up properly, or when the port conflicts with a local service.
Symptoms
ssh -L 8080:localhost:80 user@hostfails withbind: Address already in usechannel_setup_fwd_listener_tcpip: cannot listen to port: 8080Could not request local forwarding.- Port appears free in
netstatbut SSH still cannot bind (IPv4 vs IPv6 conflict) - SSH tunnel works on one port but fails on another
Common Causes
- Another SSH tunnel already using the same local port
- A local service (web server, proxy, database) listening on the target port
- Zombie SSH process from a previous tunnel still holding the port
- IPv6 wildcard binding conflicting with IPv4-specific binding
- Docker container mapping the same host port
Step-by-Step Fix
- 1.Identify what is using the port:
- 2.```bash
- 3.# Check all listeners on the port
- 4.sudo ss -tlnp | grep 8080
- 5.sudo netstat -tlnp | grep 8080
- 6.sudo lsof -i :8080
- 7.
` - 8.Kill the process holding the port if it is a stale SSH tunnel:
- 9.```bash
- 10.# Find and kill stale SSH processes
- 11.ps aux | grep "ssh.*-L.*8080"
- 12.kill <pid>
- 13.# Or use fuser to kill whatever is on the port
- 14.sudo fuser -k 8080/tcp
- 15.
` - 16.Use a different local port for the tunnel:
- 17.```bash
- 18.# Use a high-numbered unused port
- 19.ssh -L 18080:localhost:80 user@host
- 20.
` - 21.Bind to a specific address instead of all interfaces:
- 22.```bash
- 23.# Bind to localhost only (127.0.0.1)
- 24.ssh -L 127.0.0.1:8080:localhost:80 user@host
- 25.# Or bind to a specific interface
- 26.ssh -L 192.168.1.100:8080:localhost:80 user@host
- 27.
` - 28.Check for IPv4/IPv6 binding conflicts:
- 29.```bash
- 30.# A process may be listening on [::]:8080 which also captures 0.0.0.0:8080
- 31.sudo ss -tlnp | grep 8080
- 32.# If you see :::8080, the process is listening on all IPv6 and IPv4 addresses
- 33.# Solution: use a specific bind address as shown above
- 34.
` - 35.Use SSH dynamic port forwarding as an alternative:
- 36.```bash
- 37.# Instead of specific port forwarding, use a SOCKS proxy
- 38.ssh -D 1080 user@host
- 39.# Configure your application to use SOCKS5 proxy at localhost:1080
- 40.
`
Prevention
- Document all active SSH tunnels and their port mappings in a shared resource
- Use a port range convention for tunnels (e.g., 18000-18999) to avoid conflicts
- Implement SSH config with
LocalForwarddirectives for consistent port allocation - Use
ssh -O checkto verify if a multiplexed tunnel is active before creating a new one - Set up connection monitoring to detect and clean up orphaned tunnel processes