> ## Documentation Index
> Fetch the complete documentation index at: /docs/llms.txt
> Use this file to discover all available pages before exploring further.

# SSH tunnels

> Connect Ordyn to a database that isn't reachable from the internet, through a bastion host.

If your database only accepts connections from inside your network, Ordyn can reach it through a bastion host (a jump server) over SSH. Ordyn logs into the bastion and forwards its database connection through it. The bastion connects to the database from inside your network, so the database itself stays private.

Tunnels work with [PostgreSQL](/integrations/postgresql) and [PlanetScale](/integrations/planetscale) Postgres. PlanetScale's Vitess (MySQL) engine can't use a tunnel.

## Permissions

For EC2 Instance Connect, the AWS role Ordyn assumes needs ec2-instance-connect:SendSSHPublicKey on the bastion instance, plus ec2-instance-connect:OpenTunnel on the endpoint when it sits behind an EC2 Instance Connect Endpoint.

### EC2 Instance Connect

Push a one-time SSH key to the bastion and open a tunnel through an EC2 Instance Connect Endpoint, so no SSH key is stored.

| Permission | Used for |
| - | - |
| `ec2-instance-connect:SendSSHPublicKey` | Pushing a temporary SSH key to the bastion instance, limited to its OS user with the ec2:osuser condition |
| `ec2-instance-connect:OpenTunnel` | On the endpoint, limited to the SSH port and tunnels of at most one hour |
| `ec2:DescribeInstanceConnectEndpoints` | Finding the endpoint (on `*`) |
| `ec2:DescribeInstances` | Finding the bastion's private address (on `*`) |

* Ordyn shows a policy scoped to your instance, OS user, endpoint and port when you set up the tunnel.

Permission catalog version `2026-10-16.1`. See [permission changes](/reference/permission-changes).

## Choose how Ordyn reaches the bastion

Turn on **SSH tunnel** in the connection form, then pick an option under **Connect via**:

| Option | Use it when | What the bastion needs |
| - | - | - |
| **Public bastion host** | The bastion accepts SSH from the internet. | SSH open to the internet. |
| **EC2 Instance Connect Endpoint** | The bastion is an EC2 instance with no inbound internet access, reached through an [EC2 Instance Connect Endpoint](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/connect-with-ec2-instance-connect-endpoint.html) in its VPC. | SSH allowed from the endpoint's security group only, with the endpoint's **Preserve Client IP** setting off (the default). |

Either way, the bastion's SSH server must allow TCP forwarding (`AllowTcpForwarding yes`, the default in OpenSSH).

## Choose how Ordyn logs in

| Login | How it works | What Ordyn stores |
| - | - | - |
| **Password** | An SSH username and password for the bastion. | The password, encrypted. |
| **AWS EC2 Instance Connect** | For each connection, Ordyn creates a one-time SSH key and pushes it to the bastion with `ec2-instance-connect:SendSSHPublicKey`, using the role of your [AWS connection](/integrations/aws-cloudwatch). The key works for 60 seconds. | No SSH secret. |

EC2 Instance Connect is the usual choice for EC2 bastions: there is no password to rotate, and your AWS role decides exactly which instance and user Ordyn can log in as.

## Set up a public bastion

**1. Fill in the tunnel**

Enter the SSH host, port, and username, and choose the login.

**2. With EC2 Instance Connect: grant the role**

Enter the bastion's instance ID and region, and pick the AWS connection whose role Ordyn should use. The form shows an AWS CloudShell command to run once. It adds this inline policy to that role, letting it push keys to this instance only, as this user:

```json theme={null}
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "ec2-instance-connect:SendSSHPublicKey",
      "Resource": "arn:aws:ec2:<region>:<account-id>:instance/<bastion-instance-id>",
      "Condition": { "StringEquals": { "ec2:osuser": "<ssh-username>" } }
    }
  ]
}
```

**3. Test and trust the host key**

Click **Test Connection**, check the bastion's host key, and click **Trust and test**. See [Host key check](#host-key-check).

## Set up a bastion behind an EC2 Instance Connect Endpoint

**1. Fill in the tunnel**

Under **Connect via**, choose **EC2 Instance Connect Endpoint**. Enter the endpoint ID (`eice-...`), the bastion's instance ID and region, the SSH port and username, and pick the AWS connection whose role Ordyn should use. There is no host to enter: Ordyn looks up the endpoint and the bastion's private IP address with that role.

**2. Grant the role**

The form shows an AWS CloudShell command to run once. It lets the role open tunnels through this endpoint to the bastion's SSH port, for at most an hour each, and look up the endpoint and the bastion's private IP. With EC2 Instance Connect login, the same policy also includes the key push shown above.

