Summary
ssh -L <local-socket>:<remote-socket> fails against ssh2incus: the channel is refused with
unknown channel type. TCP forwarding into the instance works, so this looks like a missing
channel handler rather than a design decision.
This is the local half of #20, which reports the same gap for ssh -R. The two are separate
code paths — a remote forward is a global request, a local one a channel type — and they fail
differently: #20 is refused at request time and warns, this one is accepted at request time and
only fails when traffic flows, which makes it much harder to attribute.
The practical consequence is that the instance's docker socket cannot be forwarded to the
client, which is the standard way to make DOCKER_HOST=ssh:// fast.
Reproduction
Server: ssh2incus 0.9 (70c60c0), started as ssh2incus -m -W --web-listen 0.0.0.0:8444 --banner.
Host: Debian 13, x86_64, incus 6.0.5. Instance: unprivileged Debian 13 container running
docker 26.1.5, whose daemon listens on /var/run/docker.sock inside the instance.
Client: OpenSSH_10.3p1 on macOS 26.6.2.
$ ssh -v -N -L /tmp/d.sock:/var/run/docker.sock -p 2222 instance@sshhost
$ curl --unix-socket /tmp/d.sock http://localhost/version
The client reports:
debug1: Connection to port -2 forwarding to /var/run/docker.sock port -2 requested.
debug1: channel 2: new direct-streamlocal@openssh.com [direct-streamlocal@openssh.com]
channel 2: open failed: unknown channel type: unsupported channel type
Same result for every remote path tried, including one that certainly exists and is
readable by the authenticated user:
/var/run/docker.sock — exists inside the instance
/proc/<instance-init-pid>/root/run/docker.sock — the same socket as seen from the host
/var/lib/incus/unix.socket — a host socket
So the channel is refused before any path is resolved, and no remote path works around it.
What does work, for contrast
TCP forwarding into the instance is fine, including the empty-host form from the docs:
$ ssh -N -L 127.0.0.1:18098::80 -p 2222 instance@sshhost
$ curl http://127.0.0.1:18098/ # answered by the instance's web server
That also revealed where the forward is dialled from. Forwarding to a port bound to
127.0.0.1 inside the instance fails:
channel 3: open failed: connect failed: failed to connect to tcp://10.0.0.42:34567
i.e. ssh2incus connects to the instance's address from the host, not from within the
instance's network namespace. That is worth documenting on its own — it means a service
bound to the instance's loopback cannot be reached through a forward, and a service that
can be reached is also reachable by every other instance on the bridge.
Why it matters
DOCKER_HOST=ssh://user@host:2222 makes the docker CLI spawn one ssh per command
(ssh -l user -p 2222 -o ConnectTimeout=30 -T -- host docker system dial-stdio). Measured
against this server that is ~0.29 s per docker command; a deployment issues dozens.
The usual fix is to forward the remote docker socket once and point DOCKER_HOST at the
local socket — which is exactly what this channel type is for. With ssh2incus that is not
possible today, and the failure is invisible until traffic flows: ssh -L to a unix socket
succeeds on its own, so tooling reports "the daemon did not answer", which sends people
looking at socket permissions and docker group membership instead.
Suggested implementation
direct-tcpip is already handled, so a channel handler keyed by channel type exists;
direct-streamlocal@openssh.com would be another entry in it, carrying a socket path instead
of a host and port.
What it dials could reuse what agent forwarding already does — the README notes that ssh2incus
"automatically creates a proxy socket device in the instance and removes it when the connection
closes". The same mechanism inverted (a proxy device with bind=host,
connect=unix:<requested path>, removed when the channel closes) would let the channel be
dialled on the host and terminate on the instance's socket.
Note that this cannot follow direct-tcpip's route of connecting to the instance's address
from the host: a unix socket has no address, and the instance's own filesystem is the only
place the path means anything. So unlike the TCP case, this one has to reach into the
instance — which is also why it would be more useful than the TCP one, not less.
Summary
ssh -L <local-socket>:<remote-socket>fails against ssh2incus: the channel is refused withunknown channel type. TCP forwarding into the instance works, so this looks like a missingchannel handler rather than a design decision.
This is the local half of #20, which reports the same gap for
ssh -R. The two are separatecode paths — a remote forward is a global request, a local one a channel type — and they fail
differently: #20 is refused at request time and warns, this one is accepted at request time and
only fails when traffic flows, which makes it much harder to attribute.
The practical consequence is that the instance's docker socket cannot be forwarded to the
client, which is the standard way to make
DOCKER_HOST=ssh://fast.Reproduction
Server: ssh2incus 0.9 (70c60c0), started as
ssh2incus -m -W --web-listen 0.0.0.0:8444 --banner.Host: Debian 13, x86_64, incus 6.0.5. Instance: unprivileged Debian 13 container running
docker 26.1.5, whose daemon listens on
/var/run/docker.sockinside the instance.Client: OpenSSH_10.3p1 on macOS 26.6.2.
The client reports:
Same result for every remote path tried, including one that certainly exists and is
readable by the authenticated user:
/var/run/docker.sock— exists inside the instance/proc/<instance-init-pid>/root/run/docker.sock— the same socket as seen from the host/var/lib/incus/unix.socket— a host socketSo the channel is refused before any path is resolved, and no remote path works around it.
What does work, for contrast
TCP forwarding into the instance is fine, including the empty-host form from the docs:
That also revealed where the forward is dialled from. Forwarding to a port bound to
127.0.0.1inside the instance fails:i.e. ssh2incus connects to the instance's address from the host, not from within the
instance's network namespace. That is worth documenting on its own — it means a service
bound to the instance's loopback cannot be reached through a forward, and a service that
can be reached is also reachable by every other instance on the bridge.
Why it matters
DOCKER_HOST=ssh://user@host:2222makes the docker CLI spawn onesshper command(
ssh -l user -p 2222 -o ConnectTimeout=30 -T -- host docker system dial-stdio). Measuredagainst this server that is ~0.29 s per docker command; a deployment issues dozens.
The usual fix is to forward the remote docker socket once and point
DOCKER_HOSTat thelocal socket — which is exactly what this channel type is for. With ssh2incus that is not
possible today, and the failure is invisible until traffic flows:
ssh -Lto a unix socketsucceeds on its own, so tooling reports "the daemon did not answer", which sends people
looking at socket permissions and
dockergroup membership instead.Suggested implementation
direct-tcpipis already handled, so a channel handler keyed by channel type exists;direct-streamlocal@openssh.comwould be another entry in it, carrying a socket path insteadof a host and port.
What it dials could reuse what agent forwarding already does — the README notes that ssh2incus
"automatically creates a proxy socket device in the instance and removes it when the connection
closes". The same mechanism inverted (a proxy device with
bind=host,connect=unix:<requested path>, removed when the channel closes) would let the channel bedialled on the host and terminate on the instance's socket.
Note that this cannot follow
direct-tcpip's route of connecting to the instance's addressfrom the host: a unix socket has no address, and the instance's own filesystem is the only
place the path means anything. So unlike the TCP case, this one has to reach into the
instance — which is also why it would be more useful than the TCP one, not less.