SSH port forwarding fails because the client or server cannot bind a listening socket on the requested port — usually because the port is already in use, is privileged, or a stale listener still holds it.
When you run SSH with `-L` (local forwarding) or `-R` (remote forwarding), SSH must open a listening socket on the forwarding port. `channel_setup_fwd_listener: cannot listen to port: <port>` means the `bind()`/`listen()` call for that socket was refused. For `-L`, the listener is created by your local SSH client; for `-R`, it is created by the remote `sshd`. SSH then emits a follow-up message such as `Could not request local forwarding` or `Warning: remote port forwarding failed`. This is a local/remote socket-binding problem, not a network connectivity or authentication problem — the SSH session itself usually connects fine. The kernel refuses the bind for one of a small set of reasons: another process already holds the address/port, the port is privileged and you are unprivileged, or a previous forwarder is still alive and holding the socket. Identifying which process owns the port is almost always the fastest path to a fix.
Find out exactly what is holding the forwarding port before changing anything. Replace 8080 with your actual port:
# Linux: show the listening socket and the owning PID/program
ss -ltnp 'sport = :8080'
# macOS / portable
lsof -nP -iTCP:8080 -sTCP:LISTENFor a remote (-R) tunnel, run the same command on the SSH server, since sshd creates that listener there.
If nothing is listed, the port is actually free and the cause is privilege or configuration (see the steps below). If a process is listed, note its PID and command — the next step decides what to do with it.
If the listener is a leftover SSH forwarder you no longer need, terminate only that process using the PID you found in step 1. Send a graceful SIGTERM first:
# Replace 12345 with the exact PID from `ss`/`lsof`
kill 12345
# Confirm it is gone; only then escalate if it is truly stuck
ss -ltnp 'sport = :8080'
kill -9 12345 # last resort, only for that one PIDDo not run pkill ssh or pkill -9 ssh: that kills every SSH process you own, tearing down unrelated live sessions and tunnels. If the listener belongs to a real application (not SSH), do not kill it — forward to a different local port instead (step 5).
If the tunnel was started with SSH connection multiplexing, close it cleanly instead of killing it:
ssh -O exit -S <control-socket-path> user@remote # or check: ssh -O check user@remotePorts below 1024 require elevated privileges to bind. The safe fix is to not bind a privileged port: pick a high local port (for example 8080 instead of 80) and have your local client connect to that high port.
# Avoid: requesting a privileged local listen port as a normal user
# ssh -L 80:localhost:80 [email protected] # bind on :80 will be refused
# Prefer: listen on a high local port, forward to the privileged remote port
ssh -L 8080:localhost:80 [email protected]
# then point your local client at http://localhost:8080Do not run the whole SSH client under sudo just to open a privileged listener — that runs your entire SSH session, agent access, and key handling as root, which is unnecessary and risky. If a privileged local port is genuinely required (for example software that hard-codes port 443), grant that capability narrowly rather than running SSH as root — e.g. start SSH via authbind --deep ssh ..., or use a local firewall/pf/iptables redirect from the privileged port to a high port. Reserve these for cases where a high port truly cannot be used.
A repeated LocalForward/RemoteForward for the same port makes SSH try to open the listener twice. Inspect the effective configuration for your host:
ssh -G [email protected] | grep -i forwardThis expands all matching Host/Match blocks and shows every forward that will be applied. If you see the same listen port more than once, edit ~/.ssh/config and remove the duplicate:
Host myhost
HostName remote.com
User user
LocalForward 8080 localhost:80
# delete any second line that re-declares LocalForward 8080 ...Also remember that command-line -L/-R flags add to (do not replace) config-file forwards, so a port declared in both places will collide.
If the chosen port is owned by an application you must not disturb, simply pick an unused high port. Anything in the ephemeral range is fine, but verify it is free first:
ss -ltn 'sport = :54321' # should print no listening socket
ssh -L 54321:destination.internal:8080 [email protected]Then connect your local client to localhost:54321. This sidesteps the conflict without killing other processes and is a good, non-destructive workaround while you investigate the root cause.
With -R, the listener lives on the SSH server. By default sshd binds remote forwards to loopback (127.0.0.1) only. If you request a public bind address without permission, the listen fails.
To expose a remote-forwarded port beyond loopback, set GatewayPorts appropriately in the server's /etc/ssh/sshd_config:
# Lets the client choose the bind address (e.g. ssh -R '0.0.0.0:8080:...')
GatewayPorts clientspecifiedThen reload sshd:
sudo systemctl reload ssh # or: sudo systemctl reload sshdPrefer clientspecified over a blanket yes so each tunnel explicitly opts into a public bind address. Confirm the remote port is actually free on the server with ss -ltnp there.
If ss/lsof shows the port free but the bind still fails, a different address family may already hold it, or a policy may block it. Force a single family to isolate the problem:
ssh -4 -L 8080:localhost:80 [email protected] # force IPv4
ssh -6 -L 8080:localhost:80 [email protected] # force IPv6On SELinux systems, a non-standard listen port may need a label; check before changing anything:
sudo semanage port -l | grep -w 8080For host firewalls, confirm the port is permitted (review rules; do not blanket-disable the firewall):
sudo iptables -S | grep 8080
sudo firewall-cmd --list-ports # firewalld-based systemsNote that for a purely local -L listener bound to 127.0.0.1, host firewalls usually are not involved; this step matters mainly for remote (-R) listeners or forwards bound to a public address.
SSH opens local-forward listeners with SO_REUSEADDR, so a clean TIME_WAIT state from a previous connection on the *connect* side does not normally block a new listener — if a bind is being refused, an actual live listener is almost always still holding the port. Tunables like net.ipv4.tcp_tw_reuse apply to the connecting side and are not a fix for a failed listen(); avoid changing them for this error.
For background tunnels (ssh -f -N -L ...) and multiplexed connections (ControlMaster/ControlPersist), the listener can outlive your interactive shell. Use ssh -O check/ssh -O exit against the control socket to manage them cleanly instead of killing processes.
For -R forwards, you can let sshd pick a free port by requesting port 0 (ssh -R 0:localhost:80 ...); recent OpenSSH prints the allocated port. The remote listener still binds to loopback unless GatewayPorts permits otherwise. If diagnostics are unclear, add -v to the SSH command — the verbose log shows the exact forward request and which side rejected the bind.
sign_and_send_pubkey: no mutual signature supported
How to fix "sign_and_send_pubkey: no mutual signature supported" in SSH
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
How to fix SSH man-in-the-middle attack warning in SSH
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ WARNING: UNPROTECTED PRIVATE KEY FILE! @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
How to fix "WARNING: UNPROTECTED PRIVATE KEY FILE!" in SSH
sign_and_send_pubkey: signing failed for RSA from agent: agent refused operation
How to fix "sign_and_send_pubkey: signing failed for RSA from agent: agent refused operation" in SSH
Bad owner or permissions on /home/user/.ssh/known_hosts
How to fix "Bad owner or permissions on known_hosts" in SSH