```json theme={null}
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "ec2-instance-connect:OpenTunnel",
      "Resource": "arn:aws:ec2:<region>:<account-id>:instance-connect-endpoint/<endpoint-id>",
      "Condition": {
        "NumericEquals": { "ec2-instance-connect:remotePort": 22 },
        "NumericLessThanEquals": { "ec2-instance-connect:maxTunnelDuration": 3600 }
      }
    },
    {
      "Effect": "Allow",
      "Action": ["ec2:DescribeInstanceConnectEndpoints", "ec2:DescribeInstances"],
      "Resource": "*"
    }
  ]
}
```

The tunnel permission is limited to this endpoint and the SSH port, not to the bastion itself: the role can open that port on any address the endpoint can reach. It isn't pinned to the bastion's private IP because that IP can change when the instance restarts. To narrow it further, add an `ec2-instance-connect:privateIpAddress` condition with the bastion's IP (and update it if the IP changes), or use a dedicated endpoint whose security group only reaches the bastion.

The two lookups can't be limited to one resource in IAM, so the role can read metadata for every instance and endpoint it can see; they never change anything.

**3. Allow SSH from the endpoint**

The bastion's security group must allow SSH (port 22, or your SSH port) from the endpoint's security group. The bastion needs no inbound rule from the internet.

This assumes the endpoint's **Preserve Client IP** setting is off, which is the default. With it on, the bastion sees each client's own IP address instead, and Ordyn's servers don't have fixed IP addresses to allow. Use an endpoint with Preserve Client IP off for Ordyn.

**4. Test and trust the host key**

Click **Test Connection**, check the bastion's host key, and click **Trust and test**. See [Host key check](#host-key-check).

### Connection limits

An endpoint allows 20 connections at once, shared with your engineers. Ordyn reuses its SSH connections across queries and normally keeps at most **3** open through an endpoint at a time, across all of its servers. In rare cases, such as an outage of Ordyn's coordination service, it can briefly open more rather than fail your queries. If all three are busy for 45 seconds, the query fails with a message saying so. Each tunnel is closed once idle and replaced before the endpoint's one-hour limit. If you need a different limit, contact us.

## Host key check

Before Ordyn sends any SSH login credential, it shows you the bastion's SSH host key. Reading the key opens a connection (through your EC2 Instance Connect Endpoint, if you use one) without logging in to the bastion. Compare the fingerprint with the bastion's own key. On the bastion, run:

```bash theme={null}
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
```

If they match, click **Trust and test**. From then on, Ordyn only connects to a bastion presenting that key. If you replace or re-key the bastion, the next test tells you the key changed and shows the new fingerprint to check and trust.

A trusted key belongs to one bastion: the host and port, or for an endpoint, the endpoint, instance, region, and AWS connection. Changing any of these asks you to check the key again.

## Editing a connection

When you edit a connection, its tunnel settings are filled in. Leave the SSH password blank to keep the saved one. Ordyn only reuses a saved password for the same bastion, username, and login; if you point the tunnel somewhere else, enter the password again. Turn **SSH tunnel** off and save to remove the tunnel.

## TLS through the tunnel

The database connection keeps its TLS settings through the tunnel. With PostgreSQL's `verify-full`, and always for PlanetScale, Ordyn checks the certificate against the database's real hostname, not the tunnel's local address.

## Troubleshooting

> **Ordyn's AWS role isn't allowed to push SSH keys / open a tunnel / look up the endpoint**
>
> The role of the chosen AWS connection is missing the permission the message names. Run the command the form shows, then test again. IAM changes can take a few seconds to apply.

> **EC2 Instance Connect Endpoint ... wasn't found, or isn't ready**
>
> Check the endpoint ID, and that the bastion's region is set if it differs from your AWS connection's region. A new endpoint takes a few minutes to become ready.

> **Bastion instance ... isn't running**
>
> Start the instance. Ordyn looks up its private IP each time, so a restart with a new IP address is fine.

> **The connection times out**
>
> For a public bastion, check that its security group and firewall allow SSH from the internet. Behind an endpoint, check that the bastion's security group allows SSH from the endpoint's security group, and that the endpoint's **Preserve Client IP** setting is off.

> **The bastion's SSH host key changed**
>
> The bastion presented a key other than the one you trusted. If you replaced or re-keyed it, check the new fingerprint on the bastion and trust it. Otherwise, find out why before trusting it.

> **All 3 SSH connections ... are in use**
>
> Ordyn's queries through this endpoint are using every connection it may hold. Try again shortly.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.