# Network tunnels

A tunnel lets MainPath reach servers behind a firewall or NAT without exposing SSH to the internet.

> Source: https://www.mainpath.ai/en/docs/network-tunnels/

Setup, deployments (project images and Docker apps), and installing monitoring all need SSH access to the server. If the server sits on a company network, behind NAT, or behind a firewall that does not open port 22, a public host is not enough. In the **Servers** tab, under **Network tunnels**, you create a tunnel and assign it to the server.

Several servers may share the same tunnel, for example one company VPN for many internal hosts. An organization can have at most five tunnels. The private network of [workspaces]({{< relref "workspace-vm-tailscale" >}}) stays separate; do not mix Tailscale there with these tunnels.

## Which type to use

| Type | Good fit when |
|------|---------------|
| SSH jump / bastion | You already have a publicly reachable jump host through which internal hosts are reachable over SSH |
| Reverse SSH | The target server must not accept inbound SSH and opens the connection itself |
| WireGuard | MainPath should join your existing WireGuard network as a client |
| OpenVPN | MainPath should join your existing OpenVPN network as a client |

The server **Host** is then the address **behind** the tunnel, for example `10.0.1.20` or a name on the LAN. HTTP and HTTPS on the server stay public if you serve websites from it.

## Creating a tunnel

1. Open **Servers** in the sidebar.
2. Under **Network tunnels**, click **Add tunnel**.
3. Enter a name and a short id, choose the type, and fill in the fields.
4. Save. **Test connection** checks whether the tunnel is up; the card shows connected, connecting, or error.

Only organization administrators create or change tunnels. OpenVPN and WireGuard configuration plus the OpenVPN username are visible to admins; passwords and jump keys stay write-only. An update replaces them.

A failed test shows a short summary and, under **Technical details**, the OpenVPN log.

### SSH jump

Enter the bastion host, port, and user, and store the private key (recommended) or a password. MainPath jumps through that host to the internal server.

### Reverse SSH

The server on your network starts the connection. After saving, MainPath shows a ready-made command. You see the private key only once; copy it to the server and run the command there (for example as a systemd unit). While that process runs, MainPath can reach the internal SSH service.

### WireGuard and OpenVPN

Paste the client configuration (`.conf` or `.ovpn`). For OpenVPN you can also store a username and password if your server requires them. MainPath connects as a client; you do not need to install a VPN service on the app server itself.

## Assigning a server

When you create or edit an SSH, cloud, or managed server, choose the tunnel under **Access via tunnel (optional)**. Without an assignment MainPath keeps using direct public SSH.

Once a tunnel is set, MainPath addresses on a **public** SSH port are no longer required if SSH stays closed to the internet. The optional [SSH firewall]({{< relref "servers" >}}#monitoring-and-firewall) and the tunnel complement each other: the firewall limits who may knock on the public port; the tunnel is the path when that port does not exist.

## Monitoring

**Monitor server** installs the agent through the same tunnel. Afterwards the agent connects outbound to monitoring on its own; the tunnel is no longer needed for that as long as HTTPS from your network to MainPath is allowed.

## CLI and agent

```bash
mp platform servers tunnels list
mp platform servers tunnels get <tunnel-id>
mp platform servers tunnels test <tunnel-id>
mp platform servers tunnels test <tunnel-id> --dest-host 10.0.1.20 --dest-port 22
mp platform servers tunnels create --type ssh --slug office-jump --name "Office Jump" \
  --configuration-json '{"sshKind":"jump","jumpHost":"bastion.example","jumpPort":"22","jumpUser":"jump","jumpAuthMethod":"key","jumpPrivateKey":"<private-key>"}'
mp platform servers update <server-id> \
  --configuration-json '{"tunnelId":"<tunnel-id>"}'
```

Set `tunnelId` to `none` to clear the assignment. The same steps are available to the agent as `platform_tunnels_*`; deletion requires `confirm=true`. CLI details are under [CLI and agent plugin]({{< relref "cli-and-agent-plugin" >}}).

