SSH tunnel: "bind: Address already in use" and "remote port forwarding failed"

Something already listens on the port your tunnel wants. With -L that port is on your machine, and with -R it is on the server. Often it is an old SSH session still holding the tunnel. Find it with lsof -nP -iTCP:8080 -sTCP:LISTEN, then stop that process or use another port. Add -o ExitOnForwardFailure=yes, because without it SSH logs in anyway and leaves you with no tunnel.

Updated . All messages and commands reproduced with OpenSSH 10.3 on macOS against an OpenSSH 10.0 server on Linux.

Local forwarding (-L): the port is busy on your machine

With ssh -L 8080:localhost:80 user@server you may see:

bind [127.0.0.1]:8080: Address already in use
channel_setup_fwd_listener_tcpip: cannot listen to port: 8080
Could not request local forwarding.

Find out who holds the port. This works on macOS and Linux:

lsof -nP -iTCP:8080 -sTCP:LISTEN
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
ssh     4242 user    4u  IPv4 ...         0t0  TCP 127.0.0.1:8080 (LISTEN)

On Windows use netstat -ano | findstr :8080.

  • ssh: an earlier tunnel is still running, often started with -f and forgotten. Run kill 4242 with the PID shown, or see the section on shared connections below.
  • A dev server, Docker or a database: pick another local port. Only the left number has to change: ssh -L 18080:localhost:80 user@server.
  • On macOS, ControlCe on port 5000 or 7000 is the AirPlay Receiver. Pick another port, or turn off AirPlay Receiver in System Settings.

Not sure which flag you need? SSH tunnels: local, remote and dynamic forwarding compares the three.

Only the first line appeared

If you see just bind [127.0.0.1]:8080: Address already in use and the session opens, then SSH bound the IPv6 side but not the IPv4 one. -L 8080:... tries both. The port is half working: clients using 127.0.0.1 reach the other program, not your tunnel. Name the address, and SSH will report the failure properly:

ssh -o ExitOnForwardFailure=yes -L 127.0.0.1:8080:localhost:80 user@server

Remote forwarding (-R): the port is busy on the server

With ssh -R 8080:localhost:80 user@server:

Warning: remote port forwarding failed for listen port 8080

The session still opens, and the warning is easy to miss. The cause is on the server, and its log holds the same bind line. See who holds the port there:

ssh user@server "ss -ltnp | grep ':8080'"

The process name appears only for your own processes, unless you run it with sudo. Sometimes the owner is sshd or sshd-session: the previous connection from your laptop or your tunnel script died without the server noticing, and it still holds the port. Find and end that session on the server:

sudo ss -ltnp | grep ':8080'
sudo kill PID

To make the server clear these on its own, add this to sshd_config and reload the service. It drops a client that has been silent for about 90 seconds:

ClientAliveInterval 30
ClientAliveCountMax 3

A busy port is not the only reason for this warning. If the port is free, the server may forbid the forward, which is covered in administratively prohibited.

If you do not need a fixed port, let the server pick one. The client prints the port it got:

ssh -R 0:localhost:80 user@server
Allocated port 38587 for remote forward to localhost:80

Make a failed tunnel fail loudly

By default SSH logs in even when the forward failed, so scripts carry on and fail later for a confusing reason. With this option it exits with status 255 instead:

ssh -o ExitOnForwardFailure=yes -N -L 127.0.0.1:8080:localhost:80 user@server

For -R, the message then reads:

Error: remote port forwarding failed for listen port 8080

Use this option in everything automated, including autossh. That way a retry loop can see the failure and try again when the port is free.

Shared connections hold ports too

With ControlMaster, the forward belongs to the background master process, so closing your terminal does not free the port. You can talk to the master, using the same host and config:

ssh -O check user@server
ssh -O cancel -L 127.0.0.1:8080:localhost:80 user@server
ssh -O exit user@server

See SSH multiplexing for more.

Questions

How do I find which process is using a port?

On macOS and Linux run lsof -nP -iTCP:8080 -sTCP:LISTEN, or ss -ltnp on Linux. On Windows use netstat -ano | findstr :8080. The output shows the program and its PID.

Why does SSH log in even though the port forwarding failed?

By default a failed forward is only a warning. Add -o ExitOnForwardFailure=yes and ssh exits with status 255 instead, so scripts notice.

How do I close an SSH tunnel running in the background?

Find its ssh process with lsof and run kill with its PID. If it belongs to a ControlMaster connection, use ssh -O cancel or ssh -O exit with the same host.

Sources

More in Error messages

All guides