SSH bastion setup
Elglide connects to your source database through your SSH bastion — a jump host you already control. You open one inbound rule on the bastion: SSH, from our static IPs only. We initiate every session, it closes when the extract finishes, and the key can reach only the one database host and port you allow.
authorized_keys. That's all.
Architecture
One trust boundary: your firewall. Our connector dials your bastion; the bastion forwards that single connection to your source database.
When to use it
- You already run a bastion, or your security policy requires all access through one entry point.
- You don't want to install Elglide software inside your network.
- If source data must never leave your network, use Hybrid Agent instead.
Setup
1. Bastion sizing
A small VM is fine — 1 vCPU, 1 GB RAM, your distro of choice. Each extract opens one SSH session and closes it on completion. We never keep persistent tunnels.
2. Allow inbound SSH from us
Allow inbound TCP 22 (or whatever non-default port you've
chosen for SSH) from
our static egress IPs.
That's the only inbound rule we need.
3. Generate the keypair (in our portal)
From Account → SSH Tunnels (sign-in required) click + New SSH tunnel. Fill in the bastion host, port and SSH user, then Generate keypair — we show you the public key to install in step 5.
Keep Ed25519 unless your bastion rejects it; then use RSA-4096.
4. Find your DB_HOST:DB_PORT
Use the address that reaches the database from the bastion. If the
database runs on the bastion itself, use 127.0.0.1. Confirm on the bastion:
getent hosts DB_HOST # resolves here?
nc -vz DB_HOST DB_PORT # reachable from here?
5. Create the user & install the key by platform
Replace <your-public-key> with the key from step 3 and
DB_HOST:DB_PORT with the value from step 4. The line is the same on
every platform:
restrict,port-forwarding,permitopen="DB_HOST:DB_PORT" <your-public-key>
restrict denies everything, port-forwarding re-enables
forwarding only, and permitopen limits it to that one address — so this key
cannot reach anything else on your network. Needs OpenSSH 7.2+ (check
ssh -V); on older servers use:
no-pty,no-agent-forwarding,no-X11-forwarding,no-user-rc,permitopen="DB_HOST:DB_PORT" <your-public-key>
- Create the user:
sudo useradd -m -s /usr/sbin/nologin elglide sudo passwd -l elglide sudo mkdir -p /home/elglide/.ssh && sudo chmod 700 /home/elglide/.ssh sudo touch /home/elglide/.ssh/authorized_keys sudo chmod 600 /home/elglide/.ssh/authorized_keys sudo chown -R elglide:elglide /home/elglide/.ssh - Install the key:
echo 'restrict,port-forwarding,permitopen="DB_HOST:DB_PORT" <your-public-key>' \ | sudo tee -a /home/elglide/.ssh/authorized_keys - Check forwarding is allowed and sshd is running:
sudo sshd -T | grep -i allowtcpforwarding # expect: yes sudo systemctl enable --now ssh # sshd on RHEL-family
nologin shell is fine — forwarding doesn't need one. If
allowtcpforwarding is no, set
AllowTcpForwarding yes in /etc/ssh/sshd_config and
reload; permitopen can't override a server-wide no.
dscl service accounts on macOS are fiddly. For an
evaluation, use an account you already log in with. For anything long-lived, a
small Linux VM is easier.
- Enable Remote Login:
Then restrict it: System Settings → General → Sharing → Remote Login → Only these users.sudo systemsetup -setremotelogin on sudo systemsetup -getremotelogin # expect: Remote Login: On - Install the key:
mkdir -p ~/.ssh && chmod 700 ~/.ssh printf '%s\n' 'restrict,port-forwarding,permitopen="DB_HOST:DB_PORT" <your-public-key>' \ >> ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys - Check forwarding:
sudo sshd -T | grep -i allowtcpforwarding # expect: yes
sshd doesn't answer and our dial times out — which looks like a
firewall problem. Overnight extracts are the usual casualty. Leave
sudo caffeinate -s running, or disable sleep in System Settings →
Energy Saver / Battery.
Windows has a native OpenSSH Server — an optional feature, already on the box.
- Install and start it (elevated PowerShell):
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 Set-Service -Name sshd -StartupType Automatic Start-Service sshd Get-Service sshd | Select-Object Status, StartType # expect: Running, Automatic - Check which
authorized_keysfile sshd reads:
The shipped config sends all administrators to one shared file:Select-String -Path C:\ProgramData\ssh\sshd_config -Pattern '^\s*Match|AuthorizedKeysFile'
So an admin user's key goes inMatch Group administrators AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keysC:\ProgramData\ssh\administrators_authorized_keysandC:\Users\<user>\.ssh\authorized_keysis ignored. A non-admin user uses the per-user file.Editing the wrong file gives a barePermission denied (publickey)— no hint that the file you edited was never read. - Create a non-admin user (recommended — keeps your key out of
the shared admin file):
The password is never used — we authenticate with the key — but Windows requires one.$pw = Read-Host -AsSecureString "Password for elglide-tunnel" New-LocalUser -Name elglide-tunnel -Password $pw -PasswordNeverExpires -AccountNeverExpires # Do NOT add this user to Administrators. - Install the key with a strict ACL:
$dir = "C:\Users\elglide-tunnel\.ssh" $path = "$dir\authorized_keys" New-Item -ItemType Directory -Force -Path $dir | Out-Null Set-Content -Path $path -Encoding ascii ` -Value 'restrict,port-forwarding,permitopen="DB_HOST:DB_PORT" <your-public-key>' icacls $path /inheritance:r icacls $path /grant "elglide-tunnel:R" icacls $path /grant "SYSTEM:F" icacls $path /setowner elglide-tunnelC:\Users\elglide-tunnelwon't exist until that account first logs in — creating the folder yourself, as above, is fine. - Verify the ACL:
Get-Content $pathThis command failing withAccess … is deniedfrom your admin shell is the correct result — it's what OpenSSHStrictModesrequires. If it prints the key, re-run theicaclslines or sshd will reject it. - Restart after any
sshd_configedit:
Windows doesn't reloadRestart-Service sshdsshd_configon its own.authorized_keysis re-read per connection, so key changes need no restart.
If your tunnel user is an administrator, skip steps 3–5 and append the
same line to C:\ProgramData\ssh\administrators_authorized_keys.
6. Test it yourself first
Before clicking Diagnose, reproduce what we do — an SSH session plus a local forward — from any machine that can reach the bastion. In one terminal:
ssh -N -L 15432:DB_HOST:DB_PORT BASTION_USER@BASTION_HOST -p BASTION_PORT
In a second, connect through it:
# PostgreSQL
psql -h 127.0.0.1 -p 15432 -U <db-user> -d <db-name> -c 'select 1'
# MySQL / MariaDB
mysql -h 127.0.0.1 -P 15432 -u <db-user> -p -e 'select 1'
# SQL Server
sqlcmd -S 127.0.0.1,15432 -U <db-user> -Q "select 1"
select 1 returns, your bastion, firewall, AllowTcpForwarding,
DB_HOST:DB_PORT and database credentials are all correct. You're
authenticating as yourself here, so this doesn't test our key line — if this passes but
Diagnose still fails on the SSH step, the problem is the key line, not your network.
7. Configure the source DB connection
From Connections (sign-in required) → Edit / Create. Pick SSH Tunnel in the Connectivity section and select the tunnel you just made. Click Diagnose to run the 4-step test (network → SSH → database → permissions).
8. Start, stop, revoke
There's no Elglide process to manage — only your SSH service.
| Platform | Start / enable at boot | Stop | Status |
|---|---|---|---|
| Linux | sudo systemctl enable --now ssh |
sudo systemctl stop ssh |
systemctl status ssh |
| macOS | sudo systemsetup -setremotelogin on |
sudo systemsetup -setremotelogin off |
sudo systemsetup -getremotelogin |
| Windows | Start-Service sshd |
Stop-Service sshd |
Get-Service sshd |
elglide-tenant-<N> line — don't
stop sshd. Removing the line takes effect on our next attempt, no
restart needed. Stopping sshd is usually how you reach the machine too, and
on a cloud host with no console it locks you out.
9. If your bastion's address changes
ngrok, Cloudflare Tunnel quick tunnels and similar free tiers hand out a new host and port on every restart, so the values you saved in step 3 go stale.
Symptom: it worked yesterday, and today Diagnose fails on
network reachability only — the later steps never run.
Fix: Account → SSH Tunnels → pencil icon → update Bastion host
and Port → Save → re-run Diagnose.
10. Replacing the public key (rotation)
We rotate keys periodically (or on demand) — Account → SSH Tunnels → the rotate icon. Choose Rotate the shared key to swap one key across every Connection that uses this tunnel, or Detach a specific source to give just one Connection its own fresh keypair while the others keep their existing one.
After clicking, we show you the new public key + the install line.
Until you replace the old line in authorized_keys on the
bastion (or the Windows equivalent from the Windows tab in step 5),
every sync that uses this tunnel will fail with
Permission denied (publickey).
Every key we issue ends with a comment of the form
elglide-tenant-<N>, where
<N> is your tenant id (visible in the portal).
That comment is how you identify which line to replace.
Replace <bastion-user> with the user you created
in step 5, <your-new-public-key> with the freshly
shown key, and DB_HOST:DB_PORT with the value from step 4:
sudo -u <bastion-user> sed -i '/elglide-tenant-<N>/d' ~<bastion-user>/.ssh/authorized_keys
echo 'restrict,port-forwarding,permitopen="DB_HOST:DB_PORT" <your-new-public-key>' \
| sudo -u <bastion-user> tee -a ~<bastion-user>/.ssh/authorized_keys
sudo -u <bastion-user> chmod 600 ~<bastion-user>/.ssh/authorized_keys
Then re-run the Diagnose modal on the Connection — the SSH step
should turn green within seconds. No bastion restart needed; sshd
re-reads authorized_keys on every connection.
Already set up?
permitlisten. If it's there, change
it to permitopen. permitlisten only applies to
ssh -R; we use ssh -L, so it does nothing — the key can
currently forward anywhere your bastion can reach. Edit the
elglide-tenant-<N> line in authorized_keys. No rotation,
no downtime.
Troubleshooting
| Diagnose step shows… | Most likely cause | Fix |
|---|---|---|
| ✗ Network reachability — TCP refused | Inbound firewall blocks our IPs | Allow our static IPs on the bastion's SSH port |
| ✗ Network reachability — timeout | Wrong bastion hostname or no public DNS | Use the bastion's public IP or a resolvable DNS name |
| ✗ Network reachability — timeout, and it worked yesterday | Bastion address changed (ngrok / Cloudflare free tier reassigns on restart) | Update Bastion host and Port on the tunnel row (step 9). Don't rotate the key. |
| ✗ Network reachability — timeout on a macOS bastion | The Mac is asleep | sudo caffeinate -s, or disable sleep |
| ✗ SSH tunnel — Permission denied (publickey) | Public key not installed, wrong username, or
authorized_keys file permissions wrong |
Verify the user exists, the public key matches, and file
perms are 600 (the dir 700) |
| ✗ Port forwarding — every earlier step green | AllowTcpForwarding no, or restrict without
port-forwarding after it |
Set AllowTcpForwarding yes and restart sshd; confirm with
sudo sshd -T | grep -i allowtcpforwarding |
✗ SSH tunnel — administratively prohibited: open failed |
permitopen doesn't match the address we forward to — or the line
still says permitlisten, which does nothing here |
Set permitopen="DB_HOST:DB_PORT" to exactly the step 4 value.
db.internal:5432 and 10.0.1.7:5432 are different to
OpenSSH. |
| ✗ Database access — login failed | DB credentials are wrong (everything before this passed, so the bastion is fine) | Update the username/password on the Connection |
| ✗ Permissions check — SELECT denied | The DB user can connect but lacks SELECT on
INFORMATION_SCHEMA / information_schema |
Grant SELECT on the metadata views (or use a role with schema-discovery privileges) |
| ✗ SSH tunnel — Permission denied (publickey) after a rotation | Old elglide-tenant-<N> line still in
authorized_keys; sshd is matching the old
key, not the new one |
Delete the old line; keep only the freshly-shown one. Step 10 above. |
| ✗ SSH tunnel — Permission denied (publickey) on a Windows bastion | Wrong authorized_keys file edited (per-user
vs admin), OR the ACL still grants Administrators access |
Confirm which file your sshd reads (Windows tab, step 5), and verify
Get-Content on it fails from an admin shell |
Any step — a Windows sshd_config change had no effect |
Windows doesn't reload sshd_config automatically |
Restart-Service sshd |
Still stuck? Open the failed step in the Diagnose modal and click Show details — the technical detail there includes the exact driver error, which is what to share when filing a support ticket.