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-fand forgotten. Runkill 4242with 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,
ControlCeon 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.