Your device sits behind a carrier NAT, it can open outbound connections, and it runs an SSH server nobody can reach. The request that arrives is usually phrased as "give it a VPN", which is one answer among several. Before you accept it, decide whether you need one administrative TCP port or a routed path to an entire subnet, because those are different projects with different exposure.

A reverse tunnel answers the narrow version well. The device dials your hub, asks the hub to listen on a loopback port, and forwards that port back to its own SSH service. Administrators then reach the listener through the hub, not through the public internet.

Three conditions that settle the choice

Write these down before any configuration. They are the boundary between a bounded management entry and a network extension, and mixing them up is how a five-line tunnel turns into an unowned flat network.

ConditionReverse tunnel fitsSomething else is needed
How many services must be reachable?One, or a small fixed set on loopbackAnything that needs many ports or dynamic discovery
Who connects, and how are they authenticated?Named administrators with their own hub accountsThird parties, or anything needing per-flow audit you do not control
Does the device's own LAN need to be reachable?NoYes, so a routed VPN or a managed platform is the honest answer

The third row is the one people argue about. A tunnel to a device's SSH port does not put its neighbours on your network, and it should not be made to look like it does through a chain of further forwards. If the requirement is really the LAN, say so and design that instead.

Hub side: a bounded relay account

Give each device its own relay account with public-key authentication and no interactive session. A Match block scoped to that account keeps the grant small, and the private key stays on the device where it belongs.

Match User relay-dev-7
    AuthenticationMethods publickey
    PubkeyAuthentication yes
    AllowTcpForwarding remote
    PermitListen 127.0.0.1:21022
    GatewayPorts no
    MaxSessions 0
    PermitTTY no
    X11Forwarding no
    AllowAgentForwarding no

MaxSessions 0 is the interesting line. It denies shell and subsystem sessions while still permitting the forwarding the device needs, so a stolen key cannot be turned into an interactive login. PermitListen pins the hub-side listener address and port, and GatewayPorts no keeps it on loopback rather than opening it to the network the hub sits on.

Test the effective configuration before reloading, and keep a second session open while you do it. A Match block that accidentally applies to everyone, or a typo in an option name, is the classic way to lock yourself out of the box you are hardening. Dump the running configuration and read the options back rather than trusting the file you just wrote. Note that option support differs between builds, so an accepted line in a newer server may simply be ignored by an older one.

Device side: forwarding that fails loudly

The device opens the connection and requests the listener. The two addresses in the -R argument mean different things, and reading them left to right is the fastest way to remember which is which.

ssh -NT \
    -o ExitOnForwardFailure=yes \
    -o ServerAliveInterval=30 \
    -o ServerAliveCountMax=3 \
    -R 127.0.0.1:21022:127.0.0.1:22 \
    [email protected]

The left-hand pair is where the hub should listen. The right-hand pair is what the device connects to when a request arrives, resolved from the device's own side. Swapping them produces a tunnel that comes up cleanly and forwards to the wrong place, which is worse than a failure.

ExitOnForwardFailure=yes makes the command exit when the requested listener cannot be established, so a restart loop tells you the truth instead of leaving a half-working session. It says nothing about whether the local SSH service is healthy later, so pair it with a process manager and an occasional real connection test.

Never replace the loopback listen address with a wildcard to avoid an extra hop. That single change is the difference between a management path and a publicly reachable port, and it usually happens during debugging and then stays. If a colleague cannot reach the port through the hub, fix the jump configuration instead of widening the bind. Loopback is the whole security argument for this design.

Administrator access through the hub

An administrator with an ordinary account on the hub can jump through it to the forwarded port. The relay account is not used for this, which is the point of keeping them separate.

ssh -J [email protected] -o HostKeyAlias=device-7 \
    -p 21022 [email protected]

Verify the device's host key under the alias you chose, because that key identifies the device at the end of the chain and the tunnel is only transport. The end-to-end login still authenticates both ends; a tunnel that terminates on a shared hub does not make it a trusted path on its own.

Own the lifecycle

Run the device command under a process manager with exactly one active instance and a restart policy you chose deliberately. The keepalive options detect a dead peer; they do not resurrect a process that exited.

Then write the retirement procedure. Each device gets its own listener and its own key, so decommissioning means revoking that account and removing that key, not rotating a fleet-wide secret that every other device shares.

Finally, remember what loopback buys you. It limits network exposure and does nothing about other local users on a shared hub, so the hub's own access policy still has to be right. The sshd_config reference documents each of the options used above, and it is worth reading the forwarding sections in full before you widen any of them.

ບົດຄວາມທີ່ກ່ຽວຂ້ອງ