SSH tunnels

Persistent SSH tunnel between two VPS with autossh: a receiver service shows up on the initiator's localhost without being exposed.

  • Category: Servers and infrastructure
  • Status: Available
  • Requirements: Two VPS (from the same company for the automatic flow); Being a company member with access to both VPS; Systems with systemd and a compatible OpenSSH (check with ver_funciones_vps, feature “SSH tunnels between VPS”)
  • MCP tools (Read only): ver_estado_receptor_tunel_vps, listar_claves_receptor_tunel_vps, ver_estado_iniciador_tunel_vps, listar_tuneles_vps, ver_tunel_vps
  • MCP tools (Write or modify): configurar_receptor_tunel_vps, agregar_clave_receptor_tunel_vps, configurar_iniciador_tunel_vps, crear_tunel_vps, controlar_tunel_vps
  • MCP tools (Destructive): configurar_tunel_entre_vps, eliminar_receptor_tunel_vps, eliminar_clave_receptor_tunel_vps, eliminar_iniciador_tunel_vps, eliminar_tunel_vps

Markdown

What can you do with it?

  • Use a service listening on one VPS (a database, an internal API) from another VPS as if it were on localhost, without publishing it on the internet.
  • Set everything up in a single call (configurar_tunel_entre_vps), or step by step.
  • Authorize or revoke the keys allowed into the receiver.
  • Start, stop or restart each tunnel, and see its live status.

When should you use it?

  • An app on one VPS needs the database living on another.
  • Splitting an internal API from its frontend without opening public ports for the service.

How does it work in ArduMaker?

It is a local forward (ssh -L) running on the initiator: whatever listens on the receiver's puerto_remoto becomes available at localhost:puerto_local inside the initiator. It does not work the other way round and exposes nothing to the internet.

The steps

  1. Receiver (configurar_receptor_tunel_vps): creates a user dedicated to the tunnel, configures sshd to accept that user on port 2222 (no terminal, no X11, no agent, TCP forwarding only) and publishes 2222 through a port forward. It runs in the background.
  2. Initiator (configurar_iniciador_tunel_vps): installs autossh and generates an ed25519 key pair. Also in the background.
  3. Key: the initiator's public key is authorized on the receiver (agregar_clave_receptor_tunel_vps).
  4. Tunnel (crear_tunel_vps): creates on the initiator a systemd service ardutunnel_<nombre> running autossh -L <local>:<target>:<port> against the receiver, enables it and starts it.

configurar_tunel_entre_vps does all four steps and also starts the tunnel. It waits for each role to finish installing (up to a few minutes); if one does not finish in time, it stops with tunel_rol_instalando and can be resumed with desde. A role already installed is not reinstalled, a key already authorized is reused and a tunnel already created with that name and local port comes back as it is.

What actually changes?

On the receiver

  • A tunnel user with its authorized keys in its own authorized_keys block.
  • An ArduMaker-managed sshd configuration file that adds port 2222 with its Match LocalPort block (port 22 stays declared). Do not edit it by hand: it is fully rewritten whenever a role changes.
  • A public port forward to the VPS port 2222.

On the initiator

  • The autossh package and an ed25519 key pair for the tunnel user.
  • One ardutunnel_<nombre>.service per tunnel, with Restart=always, enabled at boot.
  • A tunnel-specific known_hosts: the receiver's first key is pinned and, if it later changes, the tunnel does not connect.

Ports and connection

ItemValue
Receiver port inside the VPS2222
Public portThe host_port of the forward to 2222 (public_port)
HostThe receiver dnsip
Connection string<usuario>@<dnsip> -p <public_port>
ExecStart=/usr/bin/autossh -M 0 -N \
  -o "ServerAliveInterval=30" -o "ServerAliveCountMax=3" \
  -o "StrictHostKeyChecking=accept-new" -o "ExitOnForwardFailure=yes" \
  -L <puerto_local>:localhost:<puerto_remoto> \
  -i <clave_del_tunel> -p <public_port> <usuario>@<dnsip>

ver_estado_receptor_tunel_vps returns the connection string and a usage example to connect from another machine with ssh -N -L.

Diagram

  1. App on the initiator: localhost:<puerto_local>
  2. autossh: ardutunnel_<nombre> service
  3. Receiver port forward: <dnsip>:<public_port> → 2222
  4. Receiver sshd :2222: tunnel user only, forwarding only
  5. Receiver service: localhost:<puerto_remoto>
A tunnel between two VPS

Expected result

  • ver_tunel_vps shows the tunnel with the live status of its systemd service.
  • listar_tuneles_vps lists the initiator's tunnels; ver_estado_iniciador_tunel_vps returns its public key and the tunnel count.
  • From the initiator, the service answers at localhost:<puerto_local>.
systemctl status ardutunnel_mi-base
nc -vz localhost 15432

Errors and troubleshooting

AnswerCauseWhat to do
409 tunel_rol_instalandoA role is still installingAsk again; it resumes from there
tunel_rol_fallidoA role installation failedCheck the role status and retry
tunel_mismo_vpsReceiver and initiator are the same VPSUse two different VPS
409 local port in useAnother initiator tunnel already uses that local_portPick another port
409 Key already registeredThe key is already authorizedNothing to do
The tunnel does not connectThe receiver key changed, or the initiator key is not authorizedCheck ver_logs_systemd_vps for the ardutunnel_<nombre> service

If a step of the flow fails, whatever can be undone is undone; installed packages remain and are reported in residuo (reinstalling changes nothing). The flow accepts idempotency_key and simular.

MCP access

Yes, in the /mcp and /mcp/vps profiles. Installing a role returns an operacion_id: ver_operacion says when it is ready.

ToolType
configurar_tunel_entre_vpsWrite (full flow)
ver_estado_receptor_tunel_vps, listar_claves_receptor_tunel_vps, ver_estado_iniciador_tunel_vps, listar_tuneles_vps, ver_tunel_vpsRead
configurar_receptor_tunel_vps, configurar_iniciador_tunel_vps, agregar_clave_receptor_tunel_vps, crear_tunel_vpsWrite
controlar_tunel_vpsWrite (stop drops traffic)
eliminar_tunel_vps, eliminar_clave_receptor_tunel_vps, eliminar_receptor_tunel_vps, eliminar_iniciador_tunel_vpsDestructive

Reference at /mcp/docs and examples at /mcp/tuneles.

Related features