<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Svyatoslav Pavlov</title>
    <description>The latest articles on DEV Community by Svyatoslav Pavlov (@wolfhound1995).</description>
    <link>https://dev.to/wolfhound1995</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4060150%2Fade29fc0-19eb-4bbf-9bf8-0fc51e252710.jpg</url>
      <title>DEV Community: Svyatoslav Pavlov</title>
      <link>https://dev.to/wolfhound1995</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wolfhound1995"/>
    <language>en</language>
    <item>
      <title>Connect OpenAI Codex to your servers over SSH — safely</title>
      <dc:creator>Svyatoslav Pavlov</dc:creator>
      <pubDate>Wed, 16 Sep 2026 18:14:39 +0000</pubDate>
      <link>https://dev.to/wolfhound1995/connect-openai-codex-to-your-servers-over-ssh-safely-40ah</link>
      <guid>https://dev.to/wolfhound1995/connect-openai-codex-to-your-servers-over-ssh-safely-40ah</guid>
      <description>&lt;p&gt;Codex is happy inside a repo. The task that sends you looking for server access is the one that leaves it — the deploy failed, the migration hung, the service won't come back up &lt;em&gt;on the actual box&lt;/em&gt;. The tempting fix is to let Codex run &lt;code&gt;ssh&lt;/code&gt; itself: it already executes shell commands on your machine, so if a key sits unlocked in &lt;code&gt;~/.ssh&lt;/code&gt; or a running &lt;code&gt;ssh-agent&lt;/code&gt;, &lt;code&gt;ssh prod&lt;/code&gt; works for it without you deciding anything. That's not "giving Codex access" — that's discovering you gave it away by default.&lt;/p&gt;

&lt;p&gt;There's a cleaner arrangement, and Codex is built for it. OpenAI's Codex CLI speaks &lt;a href="https://termal.in/blog/what-is-an-mcp-server/" rel="noopener noreferrer"&gt;MCP&lt;/a&gt;, and Termalin is an SSH client with a built-in MCP server. Point Codex at that server and it works your hosts for real — runs commands, reads and writes files — while the agent never holds a key. This is the same shape as the &lt;a href="https://termal.in/blog/connect-claude-code-to-your-server-over-ssh/" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt; and &lt;a href="https://termal.in/blog/cursor-ssh-access-for-agents/" rel="noopener noreferrer"&gt;Cursor&lt;/a&gt; setups; here's the Codex-specific version, in about twenty minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Termalin&lt;/strong&gt; — the desktop app; the &lt;a href="https://termal.in/download/" rel="noopener noreferrer"&gt;Free tier is fine&lt;/a&gt;. Its built-in MCP server is what Codex will talk to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Codex CLI&lt;/strong&gt;, on the same machine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One host&lt;/strong&gt; saved in Termalin. Pick something you don't mind an agent poking at for the first run — staging, a throwaway VPS.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1 — turn agent access on and set a policy
&lt;/h2&gt;

&lt;p&gt;Agent access is &lt;strong&gt;off by default&lt;/strong&gt;. In Termalin, open &lt;strong&gt;Settings → MCP&lt;/strong&gt;, enable it, and tick the hosts Codex may reach. Nothing outside that list exists as far as the agent is concerned; adding a host is a decision you make in the app, not something Codex can talk its way into.&lt;/p&gt;

&lt;p&gt;Two things happen underneath. Termalin writes a host inventory for the MCP server with &lt;strong&gt;agent-only auth entries — no passwords or keys land in that file&lt;/strong&gt;. And authentication stays with the app's key custodian: you unlock once, and Termalin signs on the agent's behalf. There's no key file for Codex to read, so there's nothing to leak — which also closes the &lt;code&gt;~/.ssh&lt;/code&gt; back door above.&lt;/p&gt;

&lt;p&gt;While you're there, set each host's &lt;strong&gt;agent policy&lt;/strong&gt;: full access, an allowlist of commands, or blocked. For anything you care about, start with an allowlist — the shortest list that lets Codex do the job you have in mind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — register the server in config.toml
&lt;/h2&gt;

&lt;p&gt;Codex reads MCP servers from the &lt;code&gt;mcp_servers&lt;/code&gt; section of &lt;code&gt;~/.codex/config.toml&lt;/code&gt;. Add one entry pointing at the bundled &lt;code&gt;termalin-mcp&lt;/code&gt; binary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[mcp_servers.termalin]&lt;/span&gt;
&lt;span class="py"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"&amp;lt;path&amp;gt;/termalin-mcp"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Recent Codex builds can add it from the command line instead — the same result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;codex mcp add termalin &lt;span class="nt"&gt;--&lt;/span&gt; &amp;lt;path&amp;gt;/termalin-mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole integration, and the same config drives Codex in the IDE. Restart Codex, confirm the server shows as connected, and the tools appear to the agent: &lt;code&gt;hosts_list&lt;/code&gt;, &lt;code&gt;ssh_exec&lt;/code&gt;, persistent sessions (&lt;code&gt;session_open&lt;/code&gt; / &lt;code&gt;session_exec&lt;/code&gt;), SFTP reads and writes, and port forwards.&lt;/p&gt;

&lt;p&gt;One Codex habit worth keeping for the first sessions: leave its command approvals on. Codex asks before it acts unless you tell it not to — don't tell it not to yet. Widen that the same way you widen the host list: one boring thing at a time, after you've watched how it behaves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — give it a real chore
&lt;/h2&gt;

&lt;p&gt;Skip the demo command. Hand it something you'd actually do:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The deploy to &lt;em&gt;staging-1&lt;/em&gt; finished but the health check is flapping. Find out why and fix it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Codex calls &lt;code&gt;hosts_list&lt;/code&gt;, sees &lt;code&gt;staging-1&lt;/code&gt;, opens a session, and starts pulling threads — service status, the last hundred log lines, the config diff against what's running. It proposes a fix, applies it within the policy you set, and confirms the check goes green.&lt;/p&gt;

&lt;p&gt;The reason this is comfortable rather than nerve-wracking is that none of it is invisible. Every session Codex opens shows up in Termalin as a &lt;strong&gt;live terminal tab&lt;/strong&gt;, and the &lt;strong&gt;watch grid&lt;/strong&gt; puts all open sessions side by side with the agent-driven tiles glowing. You're watching commands land as they run, not reading a summary afterward — and you can step in mid-session at any point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the boundaries are
&lt;/h2&gt;

&lt;p&gt;The defaults worth knowing before you point Codex at anything that matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Your keys never enter Codex's environment.&lt;/strong&gt; The custodian signs; the agent requests. Revoking access is a toggle in Settings, not a key rotation across your fleet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Its reach is the host list&lt;/strong&gt; you ticked in Step 1, and each host's command policy bounds what it can run there. "Full" on production is still a full shell — set it deliberately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Typing into &lt;em&gt;your&lt;/em&gt; session is separate.&lt;/strong&gt; By default Codex opens its own sessions; joining one you already have open is its own consent toggle, and on Pro. It never rides along silently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every command is on the record.&lt;/strong&gt; Session recordings capture output only — never keystrokes — and each agent command is &lt;a href="https://termal.in/blog/audit-what-an-ai-agent-does-on-your-servers/" rel="noopener noreferrer"&gt;tagged as the agent's&lt;/a&gt; in the audit log, with the device and IP it came from. "The agent did this" is a filter you apply, not a story you reconstruct.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When Codex runs where your desktop isn't
&lt;/h2&gt;

&lt;p&gt;If Codex is running in CI or a cloud sandbox rather than on your laptop, the local stdio binary isn't reachable — but Termalin's &lt;strong&gt;hosted MCP endpoint&lt;/strong&gt; is. Create an API key in the web cabinet, scope it to the servers that job should touch, set it to read-only or an allowlist, and give it an expiry. The endpoint reaches your &lt;a href="https://termal.in/mcp/" rel="noopener noreferrer"&gt;Connector-enrolled servers&lt;/a&gt; keyless, authenticating each run with a short-lived certificate — no key handed out, nothing standing. The full pattern, with a copy-paste pipeline example, is in &lt;a href="https://termal.in/blog/agent-ssh-access-in-ci-cd/" rel="noopener noreferrer"&gt;giving an agent SSH access in CI/CD&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The habit that makes it stick
&lt;/h2&gt;

&lt;p&gt;Treat the first week like onboarding a fast-typing teammate. Keep the watch grid open. Give Codex chores with a clear done-state — "make the health check pass", "renew the cert" — and read how it gets there. Keep approvals on until it's earned auto-approve for the dull tools, and widen the host list one unexciting box at a time.&lt;/p&gt;

&lt;p&gt;The asymmetry is the whole point: Codex gets real reach, and you keep the one thing that can't be handed back once it's out.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Termalin is a cross-platform SSH client with a built-in MCP server, a key custodian and &lt;a href="https://termal.in/ssh-for-ai-agents/" rel="noopener noreferrer"&gt;per-host agent policy&lt;/a&gt;. Start from the &lt;a href="https://termal.in/codex-ssh/" rel="noopener noreferrer"&gt;Codex SSH page&lt;/a&gt;, read the &lt;a href="https://termal.in/mcp/" rel="noopener noreferrer"&gt;MCP reference&lt;/a&gt;, or &lt;a href="https://termal.in/download/" rel="noopener noreferrer"&gt;download the app&lt;/a&gt; — the Free tier has no host limits.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>codex</category>
      <category>mcp</category>
      <category>aiagents</category>
      <category>ssh</category>
    </item>
    <item>
      <title>How to audit and record what an AI agent does on your servers</title>
      <dc:creator>Svyatoslav Pavlov</dc:creator>
      <pubDate>Tue, 15 Sep 2026 16:49:19 +0000</pubDate>
      <link>https://dev.to/wolfhound1995/how-to-audit-and-record-what-an-ai-agent-does-on-your-servers-40i2</link>
      <guid>https://dev.to/wolfhound1995/how-to-audit-and-record-what-an-ai-agent-does-on-your-servers-40i2</guid>
      <description>&lt;p&gt;The moment you let an AI agent touch a real server, a new question appears that never mattered when it was just you at the keyboard: &lt;strong&gt;what did it actually do?&lt;/strong&gt; Not what it &lt;em&gt;says&lt;/em&gt; it did — agents narrate their work, and the narration is often tidy fiction, skipping the command that failed, the file it read "just to check," the thing it tried before the thing that worked. When you ask a human colleague "what did you run on prod last night?" you're trusting a memory. When you ask an agent, you're trusting a language model's account of a language model's actions. Neither is an audit trail. This post is about building one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the agent's own report doesn't count
&lt;/h2&gt;

&lt;p&gt;Three reasons, and they compound:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It's produced by the thing being audited.&lt;/strong&gt; A record the actor writes about itself is a statement, not evidence. If the agent is confused — or &lt;a href="https://termal.in/blog/agent-ssh-access-blast-radius/" rel="noopener noreferrer"&gt;steered by a poisoned log line or filename it read along the way&lt;/a&gt; — its report is confused or steered too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's lossy on purpose.&lt;/strong&gt; Models summarize. The command that returned an error, the exploratory &lt;code&gt;cat&lt;/code&gt; of a config, the &lt;code&gt;curl&lt;/code&gt; to an address you didn't expect — exactly the lines an incident review cares about — are the ones a summary drops as noise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It isn't attributable later.&lt;/strong&gt; "The agent cleaned up disk space" is not something you can hand an auditor, diff against a change ticket, or replay six months on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fix is the same one that has always separated a real audit trail from a story: &lt;strong&gt;make the record outside the actor&lt;/strong&gt;, capture the raw session, and attach identity and time to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a real trail needs
&lt;/h2&gt;

&lt;p&gt;Borrowing the checklist from &lt;a href="https://termal.in/blog/record-audit-ssh-sessions-bastion/" rel="noopener noreferrer"&gt;auditing sessions on a bastion&lt;/a&gt;, an agent trail has to answer &lt;em&gt;who&lt;/em&gt;, &lt;em&gt;when&lt;/em&gt;, &lt;em&gt;from where&lt;/em&gt;, &lt;em&gt;what&lt;/em&gt;, and &lt;em&gt;can I trust this&lt;/em&gt; — with one addition unique to agents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Every command captured as it ran&lt;/strong&gt; — not a summary, the actual session output, replayable with its original timing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The agent distinguished from you.&lt;/strong&gt; The single most important field. A record that can't tell "the agent ran this" from "I ran this" forces you to reconstruct at 2 a.m. what should have been a filter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connection metadata alongside&lt;/strong&gt; — which host, when, from which device and IP.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output, never keystrokes.&lt;/strong&gt; More on this below — it's the trap that turns an audit trail into a liability.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A record the agent can't quietly edit.&lt;/strong&gt; If the actor can rewrite its own history, you're back to trusting the story.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The keystroke trap — sharper for agents
&lt;/h2&gt;

&lt;p&gt;A terminal session has two streams and they are not symmetric. &lt;strong&gt;Output&lt;/strong&gt; holds every command that ran (the shell echoes it) plus everything those commands printed — the complete forensic record. &lt;strong&gt;Input&lt;/strong&gt; adds exactly one thing output doesn't: the characters deliberately &lt;em&gt;not&lt;/em&gt; echoed — sudo passwords, passphrases, a token pasted into a prompt.&lt;/p&gt;

&lt;p&gt;Record input and you haven't built an audit trail, you've built &lt;a href="https://termal.in/blog/record-ssh-sessions-without-keylogging/" rel="noopener noreferrer"&gt;a keylogger aimed at your own infrastructure&lt;/a&gt;. With agents it's worse: agent sessions are the ones you're &lt;em&gt;most&lt;/em&gt; likely to record by default and &lt;em&gt;least&lt;/em&gt; likely to be watching keystroke-by-keystroke, so a capture-everything setting quietly becomes a growing archive of secrets nobody decided to collect. The rule holds in every layer: &lt;strong&gt;record output, never input.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Server-side, client-side, or both
&lt;/h2&gt;

&lt;p&gt;You can record where the session lands (on the server, with &lt;code&gt;tlog&lt;/code&gt; or auditd) or where it originates (in the client). For agents specifically, the client side isn't just a nice complement — it's the only layer that can capture the fact that mattered most: &lt;em&gt;who was driving&lt;/em&gt;. A server-side recorder sees a session on &lt;code&gt;db-01&lt;/code&gt;; it cannot see that a model opened it via MCP rather than a person via &lt;code&gt;ssh&lt;/code&gt;. That distinction has to be stamped in at the source, by the thing that brokered the connection.&lt;/p&gt;

&lt;p&gt;So the strong setup is: &lt;strong&gt;client-side recording that marks agent commands&lt;/strong&gt;, plus server-side recording where you need the "everyone, no exceptions, whatever client they used" guarantee. The two answer different questions and corroborate each other in a review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Doing it with Termalin
&lt;/h2&gt;

&lt;p&gt;Termalin is the client-side layer, and because its &lt;a href="https://termal.in/mcp/" rel="noopener noreferrer"&gt;MCP server&lt;/a&gt; is also the thing brokering the agent's access, it can stamp the record at the source:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sessions are recorded output-only by design.&lt;/strong&gt; Keystrokes are never captured — there's no setting to turn that into a keylogger. Turn recording on in &lt;strong&gt;Settings → Terminal&lt;/strong&gt; or per host on the host form, and set a &lt;strong&gt;retention window&lt;/strong&gt; (7, 30, 90, 365 days, or forever) so old recordings prune themselves instead of piling up as liability.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent commands are marked.&lt;/strong&gt; Each command an agent issues gets its own &lt;strong&gt;green marker&lt;/strong&gt; on the replay timeline (your commands and system events are amber), and the same distinction is written to the &lt;strong&gt;audit log&lt;/strong&gt;. "Show me only what the agent did" is a filter, not a reconstruction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The audit log carries the metadata.&lt;/strong&gt; Every sign-in, host connection, file operation and agent command, with where (IP · country) and which device — the who/when/from-where an auditor asks for, correlated with the session instead of scattered across log files with drifting clocks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You can watch it live.&lt;/strong&gt; The &lt;strong&gt;watch grid&lt;/strong&gt; mirrors every open session as a tile, and the ones an agent is driving glow. Live oversight doesn't lower the odds of a bad instruction landing — it lowers &lt;em&gt;dwell time&lt;/em&gt;, so a steered agent is caught in minutes, not discovered in a postmortem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recordings the agent can't rewrite.&lt;/strong&gt; On paid plans they sync end-to-end encrypted — the server stores ciphertext it can't replay, and you replay from the web cabinet anywhere. The record survives even if the box the agent touched is rebuilt.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The honest limits
&lt;/h2&gt;

&lt;p&gt;Worth stating plainly, because a trail you over-trust is its own risk:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A recording proves what ran, not what leaked.&lt;/strong&gt; If your policy lets the agent &lt;code&gt;cat&lt;/code&gt; a file, &lt;code&gt;cat .env&lt;/code&gt; returns your secrets into its context, and the recording faithfully shows it happening — after the fact. Auditing tells you &lt;em&gt;that&lt;/em&gt; it happened fast; it doesn't prevent it. Pair recording with a tight &lt;a href="https://termal.in/ssh-for-ai-agents/" rel="noopener noreferrer"&gt;per-host command policy&lt;/a&gt; and with keeping secrets off disk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client-side covers the sessions that go through the client.&lt;/strong&gt; It can't testify about a session someone opened another way — that's what the server-side layer is for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Marked as the agent" is only as trustworthy as the broker.&lt;/strong&gt; The custodian doing the marking is now a thing to protect; concentrating that trust in one hardened place is the right trade, but it is a trade.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are reasons to skip the trail — they're the difference between having an audit story and believing a marketing one. The &lt;a href="https://termal.in/blog/agent-ssh-access-in-ci-cd/" rel="noopener noreferrer"&gt;same setup runs in CI&lt;/a&gt;, where every hosted run is logged against the API key that made it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Termalin is a free, cross-platform SSH client with output-only &lt;a href="https://termal.in/ssh-session-recording/" rel="noopener noreferrer"&gt;session recording&lt;/a&gt;, an agent-tagged audit log and a live watch grid — &lt;a href="https://termal.in/download/" rel="noopener noreferrer"&gt;download it&lt;/a&gt;, or read the &lt;a href="https://termal.in/security/" rel="noopener noreferrer"&gt;security model&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>sessionrecording</category>
      <category>audit</category>
      <category>security</category>
    </item>
    <item>
      <title>What is a jump host? (And how to use one without collecting keys on it)</title>
      <dc:creator>Svyatoslav Pavlov</dc:creator>
      <pubDate>Mon, 14 Sep 2026 16:28:41 +0000</pubDate>
      <link>https://dev.to/wolfhound1995/what-is-a-jump-host-and-how-to-use-one-without-collecting-keys-on-it-58jc</link>
      <guid>https://dev.to/wolfhound1995/what-is-a-jump-host-and-how-to-use-one-without-collecting-keys-on-it-58jc</guid>
      <description>&lt;p&gt;A &lt;strong&gt;jump host&lt;/strong&gt; is a hardened intermediary server that acts as the single, audited entry point into a private network. The servers you actually care about — databases, app servers, build machines — have no public address at all; the only way to reach them over SSH is to go &lt;em&gt;through&lt;/em&gt; the jump host. You connect to it from the outside, and from there your connection continues inward to the machine you wanted.&lt;/p&gt;

&lt;p&gt;That's the definition. The design question that follows — how to pass through it without scattering credentials along the way — is where most setups go wrong, and it's what the rest of this page is about. If you already know the concept and just want the commands, the &lt;a href="https://termal.in/blog/ssh-proxyjump-bastion-host/" rel="noopener noreferrer"&gt;ProxyJump setup guide&lt;/a&gt; is the how-to; this page is the what and the why.&lt;/p&gt;

&lt;h2&gt;
  
  
  Jump host, bastion, jump box, gateway: one idea, four names
&lt;/h2&gt;

&lt;p&gt;The terminology is an archaeology of the same pattern. &lt;strong&gt;Bastion host&lt;/strong&gt; is the oldest term, borrowed from fortification — the strongpoint built to face the attack. &lt;strong&gt;Gateway&lt;/strong&gt; emphasizes the network role: the machine that connects two networks that otherwise don't touch. &lt;strong&gt;Jump host&lt;/strong&gt; and &lt;strong&gt;jump box&lt;/strong&gt; describe what operators experience — you "jump" through it to get somewhere else. Cloud vendors' managed offerings are the same idea again, productized.&lt;/p&gt;

&lt;p&gt;There are shades of usage — "bastion" tends to imply deliberate hardening, "jump host" sometimes just means "the box I hop through" — but in practice the terms are interchangeable. If a machine's job is to be the one exposed SSH door in front of private infrastructure, it's a jump host, whatever the runbook calls it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why funnel everyone through one machine
&lt;/h2&gt;

&lt;p&gt;The logic is concentration. Exposing twenty servers to the internet means twenty attack surfaces to patch, twenty sshd configs to keep tight, twenty places to watch for brute-force noise. Put a jump host in front and the arithmetic changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One machine to harden.&lt;/strong&gt; Minimal packages, current patches, strict sshd config — affordable when it's one box instead of a fleet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One place for strong authentication.&lt;/strong&gt; Enforce keys-only, MFA, or short-lived certificates at the door, once, instead of wiring it into every internal server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One choke point to audit.&lt;/strong&gt; Every administrative session crosses the same threshold, so "who connected, when, from where" has a single authoritative answer — more on that below.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A smaller blast radius.&lt;/strong&gt; Internal machines can drop their public exposure entirely and accept SSH only from the jump host's address.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The trade is obvious and accepted: the jump host becomes the most attractive target on your network. The entire design rests on it holding &lt;em&gt;access&lt;/em&gt; without holding &lt;em&gt;secrets&lt;/em&gt; — which is exactly the property careless usage destroys.&lt;/p&gt;

&lt;h2&gt;
  
  
  How traffic actually flows through one
&lt;/h2&gt;

&lt;p&gt;The modern mechanism is OpenSSH's &lt;code&gt;ProxyJump&lt;/code&gt; (&lt;code&gt;ssh -J&lt;/code&gt;), and the detail that matters is &lt;em&gt;where the second connection terminates&lt;/em&gt;. Your client connects to the jump host, authenticates, and then uses that connection purely as a transport — inside it, your client opens a &lt;strong&gt;second, end-to-end encrypted SSH connection directly to the internal machine&lt;/strong&gt;. Authentication to the target happens from your laptop; the session's encryption is negotiated between your laptop and the target.&lt;/p&gt;

&lt;p&gt;The jump host shuttles ciphertext. It never sees your private key, never holds your session's plaintext, and cannot read what you type on the inner connection. Even if the jump host were fully compromised, the attacker could observe that a connection passed through — not what happened inside it.&lt;/p&gt;

&lt;p&gt;Contrast the old pattern this replaced: SSH to the jump host, land in a shell &lt;em&gt;on&lt;/em&gt; it, then run &lt;code&gt;ssh&lt;/code&gt; again from there to the target. In that world the jump host is a full participant — the inner session is decrypted and re-encrypted on the box, so anything on it can read your session, and you need credentials for the target available &lt;em&gt;on the jump host&lt;/em&gt; to make the second hop at all. That one requirement is the root of most jump-host security failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  The minimal setup
&lt;/h2&gt;

&lt;p&gt;A common misconception is that a jump host needs special software. It doesn't — it's a role, not a product. The bastion side needs an ordinary sshd and user accounts (hardened, patched, keys-only — but ordinary). All the routing intelligence lives client-side, in one line of &lt;a href="https://termal.in/blog/ssh-config-file-guide/" rel="noopener noreferrer"&gt;&lt;code&gt;~/.ssh/config&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ssh"&gt;&lt;code&gt;&lt;span class="k"&gt;Host&lt;/span&gt; bastion
    &lt;span class="k"&gt;HostName&lt;/span&gt; bastion.example.com
    &lt;span class="k"&gt;User&lt;/span&gt; jump

&lt;span class="k"&gt;Host&lt;/span&gt; db
    &lt;span class="k"&gt;HostName&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;.0.0.9
    &lt;span class="k"&gt;User&lt;/span&gt; deploy
    &lt;span class="k"&gt;ProxyJump&lt;/span&gt; bastion
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now &lt;code&gt;ssh db&lt;/code&gt; transparently routes through the bastion to a private address your laptop can't even reach directly. Chains (&lt;code&gt;ProxyJump hop1,hop2&lt;/code&gt;) and the full set of options are in the &lt;a href="https://termal.in/blog/ssh-proxyjump-bastion-host/" rel="noopener noreferrer"&gt;setup guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A minimal hardening checklist
&lt;/h2&gt;

&lt;p&gt;The bastion has to earn the trust the design places in it. None of this is exotic — the point of the pattern is that it only has to be done on &lt;em&gt;one&lt;/em&gt; machine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Keys only, no passwords.&lt;/strong&gt; &lt;code&gt;PasswordAuthentication no&lt;/code&gt; and &lt;code&gt;PermitRootLogin no&lt;/code&gt; in &lt;code&gt;sshd_config&lt;/code&gt;. An internet-facing sshd gets password-guessed around the clock; this ends that class of attack outright.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nothing on the box but the door.&lt;/strong&gt; No app code, no databases, no developer tooling — the fewer packages installed, the less to patch and the less an intruder finds to work with.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Patch on a schedule.&lt;/strong&gt; Unattended security updates for the OS, and a standing habit of keeping sshd itself current — this is the one host where "we'll update next quarter" isn't acceptable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Throttle the noise.&lt;/strong&gt; fail2ban, or sshd's own &lt;code&gt;MaxStartups&lt;/code&gt; limits, so brute-force attempts stay background static instead of becoming load.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log off the box.&lt;/strong&gt; Ship auth logs somewhere the bastion can't edit — a compromised gateway shouldn't get to rewrite its own history. Whether to go further and record sessions is its own question, covered below.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The two mistakes that undo the design
&lt;/h2&gt;

&lt;p&gt;Both come from the old land-in-a-shell habit, and both put your identity on the one machine most likely to be attacked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Storing private keys on the jump host.&lt;/strong&gt; If making the second hop requires a key sitting in &lt;code&gt;~/.ssh&lt;/code&gt; on the bastion, then everyone's keys accumulate on the single most exposed machine in the network — and a compromise of that one box yields credentials to everything behind it. The jump host was supposed to &lt;em&gt;contain&lt;/em&gt; a breach; a key collection turns it into the breach's distribution point. With ProxyJump there is simply no reason for any private key to exist there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent forwarding through it (&lt;code&gt;ssh -A&lt;/code&gt;).&lt;/strong&gt; Subtler, because the key itself stays home — but forwarding exposes your agent's socket on the jump host, and anyone with root there can use your identity for as long as you're connected. On a shared, internet-facing gateway, that's precisely the wrong place to offer it. The full argument is in &lt;a href="https://termal.in/blog/ssh-agent-forwarding-risks/" rel="noopener noreferrer"&gt;agent forwarding is convenient and dangerous&lt;/a&gt;; the short version is that ProxyJump makes forwarding unnecessary for the hop itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you don't need one
&lt;/h2&gt;

&lt;p&gt;A jump host earns its keep when there's a private network behind it and more than a couple of people in front of it. If you run a single VPS with a public address, adding a bastion adds a hop and a maintenance burden without meaningfully reducing exposure — tighten that one sshd instead. And modern mesh VPNs and overlay networks solve an overlapping problem by putting your laptop &lt;em&gt;on&lt;/em&gt; the private network, so every internal machine is reachable directly over an encrypted mesh. That can replace a jump host for connectivity — though teams often keep one anyway, precisely because a single audited SSH door is easier to reason about (and to show an auditor) than "every device can reach everything."&lt;/p&gt;

&lt;p&gt;The same honest comparison applies to zero-trust access products — identity-aware proxies that put SSO, device checks and per-request policy in front of every connection. They solve the jump host's problem with more machinery and stronger guarantees, and at some combination of headcount and compliance pressure that machinery pays for itself. Below that line, a jump host is the version of the same idea you can build in an afternoon with stock OpenSSH — no new vendor, no new agent on every laptop, and every property of it inspectable in two config files.&lt;/p&gt;

&lt;h2&gt;
  
  
  The audit dividend
&lt;/h2&gt;

&lt;p&gt;Concentration cuts both ways, and the second edge is useful: because every session crosses the jump host, it's the one place with a complete view of who came in, when, and from where. That makes the bastion the natural anchor for session auditing — and also the place where doing it wrong (shared accounts, keystroke capture, recordings stored on the same box) hurts most. The architectures and traps get a full treatment in &lt;a href="https://termal.in/blog/record-audit-ssh-sessions-bastion/" rel="noopener noreferrer"&gt;recording and auditing SSH sessions on a bastion&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Termalin fits
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://termal.in/features/" rel="noopener noreferrer"&gt;Termalin&lt;/a&gt; treats the jump host as part of a saved server's configuration: attach a bastion to a host, and every connection to that internal machine routes through it automatically — keys stay on your machine, nothing is copied to the gateway, no agent socket is exposed there, and each hop's identity is verified on first connect with a loud warning if a fingerprint ever changes. For the audit side, sessions can be recorded client-side — output only, never keystrokes — with an audit log of who connected where and when, which pairs naturally with whatever recording the bastion itself does. The Free tier has no host limits — &lt;a href="https://termal.in/download/" rel="noopener noreferrer"&gt;download it&lt;/a&gt; and put the private network one click away.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Termalin is a free, cross-platform SSH client with jump-host support, output-only session recording and a key manager — &lt;a href="https://termal.in/download/" rel="noopener noreferrer"&gt;download it&lt;/a&gt;, or see how it &lt;a href="https://termal.in/security/" rel="noopener noreferrer"&gt;handles your keys safely&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ssh</category>
      <category>bastion</category>
      <category>security</category>
      <category>networking</category>
    </item>
    <item>
      <title>ssh-agent and ssh-add, in practice: stop typing your passphrase</title>
      <dc:creator>Svyatoslav Pavlov</dc:creator>
      <pubDate>Sun, 13 Sep 2026 16:14:01 +0000</pubDate>
      <link>https://dev.to/wolfhound1995/ssh-agent-and-ssh-add-in-practice-stop-typing-your-passphrase-38lj</link>
      <guid>https://dev.to/wolfhound1995/ssh-agent-and-ssh-add-in-practice-stop-typing-your-passphrase-38lj</guid>
      <description>&lt;p&gt;You've done the right thing and put a passphrase on your SSH key — and now you're typing it for the fifth time before lunch. The fix has existed for decades: the SSH agent. Unlock the key once, and every connection after that authenticates silently. The catch is that "once" only works if the agent is actually running, your shell can find it, and the key is loaded — three separate things, each with its own way of failing. This is the practical guide: starting the agent on each OS, driving it with &lt;code&gt;ssh-add&lt;/code&gt;, making the whole thing automatic, and decoding the two errors that account for nearly all agent trouble.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the agent is, in one paragraph
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ssh-agent&lt;/code&gt; is a small background process that holds your &lt;strong&gt;decrypted&lt;/strong&gt; private keys in memory. When a server challenges you, &lt;code&gt;ssh&lt;/code&gt; hands the challenge to the agent, the agent signs it with the in-memory key, and only the &lt;em&gt;signature&lt;/em&gt; goes back — the key itself never leaves the agent, never touches the wire, and stays encrypted on disk. One passphrase prompt to load the key, then every later connection is served from memory. If you want the full mental model — why it's built as a signing service and what that implies — that's covered in &lt;a href="https://termal.in/blog/what-is-the-ssh-agent/" rel="noopener noreferrer"&gt;what is the SSH agent?&lt;/a&gt;; this post stays hands-on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting the agent, per OS
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Linux.&lt;/strong&gt; On a desktop login you almost certainly have an agent already — either a systemd user service or GNOME Keyring playing the role — with &lt;code&gt;SSH_AUTH_SOCK&lt;/code&gt; exported for your whole session. Test with &lt;code&gt;ssh-add -l&lt;/code&gt;. If nothing answers (common over a bare SSH login, in containers, or on minimal servers), start one by hand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ssh-agent &lt;span class="nt"&gt;-s&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;eval&lt;/code&gt; matters. &lt;code&gt;ssh-agent -s&lt;/code&gt; only &lt;em&gt;prints&lt;/em&gt; the &lt;code&gt;SSH_AUTH_SOCK&lt;/code&gt; and &lt;code&gt;SSH_AGENT_PID&lt;/code&gt; variables; the &lt;code&gt;eval&lt;/code&gt; is what sets them in your current shell so &lt;code&gt;ssh&lt;/code&gt; can find the socket. Running &lt;code&gt;ssh-agent&lt;/code&gt; bare and wondering why nothing changed is the single most common way to "start" an agent that no tool can see.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;macOS.&lt;/strong&gt; launchd runs an agent for you from login — there is nothing to start. The macOS-specific win is the Keychain integration below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Windows.&lt;/strong&gt; Modern Windows ships an &lt;strong&gt;OpenSSH Authentication Agent&lt;/strong&gt; service, but it's not running by default. Enable it once, from an elevated PowerShell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Set-Service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ssh-agent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-StartupType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Automatic&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Start-Service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ssh-agent&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, &lt;code&gt;ssh-add&lt;/code&gt; works in any terminal, and the agent survives reboots.&lt;/p&gt;

&lt;h2&gt;
  
  
  Driving it with ssh-add
&lt;/h2&gt;

&lt;p&gt;Load, inspect, and clear keys:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-add                          &lt;span class="c"&gt;# load the default keys (id_ed25519, id_rsa, …)&lt;/span&gt;
ssh-add ~/.ssh/work_ed25519      &lt;span class="c"&gt;# load a specific key&lt;/span&gt;
ssh-add &lt;span class="nt"&gt;-l&lt;/span&gt;                       &lt;span class="c"&gt;# list fingerprints of loaded keys&lt;/span&gt;
ssh-add &lt;span class="nt"&gt;-L&lt;/span&gt;                       &lt;span class="c"&gt;# print the full public keys&lt;/span&gt;
ssh-add &lt;span class="nt"&gt;-d&lt;/span&gt; ~/.ssh/work_ed25519   &lt;span class="c"&gt;# remove one key&lt;/span&gt;
ssh-add &lt;span class="nt"&gt;-D&lt;/span&gt;                       &lt;span class="c"&gt;# remove all keys&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ssh-add -l&lt;/code&gt; is the first diagnostic for any auth problem: if the key you expect isn't in that list, the agent can't sign with it, whatever else looks right.&lt;/p&gt;

&lt;p&gt;Two flags are worth making habits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;-t&lt;/code&gt; sets a lifetime.&lt;/strong&gt; &lt;code&gt;ssh-add -t 3600 ~/.ssh/id_ed25519&lt;/code&gt; loads the key for one hour; after that the agent forgets it and the next use re-prompts. A key that sits unlocked in memory for a three-week uptime is a key usable long after you stopped thinking about it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;-c&lt;/code&gt; requires confirmation.&lt;/strong&gt; Every signing request pops a local prompt before the agent answers. Slightly annoying, exactly right for high-value keys.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Make it automatic: AddKeysToAgent
&lt;/h2&gt;

&lt;p&gt;The manual &lt;code&gt;ssh-add&lt;/code&gt; ritual disappears with one line in &lt;a href="https://termal.in/blog/ssh-config-file-guide/" rel="noopener noreferrer"&gt;&lt;code&gt;~/.ssh/config&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ssh"&gt;&lt;code&gt;&lt;span class="k"&gt;Host&lt;/span&gt; *
    &lt;span class="k"&gt;AddKeysToAgent&lt;/span&gt; &lt;span class="no"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the first &lt;code&gt;ssh&lt;/code&gt; that needs a key prompts for the passphrase &lt;em&gt;once&lt;/em&gt; and loads the key into the agent as a side effect. No separate command to remember — the prompt simply stops recurring. You can set it to a lifetime too (&lt;code&gt;AddKeysToAgent 1h&lt;/code&gt;) to get the &lt;code&gt;-t&lt;/code&gt; behaviour without the flag.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;macOS: survive reboots with the Keychain.&lt;/strong&gt; By default the agent forgets keys when it restarts. macOS can store the passphrase in your login keychain so keys reload without retyping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-add &lt;span class="nt"&gt;--apple-use-keychain&lt;/span&gt; ~/.ssh/id_ed25519
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and in &lt;code&gt;~/.ssh/config&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ssh"&gt;&lt;code&gt;&lt;span class="k"&gt;Host&lt;/span&gt; *
    &lt;span class="k"&gt;AddKeysToAgent&lt;/span&gt; &lt;span class="no"&gt;yes&lt;/span&gt;
    &lt;span class="k"&gt;UseKeychain&lt;/span&gt; &lt;span class="no"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, the passphrase question is answered by the Keychain at login and you never see it again — while the key file on disk stays encrypted.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two errors everyone hits
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Could not open a connection to your authentication agent&lt;/code&gt;&lt;/strong&gt; — &lt;code&gt;ssh-add&lt;/code&gt; can't find an agent. Either none is running, or one is running but this shell doesn't have &lt;code&gt;SSH_AUTH_SOCK&lt;/code&gt; set. The fix is the per-OS start above: the &lt;code&gt;eval "$(ssh-agent -s)"&lt;/code&gt; line on Linux (check first whether your desktop session already provides one — starting a second agent per terminal just scatters loaded keys across processes), the service start on Windows. You'll also meet this in cron jobs and CI, where no login session ever exported the variable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;The agent has no identities.&lt;/code&gt;&lt;/strong&gt; — the opposite problem: the agent is fine, but nothing is loaded. &lt;code&gt;ssh-add&lt;/code&gt; your key. If it &lt;em&gt;was&lt;/em&gt; loaded and vanished, a &lt;code&gt;-t&lt;/code&gt; lifetime expired or the agent restarted — which is precisely what &lt;code&gt;AddKeysToAgent yes&lt;/code&gt; and the macOS Keychain are for.&lt;/p&gt;

&lt;p&gt;And the third case: the agent looks perfect — &lt;code&gt;ssh-add -l&lt;/code&gt; shows the key — but the server still says &lt;code&gt;Permission denied (publickey)&lt;/code&gt;. That's no longer an agent problem; work through &lt;a href="https://termal.in/blog/ssh-permission-denied-publickey/" rel="noopener noreferrer"&gt;the publickey checklist&lt;/a&gt; — wrong user, wrong key offered, &lt;code&gt;authorized_keys&lt;/code&gt; permissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  An unlocked agent is a live credential
&lt;/h2&gt;

&lt;p&gt;The agent never leaks the key — but it signs for &lt;strong&gt;whoever can reach its socket&lt;/strong&gt;, and it doesn't ask who's asking. On your own machine that means any process running as your user can request signatures: that's the deal you accept for the convenience, and it's why agent hygiene is worth the small effort.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Give keys a lifetime&lt;/strong&gt; (&lt;code&gt;-t&lt;/code&gt;, or a lifetime on &lt;code&gt;AddKeysToAgent&lt;/code&gt;) so an idle agent goes cold on its own.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lock the agent when stepping away&lt;/strong&gt;: &lt;code&gt;ssh-add -x&lt;/code&gt; locks it behind a password, &lt;code&gt;ssh-add -X&lt;/code&gt; unlocks; &lt;code&gt;ssh-add -D&lt;/code&gt; simply drops everything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use &lt;code&gt;-c&lt;/code&gt;&lt;/strong&gt; for the keys that reach production.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't forward the agent by default.&lt;/strong&gt; &lt;code&gt;ssh -A&lt;/code&gt; exposes live use of your keys on the remote host — anyone with root there can sign as you for the length of your session. That one deserves its own read: &lt;a href="https://termal.in/blog/ssh-agent-forwarding-risks/" rel="noopener noreferrer"&gt;agent forwarding is convenient and dangerous&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How Termalin handles this
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://termal.in/features/" rel="noopener noreferrer"&gt;Termalin&lt;/a&gt; builds this whole workflow in, with the sharp edges filed down. Its key agent unlocks your keys &lt;strong&gt;once&lt;/strong&gt; and signs on your behalf — no OS service to enable, no &lt;code&gt;eval&lt;/code&gt; line, no per-shell socket to lose. Passphrase caching comes &lt;strong&gt;with a re-lock timeout by default&lt;/strong&gt;, which is the &lt;code&gt;ssh-add -t&lt;/code&gt; habit enforced for you rather than remembered by you. And because it speaks the &lt;strong&gt;standard ssh-agent protocol&lt;/strong&gt;, &lt;code&gt;git&lt;/code&gt; and your existing CLI tools sign through it unchanged — it replaces the ritual, not your toolchain.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Termalin is a free, cross-platform SSH client whose built-in agent unlocks your keys once and re-locks on a timer — &lt;a href="https://termal.in/download/" rel="noopener noreferrer"&gt;download it&lt;/a&gt;, or read how it &lt;a href="https://termal.in/security/" rel="noopener noreferrer"&gt;handles your keys safely&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ssh</category>
      <category>sshagent</category>
      <category>sshkeys</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Best SSH Clients for Linux in 2026</title>
      <dc:creator>Svyatoslav Pavlov</dc:creator>
      <pubDate>Sat, 12 Sep 2026 15:12:58 +0000</pubDate>
      <link>https://dev.to/wolfhound1995/best-ssh-clients-for-linux-in-2026-11pn</link>
      <guid>https://dev.to/wolfhound1995/best-ssh-clients-for-linux-in-2026-11pn</guid>
      <description>&lt;p&gt;Let's not pretend: on Linux, the best SSH client is the one already installed. OpenSSH plus a terminal emulator you like plus tmux is the default answer, it's the right answer for most people, and any roundup that buries it below seven GUI apps is selling something. We &lt;em&gt;are&lt;/em&gt; selling something (&lt;strong&gt;we build Termalin&lt;/strong&gt;, last entry), which is exactly why this post starts with the free setup that makes most paid clients unnecessary, and only then covers the cases where a dedicated client earns its keep: big host inventories, files-next-to-terminal workflows, sync across machines, and mixed-protocol fleets.&lt;/p&gt;

&lt;p&gt;Prices as of September 2026, checked against official sites.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OpenSSH + tmux&lt;/td&gt;
&lt;td&gt;Free, preinstalled&lt;/td&gt;
&lt;td&gt;The default answer, and usually the right one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tabby&lt;/td&gt;
&lt;td&gt;Free, open source (MIT)&lt;/td&gt;
&lt;td&gt;Open-source GUI with a host manager&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Termius&lt;/td&gt;
&lt;td&gt;Free local / Pro $10/mo (annual)&lt;/td&gt;
&lt;td&gt;Sync to native mobile apps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Remmina&lt;/td&gt;
&lt;td&gt;Free, open source&lt;/td&gt;
&lt;td&gt;RDP/VNC alongside SSH, GNOME-friendly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WindTerm&lt;/td&gt;
&lt;td&gt;Free, source published (Apache-2.0)&lt;/td&gt;
&lt;td&gt;Raw speed, minimal resource use&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Asbru-CM&lt;/td&gt;
&lt;td&gt;Free, open source&lt;/td&gt;
&lt;td&gt;Cluster commands, big host trees&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;XPipe&lt;/td&gt;
&lt;td&gt;Free / Homelab $5/mo / Pro $10/mo&lt;/td&gt;
&lt;td&gt;Docker, K8s, VM and WSL discovery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Termalin&lt;/td&gt;
&lt;td&gt;Free (no host limits) / Pro $8/mo yearly&lt;/td&gt;
&lt;td&gt;Integrated SFTP + E2E sync + AI agents&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  1. OpenSSH + tmux (+ your terminal of choice)
&lt;/h2&gt;

&lt;p&gt;The floor, and for many people the ceiling. A maintained &lt;code&gt;~/.ssh/config&lt;/code&gt; gives you host aliases, &lt;code&gt;ProxyJump&lt;/code&gt; chains, per-host keys and connection multiplexing. &lt;code&gt;ssh-agent&lt;/code&gt; means one passphrase a day. tmux keeps sessions alive through dropped Wi-Fi and gives you panes, and whichever emulator your distro ships (or Ghostty, Kitty, Alacritty if you care about rendering speed) is the glass on top. Total cost: zero, forever, with no vendor in the loop. What this stack genuinely lacks: a visual inventory once you pass a few dozen hosts, any SFTP UI beyond &lt;code&gt;sftp&lt;/code&gt;'s readline, and sync that isn't "dotfiles repo you forget to push." If none of those bite you, close this tab; that's our first recommendation against ourselves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; most Linux users, most of the time.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Tabby
&lt;/h2&gt;

&lt;p&gt;The strongest open-source GUI client on Linux: MIT license, tabs and splits, a proper saved-connection manager with credentials, serial support, and plugins. It's Electron, so memory use is an order of magnitude above a native emulator, and there's no first-party sync (config files or community plugins fill in). If you want a Termius-shaped app whose source you can read, this is the pick, and if closed source rules Termalin out for you, Tabby is what we'd hand you. Longer comparison on our &lt;a href="https://termal.in/tabby-alternative/" rel="noopener noreferrer"&gt;Tabby page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; an open-source host manager with a modern UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Termius
&lt;/h2&gt;

&lt;p&gt;Termius ships a real Linux desktop app, which most commercial clients never bothered to do, and it's the only entry here whose hosts follow you to genuinely good native iOS and Android apps. The free plan is local-only on one device; cross-device sync needs Pro at &lt;strong&gt;$10/month billed annually&lt;/strong&gt;, through Termius's cloud only. On a Linux desktop specifically, you're mostly paying for the mobile half of the ecosystem, so be sure you'll use it. The alternatives are mapped in our &lt;a href="https://termal.in/termius-alternative/" rel="noopener noreferrer"&gt;Termius comparison&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; the desk-to-phone workflow, if the phone half is real for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Remmina
&lt;/h2&gt;

&lt;p&gt;The GNOME-world staple, free and GPL. Remmina is a remote &lt;em&gt;desktop&lt;/em&gt; client first (RDP, VNC, SPICE) that also does SSH and SFTP, with a tidy profile list and system-tray quick connect. Its SSH terminal is basic and its SFTP browser is functional rather than pleasant, but if your week includes RDPing into Windows servers from a Linux workstation, having every protocol behind one profile list is worth more than a fancier terminal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; mixed RDP/VNC/SSH work on a Linux desktop.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. WindTerm
&lt;/h2&gt;

&lt;p&gt;Free for commercial use with source published under Apache-2.0, and the fastest full-featured client in this list: startup, scrolling and rendering all feel native because they are. Sessions, SFTP and a shell-aware autocompleting input line are built in. Know before adopting: the sole maintainer has said openly that development pace has dropped to a fraction of its peak, so buy the current version, not the roadmap. On Linux it's an AppImage-style unpack rather than a distro package, which your package manager won't love.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; speed and low resource use with sessions and SFTP included.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Asbru-CM
&lt;/h2&gt;

&lt;p&gt;The old-school Linux connection manager, alive and well: GPL, Perl/GTK, actively maintained in 2026. Its superpower is scale and automation: deep folder trees of hundreds of hosts, scripted expect-style logins, and &lt;strong&gt;cluster mode&lt;/strong&gt;, where one keyboard drives many sessions at once, which is the feature that keeps it installed in ops teams. The UI is unapologetically functional and the learning curve is real.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; big host trees and typing into twenty servers at once, for free.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. XPipe
&lt;/h2&gt;

&lt;p&gt;Different category, same problem space: XPipe doesn't render a terminal, it discovers and organizes everything shell-shaped (SSH hosts, Docker containers, Kubernetes pods, VMs, Podman, WSL) and opens each one in the terminal and editor you already use. It's built by Linux-first people and it shows. Community tier is free with unlimited connections; Homelab is $5/month, Professional $10/month or $80/year. Pair it with the stock OpenSSH setup from entry one and you've solved inventory without changing your terminal. Comparison &lt;a href="https://termal.in/xpipe-alternative/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; container-heavy and VM-heavy infrastructure on top of your existing terminal.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Termalin
&lt;/h2&gt;

&lt;p&gt;Ours, judged last and hardest. It's a native desktop app for Linux (plus Windows and macOS) with browser-style tabs, an SFTP panel and built-in editor next to the shell, snippets, live server stats in the status bar, and output-only session recording with replay. Sync is end-to-end encrypted, so the server only ever holds ciphertext, at &lt;strong&gt;$8/month billed yearly&lt;/strong&gt;; the free tier is the full desktop app with &lt;strong&gt;no host limits&lt;/strong&gt;, and the 14-day Pro trial needs no card. The unusual part: a built-in MCP server that gives AI agents like Claude Code supervised access to your hosts through a key custodian (the agent never sees a key) with every agent session mirrored in a live watch grid.&lt;/p&gt;

&lt;p&gt;Against ourselves, plainly: Termalin is young, &lt;strong&gt;not open source&lt;/strong&gt; (a real minus on this platform in particular), and current beta builds are unsigned. If open source is a requirement, take Tabby or Asbru-CM from this list and don't look back. If you want one window where terminal, files, encrypted sync and agent access live together, that's the case we built for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; integrated terminal + SFTP + E2E sync, or letting an AI agent work on servers under your eyes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;Start with OpenSSH + tmux; it's the default answer for a reason. Add XPipe if your problem is inventory, Remmina if your problem is RDP, Asbru-CM if your problem is scale, Tabby if you want an open-source GUI, Termius if your phone is a work tool, WindTerm if you want speed with batteries included. And if you want the integrated-with-encrypted-sync setup, or AI agents on your hosts, try ours. The platform guide lives at &lt;a href="https://termal.in/ssh-client-for-linux/" rel="noopener noreferrer"&gt;SSH client for Linux&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: this blog is written by the Termalin team. Termalin is free with no host limits on Linux, Windows and macOS — &lt;a href="https://termal.in/download/" rel="noopener noreferrer"&gt;download it&lt;/a&gt; and hold it to the standard above.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ssh</category>
      <category>linux</category>
      <category>comparison</category>
      <category>tools</category>
    </item>
    <item>
      <title>Best SSH Clients for macOS in 2026</title>
      <dc:creator>Svyatoslav Pavlov</dc:creator>
      <pubDate>Fri, 11 Sep 2026 15:53:44 +0000</pubDate>
      <link>https://dev.to/wolfhound1995/best-ssh-clients-for-macos-in-2026-5ek3</link>
      <guid>https://dev.to/wolfhound1995/best-ssh-clients-for-macos-in-2026-5ek3</guid>
      <description>&lt;p&gt;The Mac starts from a better place than Windows: OpenSSH is preinstalled, Terminal.app is decent, and half the "SSH client" category is really about what you add on top. So this roundup is honest about the fact that many Mac users need nothing at all, and then covers what earns its disk space when you outgrow that. One entry is ours: &lt;strong&gt;we build Termalin&lt;/strong&gt;, and this post lives on its blog, so weigh entry nine accordingly.&lt;/p&gt;

&lt;p&gt;Prices as of September 2026, checked against official vendor pages.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Terminal.app / iTerm2 + OpenSSH&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;The default answer; iTerm2 for power features&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Warp&lt;/td&gt;
&lt;td&gt;Free (limited AI credits) / Build $20/mo&lt;/td&gt;
&lt;td&gt;AI assistance inside the terminal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ghostty&lt;/td&gt;
&lt;td&gt;Free, open source&lt;/td&gt;
&lt;td&gt;Fast, native, minimalist terminal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Termius&lt;/td&gt;
&lt;td&gt;Free local / Pro $10/mo (annual)&lt;/td&gt;
&lt;td&gt;Sync across Mac, iPhone, iPad&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tabby&lt;/td&gt;
&lt;td&gt;Free, open source (MIT)&lt;/td&gt;
&lt;td&gt;Open-source tabs + SSH manager&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Royal TSX&lt;/td&gt;
&lt;td&gt;Free lite mode / €49 perpetual&lt;/td&gt;
&lt;td&gt;Mixed RDP + VNC + SSH estates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SecureCRT&lt;/td&gt;
&lt;td&gt;$119 perpetual (1 yr updates)&lt;/td&gt;
&lt;td&gt;Enterprise emulation and scripting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Core Shell&lt;/td&gt;
&lt;td&gt;Free / paid Premium (from $9.99)&lt;/td&gt;
&lt;td&gt;Native Mac feel, Keychain integration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Termalin&lt;/td&gt;
&lt;td&gt;Free (no host limits) / Pro $8/mo yearly&lt;/td&gt;
&lt;td&gt;Integrated SFTP + E2E sync + AI agents&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  1. Terminal.app or iTerm2, plus plain OpenSSH
&lt;/h2&gt;

&lt;p&gt;The correct first answer, and the first recommendation against our own product in this post. macOS ships a full OpenSSH client; a tidy &lt;code&gt;~/.ssh/config&lt;/code&gt; with aliases and &lt;code&gt;ProxyJump&lt;/code&gt; entries, plus the built-in Keychain integration for key passphrases (&lt;code&gt;UseKeychain yes&lt;/code&gt;), covers a shocking amount of real work. iTerm2 (free, open source) adds the power features Terminal.app lacks: real split panes, triggers, instant replay, tmux integration, shell integration marks. If your host list fits in your head, stop here and spend nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; everyone as a baseline; iTerm2 for daily heavy use.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Warp
&lt;/h2&gt;

&lt;p&gt;Warp treats the terminal as an AI product: output grouped into blocks, an agent that proposes and runs commands, shared workflows for teams. It's genuinely pleasant, and the Mac build is its most mature. The free tier includes a small monthly allowance of AI credits; the Build plan is &lt;strong&gt;$20/month&lt;/strong&gt;. As an SSH client specifically it's serviceable rather than central: there's no real host inventory, SFTP UI, or session recording, because that's not the product. Choose it for the AI, not for server management; the fuller comparison is on our &lt;a href="https://termal.in/warp-alternative/" rel="noopener noreferrer"&gt;Warp page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; developers who want an AI pair inside every shell session.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Ghostty
&lt;/h2&gt;

&lt;p&gt;The terminal purist's current favorite: native macOS app (real tabs, native windowing), extremely fast, open source, free. It supports macOS and Linux only, with no official Windows build. It is deliberately just a terminal emulator: no host manager, no SFTP, no sync. Pair it with &lt;code&gt;~/.ssh/config&lt;/code&gt; and tmux and it's a beautiful minimal setup; pair it with expectations of a Termius-style app and you'll be confused about what it's for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; people who want the best possible glass to run plain &lt;code&gt;ssh&lt;/code&gt; through.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Termius
&lt;/h2&gt;

&lt;p&gt;The strongest reason to pick Termius on a Mac is that your servers follow you to an iPhone and iPad with &lt;strong&gt;native apps that are still best in class&lt;/strong&gt;. The desktop app is clean and the vault model is coherent. The costs: the free plan is local-only on one device, and cross-device sync requires Pro at &lt;strong&gt;$10/month billed annually&lt;/strong&gt;, cloud-hosted by Termius with no self-host option. Desk-only users end up subsidizing mobile they don't use; that math is the subject of our &lt;a href="https://termal.in/termius-alternative/" rel="noopener noreferrer"&gt;Termius comparison&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; managing servers from Apple devices in your pocket, not just on your desk.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Tabby
&lt;/h2&gt;

&lt;p&gt;The open-source pick if you want an actual SSH manager rather than a bare terminal: connection tree, saved credentials, serial support, split panes, plugins, all MIT-licensed and free. On a Mac its Electron nature stands out more than on Windows; it feels heavier than iTerm2 or Ghostty and takes longer to launch. No first-party sync. If Termalin's closed source rules us out for you, this is what we'd suggest. Details on our &lt;a href="https://termal.in/tabby-alternative/" rel="noopener noreferrer"&gt;Tabby page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; open-source policy, plugin tinkering, serial consoles.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Royal TSX
&lt;/h2&gt;

&lt;p&gt;A connection manager first: one window organizing SSH, RDP, VNC, web panels and file transfers across hundreds of entries, with strong credential management. The free shareware mode handles small environments; a personal license is &lt;strong&gt;€49, perpetual&lt;/strong&gt;, with a year of maintenance included. The terminal itself is unremarkable, and the app's breadth means complexity everywhere. But for consultants juggling many clients' mixed infrastructure on a Mac, it has no direct rival. Our head-to-head is &lt;a href="https://termal.in/royal-ts-alternative/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; mixed RDP/VNC/SSH estates and MSP-style work.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. SecureCRT
&lt;/h2&gt;

&lt;p&gt;The enterprise veteran runs natively on macOS, and one &lt;strong&gt;$119 perpetual license&lt;/strong&gt; (with a year of updates) covers Windows, macOS and Linux installs. You buy it for rigor: precise terminal emulation, deep session options, Python scripting, serial and legacy protocols. You tolerate its interface, which has changed little in a decade. Procurement departments love the licensing; designers do not love the rest. Comparison on our &lt;a href="https://termal.in/securecrt-alternative/" rel="noopener noreferrer"&gt;SecureCRT page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; network engineers and orgs that require perpetual licenses.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Core Shell
&lt;/h2&gt;

&lt;p&gt;The most Mac-assimilated SSH app here: sandboxed, Keychain-integrated, with per-host themes and automatic reconnect. The free tier includes unlimited hosts; a paid Premium unlock (in-app, &lt;strong&gt;from $9.99&lt;/strong&gt;) adds sync via iCloud, import/export and scripting. macOS 12+ only, and only macOS, which is the point. It's a lovely small tool with a small tool's scope: no SFTP browser, no recording, no cross-platform story.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Mac-only users who want SSH to feel like a first-party Apple app.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Termalin
&lt;/h2&gt;

&lt;p&gt;Ours. What it adds over the stock setup: browser-style tabs across live sessions, an SFTP panel with a built-in editor beside the shell, snippets, live server stats in the status bar, output-only session recording with replay, and end-to-end encrypted sync at &lt;strong&gt;$8/month billed yearly&lt;/strong&gt; (free tier is the full app, local, &lt;strong&gt;no host limits&lt;/strong&gt;, and there's a 14-day Pro trial without a card). The differentiator is the agent layer: a built-in MCP server lets Claude Code or another AI agent work on your hosts through a key custodian, so no private key ever reaches the agent, with every agent session mirrored in a live watch grid.&lt;/p&gt;

&lt;p&gt;Against ourselves: Termalin is young, &lt;strong&gt;not open source&lt;/strong&gt;, and current beta builds are unsigned, so Gatekeeper needs a right-click-open on first launch. If you want a pure native-Mac feel, Core Shell and Ghostty are more polished as &lt;em&gt;Mac apps&lt;/em&gt; than we are today.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; one integrated window for terminal + files + sync, or supervised AI-agent access to servers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming from MobaXterm? Read this first
&lt;/h2&gt;

&lt;p&gt;A steady stream of people search "MobaXterm for Mac" after switching machines. The short answer: &lt;strong&gt;MobaXterm is Windows-only&lt;/strong&gt; and there is no Mac version, so you're replacing a bundle, not an app. Its two headline features map like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;X11 forwarding:&lt;/strong&gt; macOS has no built-in X server. Install the free XQuartz, and &lt;code&gt;ssh -X&lt;/code&gt; works from any client on this page, including Terminal.app. That's the whole migration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The automatic SFTP sidebar:&lt;/strong&gt; this is the part people actually miss. Cyberduck (free) as a separate window is the classic answer; if you want the MobaXterm-style files-next-to-terminal layout in one app, that's precisely what Termalin's SFTP panel does, and our &lt;a href="https://termal.in/mobaxterm-alternative/" rel="noopener noreferrer"&gt;MobaXterm comparison&lt;/a&gt; walks through the rest of the mapping.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;Nothing wrong with free: iTerm2 + OpenSSH is a complete answer, Ghostty if you want it faster and prettier. Apple-native small tool: Core Shell. Mobile-first: Termius. Mixed protocols: Royal TSX. Perpetual enterprise: SecureCRT. Open source with a host manager: Tabby. Integrated terminal + SFTP + encrypted sync, or AI agents: us. The platform-level guide lives at &lt;a href="https://termal.in/ssh-client-for-macos/" rel="noopener noreferrer"&gt;SSH client for macOS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: this blog is written by the Termalin team. Free, no host limits, runs on macOS, Windows and Linux — &lt;a href="https://termal.in/download/" rel="noopener noreferrer"&gt;download it&lt;/a&gt; and see if it beats your current setup.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ssh</category>
      <category>macos</category>
      <category>comparison</category>
      <category>tools</category>
    </item>
    <item>
      <title>Best SSH Clients for Windows in 2026: 9 Tested</title>
      <dc:creator>Svyatoslav Pavlov</dc:creator>
      <pubDate>Thu, 10 Sep 2026 17:22:36 +0000</pubDate>
      <link>https://dev.to/wolfhound1995/best-ssh-clients-for-windows-in-2026-9-tested-3oik</link>
      <guid>https://dev.to/wolfhound1995/best-ssh-clients-for-windows-in-2026-9-tested-3oik</guid>
      <description>&lt;p&gt;Windows is the platform where SSH client choice actually matters. On a Mac or a Linux box you can live in the stock terminal forever; on Windows the gap between "PuTTY and a pile of windows" and a good integrated client is measured in hours per week. We run all of these on real Windows 11 machines, and one of them is ours: &lt;strong&gt;we build Termalin&lt;/strong&gt;, so read entry nine knowing that, and note that we recommend against ourselves twice below.&lt;/p&gt;

&lt;p&gt;Prices as of September 2026, checked against official sites.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PuTTY / KiTTY&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Minimal, portable, runs anywhere&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows Terminal + OpenSSH&lt;/td&gt;
&lt;td&gt;Free (in the box)&lt;/td&gt;
&lt;td&gt;The no-install baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MobaXterm&lt;/td&gt;
&lt;td&gt;Home free / Pro $69 per user/year&lt;/td&gt;
&lt;td&gt;X11 forwarding, bundled admin tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bitvise&lt;/td&gt;
&lt;td&gt;Free (all use, incl. commercial)&lt;/td&gt;
&lt;td&gt;Best-in-class SFTP and tunneling GUI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mRemoteNG&lt;/td&gt;
&lt;td&gt;Free, open source&lt;/td&gt;
&lt;td&gt;Mixed RDP + SSH fleets in one window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WindTerm&lt;/td&gt;
&lt;td&gt;Free, source published (Apache-2.0)&lt;/td&gt;
&lt;td&gt;Fastest rendering, low-spec hardware&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tabby&lt;/td&gt;
&lt;td&gt;Free, open source (MIT)&lt;/td&gt;
&lt;td&gt;Open-source tabs, plugins, serial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Termius&lt;/td&gt;
&lt;td&gt;Free local / Pro $10/mo (annual)&lt;/td&gt;
&lt;td&gt;Cross-device sync incl. native mobile apps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Termalin&lt;/td&gt;
&lt;td&gt;Free (no host limits) / Pro $8/mo yearly&lt;/td&gt;
&lt;td&gt;Integrated SFTP + E2E sync + AI-agent access&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All of these are Windows-native; every price was taken from the vendor's own page.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. PuTTY / KiTTY
&lt;/h2&gt;

&lt;p&gt;Still the reflex answer, still fine. PuTTY is tiny, stable, and auditable; it does SSH, Telnet and serial, and nothing else. No tabs, no file browser, no sync: you add WinSCP for files and Pageant for keys, and your "client" is really a toolkit of four programs. KiTTY is the fork that patches the daily irritations (session filters, portability, stored passwords) at the cost of tracking upstream with a delay. If you've built decades of muscle memory here and want to keep it, nobody should talk you out of it; if the multi-window juggling is the problem, that's the itch our &lt;a href="https://termal.in/putty-alternative/" rel="noopener noreferrer"&gt;PuTTY comparison&lt;/a&gt; addresses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; locked-down machines, USB-stick portability, one-job simplicity.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Windows Terminal + built-in OpenSSH
&lt;/h2&gt;

&lt;p&gt;The baseline people forget exists. Windows 10 and 11 ship a real OpenSSH client; Windows Terminal gives it tabs, panes, and profiles that can launch &lt;code&gt;ssh myserver&lt;/code&gt; directly. Add a &lt;code&gt;~/.ssh/config&lt;/code&gt; and the native &lt;code&gt;ssh-agent&lt;/code&gt; service and you have a genuinely good setup for &lt;strong&gt;zero dollars and zero installs&lt;/strong&gt;, which matters on corporate machines where you can't install anything. What's missing: any visual host inventory, SFTP UI, or sync. It's the Windows equivalent of the Linux answer, and it's the second recommendation against ourselves in this post: if your needs stop at "a few servers, keys, tabs," start here and spend nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; admins on restricted machines, and anyone's first month of SSH.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. MobaXterm
&lt;/h2&gt;

&lt;p&gt;The Swiss-army option. One window contains sessions, an automatic SFTP sidebar for every SSH connection, an &lt;strong&gt;embedded X server&lt;/strong&gt; (still the killer feature: &lt;code&gt;ssh -X&lt;/code&gt; just works), plus bundled tools from port scanners to a local shell with Unix commands. Home Edition is free with real caps: 12 sessions, 2 tunnels. Professional is &lt;strong&gt;$69 per user/year&lt;/strong&gt;. The interface is an acquired taste, dense in a very 2010 way, and there's no sync between machines. But nothing else on Windows matches the X11-plus-SFTP combo, which is why it keeps its seat. Full rundown on our &lt;a href="https://termal.in/mobaxterm-alternative/" rel="noopener noreferrer"&gt;MobaXterm page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; sysadmins who need remote GUI apps and love a toolbox.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Bitvise
&lt;/h2&gt;

&lt;p&gt;Quietly excellent and genuinely &lt;strong&gt;free for all use, including commercial&lt;/strong&gt; (paid licenses exist, but they buy support commitments, not features). The terminal is plain, but that's not why you run Bitvise: its graphical SFTP client is fast and robust, its port-forwarding UI is the clearest we've used anywhere, and single-click remote desktop over an SSH tunnel is built in. Windows only, closed source, and visually stuck in the Windows 7 era. We compare it with Termalin &lt;a href="https://termal.in/bitvise-alternative/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; heavy SFTP transfers and tunneling without memorizing &lt;code&gt;-L&lt;/code&gt;/&lt;code&gt;-R&lt;/code&gt; syntax.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. mRemoteNG
&lt;/h2&gt;

&lt;p&gt;A free, open-source connection manager that puts &lt;strong&gt;RDP, SSH, VNC, Telnet and more in one tabbed window&lt;/strong&gt;, with a folder tree for hundreds of connections. The SSH side is actually embedded PuTTY, so terminal quality is exactly PuTTY's. Development moves slowly, with long gaps between stable releases, and the config file holding your credentials deserves careful handling. But if your day is 60% RDP into Windows servers and 40% SSH into Linux ones, nothing free does the mixed-fleet job better.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Windows admins with mixed RDP + SSH estates.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. WindTerm
&lt;/h2&gt;

&lt;p&gt;The performance champion: startup, rendering and scrolling are noticeably quicker than anything else here, and it's free for commercial use with source published under Apache-2.0. Sessions, SFTP, and a clever shell-aware autocompletion are all in the box. One thing to weigh: the maintainer has publicly said development time has dropped to a fraction of what it was, so treat the current version as the product. It's a very good current version.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; speed above all, older laptops, terminal purists who still want sessions.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Tabby
&lt;/h2&gt;

&lt;p&gt;The open-source modern option: MIT-licensed, tabs and split panes, a real SSH connection manager, serial support, and a plugin ecosystem. Being an Electron app, it eats more RAM than anything else in this list, and startup is leisurely. No first-party sync; config-file sync is DIY. If "not open source" disqualifies Termalin for you, and it's a fair disqualifier, Tabby is what we'd point you to on Windows. Longer comparison &lt;a href="https://termal.in/tabby-alternative/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; open-source policy shops and plugin tinkerers.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Termius
&lt;/h2&gt;

&lt;p&gt;The polished cross-platform incumbent. The Windows app is clean, and the real product is the ecosystem: hosts, keys and snippets syncing across desktop and &lt;strong&gt;genuinely good iOS/Android apps&lt;/strong&gt;. The free plan is local-only on one device; sync requires Pro at &lt;strong&gt;$10/month billed annually&lt;/strong&gt;, and sync runs only through Termius's cloud. If mobile is central to your workflow, it earns the money. If you only ever SSH from your desk, you're paying the mobile tax without using it; the alternatives are cataloged in our &lt;a href="https://termal.in/termius-alternative/" rel="noopener noreferrer"&gt;Termius comparison&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; people who manage servers from a phone as often as a desk.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Termalin
&lt;/h2&gt;

&lt;p&gt;Ours, so hold it to the harshest standard. What it is: a native-feeling client for Windows, macOS and Linux with browser-style tabs, an SFTP panel with a built-in editor next to the shell, live server stats in the status bar, snippets, and output-only session recording with replay. Sync is end-to-end encrypted (the server stores ciphertext only) at &lt;strong&gt;$8/month billed yearly&lt;/strong&gt;, and the free tier is the full desktop app with &lt;strong&gt;no host limits&lt;/strong&gt;. The unusual part is the agent layer: a built-in MCP server lets Claude Code or another AI agent open sessions through a key custodian, so the agent never touches a private key, while every agent session is mirrored live in a watch grid.&lt;/p&gt;

&lt;p&gt;Weaknesses, plainly: it's young, it's &lt;strong&gt;not open source&lt;/strong&gt;, and current beta builds are unsigned, so &lt;strong&gt;SmartScreen will warn you&lt;/strong&gt; on first launch until our signing certificate lands. If any of those three is disqualifying, pick Tabby or Bitvise above; both are excellent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; an integrated terminal + SFTP + E2E sync workflow, or giving AI agents supervised access to your servers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worth a look: XPipe
&lt;/h2&gt;

&lt;p&gt;Not a terminal, so it's outside the numbered list: &lt;a href="https://termal.in/xpipe-alternative/" rel="noopener noreferrer"&gt;XPipe&lt;/a&gt; is a connection hub that discovers SSH hosts, Docker containers, K8s pods and WSL distros and opens them in whatever terminal you chose above. Free Community tier; paid tiers from $5/month.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;Restricted machine: Windows Terminal + OpenSSH. X11: MobaXterm. SFTP and tunnels: Bitvise. Mixed RDP fleet: mRemoteNG. Open source: Tabby or PuTTY. Mobile-first: Termius. Integrated everything with encrypted sync, or AI agents on your hosts: us. There's a deeper platform guide at &lt;a href="https://termal.in/ssh-client-for-windows/" rel="noopener noreferrer"&gt;SSH client for Windows&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: this blog is written by the Termalin team. The free tier has no host limits — &lt;a href="https://termal.in/download/" rel="noopener noreferrer"&gt;download Termalin&lt;/a&gt; and compare it against whatever you use today.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ssh</category>
      <category>windows</category>
      <category>comparison</category>
      <category>tools</category>
    </item>
    <item>
      <title>9 Termius Alternatives in 2026 (Free and Paid, Tested)</title>
      <dc:creator>Svyatoslav Pavlov</dc:creator>
      <pubDate>Wed, 09 Sep 2026 15:18:11 +0000</pubDate>
      <link>https://dev.to/wolfhound1995/9-termius-alternatives-in-2026-free-and-paid-tested-2fh3</link>
      <guid>https://dev.to/wolfhound1995/9-termius-alternatives-in-2026-free-and-paid-tested-2fh3</guid>
      <description>&lt;p&gt;Termius is a polished client, and if it's working for you, keep it. But the reasons people go looking for a replacement are consistent, and they're structural, not cosmetic. The free Starter plan keeps your hosts in a local vault on one device: no cross-device sync, no session logs, no snippet automation. Unlocking sync means Pro at &lt;strong&gt;$10 per user per month billed annually&lt;/strong&gt;, and that sync only goes through Termius's cloud; there is no self-hosted or file-based option. On mobile, the same paywall bites hardest, because a phone is exactly where you want your hosts to follow you.&lt;/p&gt;

&lt;p&gt;So here are nine alternatives we've actually run, with prices checked in September 2026. Cards on the table: &lt;strong&gt;we build Termalin&lt;/strong&gt;, entry number one, and this blog lives on its site. We've tried to judge it by the same bar as everything else, including telling you when a competitor is the better pick.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Platforms&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Termalin&lt;/td&gt;
&lt;td&gt;Windows, macOS, Linux&lt;/td&gt;
&lt;td&gt;Free (no host limits) / Pro $8/mo yearly&lt;/td&gt;
&lt;td&gt;Termius workflow with cheaper E2E sync, AI-agent access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tabby&lt;/td&gt;
&lt;td&gt;Windows, macOS, Linux&lt;/td&gt;
&lt;td&gt;Free, open source (MIT)&lt;/td&gt;
&lt;td&gt;Open-source replacement with plugins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MobaXterm&lt;/td&gt;
&lt;td&gt;Windows only&lt;/td&gt;
&lt;td&gt;Home free / Pro $69 per user/year&lt;/td&gt;
&lt;td&gt;Windows admins who need X11 and bundled tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PuTTY / KiTTY&lt;/td&gt;
&lt;td&gt;Windows (PuTTY also Unix)&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Minimalists, locked-down machines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WindTerm&lt;/td&gt;
&lt;td&gt;Windows, macOS, Linux&lt;/td&gt;
&lt;td&gt;Free (Apache-2.0 published source)&lt;/td&gt;
&lt;td&gt;Raw speed on old hardware&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;XPipe&lt;/td&gt;
&lt;td&gt;Windows, macOS, Linux&lt;/td&gt;
&lt;td&gt;Free / Homelab $5/mo / Pro $10/mo&lt;/td&gt;
&lt;td&gt;Browsing Docker, K8s and VMs, not a terminal itself&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SecureCRT&lt;/td&gt;
&lt;td&gt;Windows, macOS, Linux&lt;/td&gt;
&lt;td&gt;$119 perpetual (1 yr updates)&lt;/td&gt;
&lt;td&gt;Enterprise emulation, scripting, serial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Warp&lt;/td&gt;
&lt;td&gt;Windows, macOS, Linux&lt;/td&gt;
&lt;td&gt;Free (limited AI credits) / Build $20/mo&lt;/td&gt;
&lt;td&gt;AI inside the terminal, not host management&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenSSH + tmux&lt;/td&gt;
&lt;td&gt;Everywhere&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;The baseline that never bills you&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Prices as of September 2026, from official sites; check before buying.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Termalin
&lt;/h2&gt;

&lt;p&gt;The closest feature-for-feature match on this list: tabbed terminal, SFTP panel with a built-in editor, snippets, live server stats, and account-based sync that is end-to-end encrypted, so the server only ever stores ciphertext. Pro is &lt;strong&gt;$8/month billed yearly&lt;/strong&gt; ($10 month-to-month) against Termius's $10, and the free tier is the full desktop app with &lt;strong&gt;no host limits&lt;/strong&gt;, just local-only. There's a 14-day Pro trial with no card. Two things Termius doesn't attempt: output-only session recording with replay, and a built-in MCP server so AI agents like Claude Code can use your hosts through a key custodian (the agent never sees a key) while you watch every session live in a grid.&lt;/p&gt;

&lt;p&gt;Now the honest part. Termalin is a young product; it is &lt;strong&gt;not open source&lt;/strong&gt;; and the current beta builds are unsigned, so Windows shows a SmartScreen warning on first run. Phones are covered by a web app, not native apps, so if the Termius iOS or Android client is the reason you pay, nothing on this list fully replaces that and you should read no further. The full head-to-head is on our &lt;a href="https://termal.in/termius-alternative/" rel="noopener noreferrer"&gt;Termius comparison page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; keeping the Termius workflow while paying less for sync, or giving an AI agent supervised SSH access.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Tabby
&lt;/h2&gt;

&lt;p&gt;If your objection to Termius is philosophical, Tabby is the answer: free, MIT-licensed, genuinely cross-platform, with a plugin ecosystem, serial support, and an SSH connection manager built in. It's an Electron app, so it's heavier on RAM than a native terminal, and there's no first-party cloud sync; you sync settings via config files or community plugins, which is either a dealbreaker or exactly the point, depending on your threat model. We keep a detailed &lt;a href="https://termal.in/tabby-alternative/" rel="noopener noreferrer"&gt;Tabby comparison&lt;/a&gt; if you want the long version.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; anyone who wants the source code and zero subscriptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. MobaXterm
&lt;/h2&gt;

&lt;p&gt;Windows only, and unapologetic about it. The Home Edition is free but capped at 12 sessions and 2 SSH tunnels; Professional is &lt;strong&gt;$69 per user per year&lt;/strong&gt; and lifts the caps. What you get for living inside one app: an embedded X server (still the easiest way to run remote GUI apps on Windows), automatic SFTP browser per session, and a small arsenal of bundled network tools. The UI is dense and dated, and there's no sync story at all. Details in our &lt;a href="https://termal.in/mobaxterm-alternative/" rel="noopener noreferrer"&gt;MobaXterm comparison&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Windows sysadmins who need X11 forwarding without ceremony.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. PuTTY / KiTTY
&lt;/h2&gt;

&lt;p&gt;The opposite trade from Termius: no accounts, no sync, no tabs, no telemetry, no cost. PuTTY is small enough to run from a USB stick and battle-tested over decades; KiTTY is a Windows fork that adds session filters, portability and automatic passwords. You will assemble the rest of the workflow yourself (WinSCP for files, Pageant for keys). If that assembly is the part you hate, see our &lt;a href="https://termal.in/putty-alternative/" rel="noopener noreferrer"&gt;PuTTY comparison&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; locked-down corporate machines and people who want one binary that does one thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. WindTerm
&lt;/h2&gt;

&lt;p&gt;The fastest client on this list, and it isn't close on older hardware. Free for commercial use, with published source under Apache-2.0. It has sessions, SFTP, and a shell-aware input line that autocompletes from history. The caveat you should know before adopting it: the sole maintainer has said openly that work commitments have cut development pace to a fraction of what it was. The project isn't abandoned, but issues sit longer than they used to. Adopt it for what it is today, not its roadmap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; speed junkies and low-spec machines.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. XPipe
&lt;/h2&gt;

&lt;p&gt;Not a terminal: XPipe is a connection hub that discovers and organizes everything you can shell into (SSH hosts, Docker containers, Kubernetes pods, VMs, WSL) and opens them in the terminal and editor you already use. The Community tier is free with unlimited connections; Homelab is $5/month and Professional $10/month (or $80/year). If your Termius pain is "too many kinds of things to connect to" rather than the terminal itself, this is the interesting answer. Comparison &lt;a href="https://termal.in/xpipe-alternative/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; heterogeneous infrastructure: containers and VMs, not just hosts.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. SecureCRT
&lt;/h2&gt;

&lt;p&gt;The enterprise workhorse. &lt;strong&gt;$119 for a perpetual license&lt;/strong&gt; with a year of updates, one license valid across Windows, macOS and Linux. Its terminal emulation and scripting (Python, VBScript) are the most rigorous in this roundup, and it handles serial, Telnet and legacy protocols that consumer clients dropped years ago. It looks and feels like the twenty-year-old professional tool it is, and that's a compliment or an insult depending on your day job. Our take versus Termalin is on the &lt;a href="https://termal.in/securecrt-alternative/" rel="noopener noreferrer"&gt;SecureCRT page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; network engineers, serial consoles, compliance-minded shops that want perpetual licenses.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Warp
&lt;/h2&gt;

&lt;p&gt;Warp is what happens when the terminal itself becomes an AI product: block-based output, shared notebooks, and an agent that writes and runs commands. The free tier includes a modest monthly allowance of AI credits; the Build plan is $20/month. As a Termius replacement specifically, it's a partial fit: SSH works fine, but host management, sync of server inventory, and SFTP are not what Warp is optimizing for. You'd be switching categories, not vendors. More in our &lt;a href="https://termal.in/warp-alternative/" rel="noopener noreferrer"&gt;Warp comparison&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; developers who want AI assistance in every shell, local or remote.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. OpenSSH + tmux + ~/.ssh/config
&lt;/h2&gt;

&lt;p&gt;The baseline deserves its slot. A well-kept &lt;code&gt;~/.ssh/config&lt;/code&gt; gives you aliases, jump hosts and per-host keys; tmux gives you sessions that survive disconnects; &lt;code&gt;ssh-agent&lt;/code&gt; gives you one passphrase per day. It costs nothing, runs everywhere, and will outlive every company on this page. What it lacks is exactly what people pay for: a visual host list, file transfer UI, and anything resembling sync across machines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; everyone, as a floor. Learn it even if you buy something on top.&lt;/p&gt;

&lt;h2&gt;
  
  
  Also considered
&lt;/h2&gt;

&lt;p&gt;Bitvise and mRemoteNG are both solid and free but Windows-only, so they're covered in our Windows roundup instead. Royal TSX is macOS-only and appears in the Mac roundup.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to choose
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Open source or nothing&lt;/strong&gt; → Tabby, or PuTTY if you want minimal. Not us.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mobile apps are non-negotiable&lt;/strong&gt; → stay on Termius, honestly. Its iOS/Android clients are still the best in class.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;X11 on Windows&lt;/strong&gt; → MobaXterm.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Perpetual license for procurement&lt;/strong&gt; → SecureCRT.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Containers and VMs everywhere&lt;/strong&gt; → XPipe, possibly alongside one of the terminals above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Same workflow, E2E sync, smaller bill, or AI agents on your hosts&lt;/strong&gt; → that's the corner we built Termalin for.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;One-line disclosure, as always: this blog is written by the Termalin team. Termalin is free with no host limits on Windows, macOS and Linux — &lt;a href="https://termal.in/download/" rel="noopener noreferrer"&gt;download it&lt;/a&gt; and judge the rest yourself.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ssh</category>
      <category>termius</category>
      <category>comparison</category>
      <category>tools</category>
    </item>
    <item>
      <title>Let Claude Code and Cursor query your database (safely) — the MCP setup</title>
      <dc:creator>Svyatoslav Pavlov</dc:creator>
      <pubDate>Tue, 08 Sep 2026 17:20:07 +0000</pubDate>
      <link>https://dev.to/wolfhound1995/let-claude-code-and-cursor-query-your-database-safely-the-mcp-setup-1376</link>
      <guid>https://dev.to/wolfhound1995/let-claude-code-and-cursor-query-your-database-safely-the-mcp-setup-1376</guid>
      <description>&lt;p&gt;An agent that can read your database answers questions you'd otherwise open a SQL editor for. "Why did signups drop on Tuesday?" "Which orders have been stuck in &lt;code&gt;pending&lt;/code&gt; for more than an hour?" "What's actually in the &lt;code&gt;events&lt;/code&gt; table?" With a query capability the model answers in seconds; without one it guesses. The useful part isn't the SQL — Claude Code and Cursor both write SQL fine — it's giving the agent a way to &lt;em&gt;run&lt;/em&gt; a read without handing it your credentials. That handoff is the whole friction, and it's what this post walks through for both tools.&lt;/p&gt;

&lt;p&gt;We've written separately about &lt;a href="https://termal.in/blog/give-an-ai-agent-database-access/" rel="noopener noreferrer"&gt;why pasting a database connection string into an agent is the move you can't undo&lt;/a&gt; — the credential is bearer access, it can write and drop as well as read, and once it's passed through a model's context you can't un-share it. Read that for the safety model; here we take it as settled and get the wiring done. The short version: the client holds the credential, the agent gets a scoped, read-only-by-default &lt;em&gt;ask&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Termalin&lt;/strong&gt; — the desktop app, &lt;a href="https://termal.in/download/" rel="noopener noreferrer"&gt;free tier is fine&lt;/a&gt;. It's an SSH and database client with a built-in &lt;a href="https://termal.in/mcp/" rel="noopener noreferrer"&gt;MCP server&lt;/a&gt;; that local server is what your agent will talk to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;At least one database connection saved&lt;/strong&gt; in Termalin's Data client — Postgres, MySQL, SQLite, SQL Server, ClickHouse, MongoDB, Redis, and more. Pick something low-stakes for the first run: an analytics replica, a staging database, a copy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code or Cursor&lt;/strong&gt; on the same machine as the app. The database tools drive the live desktop app, so the agent and the app share a box.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One expectation to set: for Claude Code and Cursor, database access runs through the &lt;strong&gt;local&lt;/strong&gt; stdio MCP server, so the agent needs to be on the same machine as your unlocked desktop app. (There's a hosted path too — the connectors for claude.ai and ChatGPT can query an &lt;em&gt;enrolled&lt;/em&gt; server's databases passwordless through &lt;code&gt;termal.in/api/v1/mcp&lt;/code&gt;, using the server's own local database trust — but for a local IDE agent, the local server is the one you want.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 — turn agent data access on
&lt;/h2&gt;

&lt;p&gt;This is off by default, and deliberately so. Open &lt;strong&gt;Settings → Agent&lt;/strong&gt; and enable &lt;strong&gt;"Let agents query your databases."&lt;/strong&gt; The subtitle spells out the posture: &lt;em&gt;off by default, and stays read-only unless a connection grants full access.&lt;/em&gt; Until you flip it, every database tool returns &lt;code&gt;database access for agents is off — enable it in Settings → Agent&lt;/code&gt;, full stop.&lt;/p&gt;

&lt;p&gt;Two defaults are worth internalizing before you go further:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It's a second opt-in, on top of the running app.&lt;/strong&gt; Turning on agent access to servers doesn't turn on database access — this is its own switch. Nothing is exposed until you make the decision.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every connection is read-only for the agent.&lt;/strong&gt; Even with the master switch on, each query is checked before it runs. A write only goes through if you've raised &lt;em&gt;that specific connection's&lt;/em&gt; agent policy to full access — a per-connection choice, not a global one. Leave it read-only for now.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The credential itself never moves: the connection's host, port and password stay in the app's vault, and the agent only gets to &lt;em&gt;ask the running app&lt;/em&gt; to run a query and hand back rows. There's no connection string in a config file for the agent — or a prompt injection — to read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — register the local MCP server
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Claude Code&lt;/strong&gt; — one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add termalin &lt;span class="nt"&gt;--&lt;/span&gt; &amp;lt;path&amp;gt;/termalin-mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;&amp;lt;path&amp;gt;&lt;/code&gt; with wherever the &lt;code&gt;termalin-mcp&lt;/code&gt; binary lives (the app can point you at it). Restart Claude Code and it picks up the tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cursor&lt;/strong&gt; — Cursor reads MCP servers from a JSON file: &lt;code&gt;.cursor/mcp.json&lt;/code&gt; in a project root for that project only, or &lt;code&gt;~/.cursor/mcp.json&lt;/code&gt; to make the server available everywhere. For a local stdio server the entry is a command and its arguments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"termalin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;path&amp;gt;/termalin-mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The global file is the sensible choice — database access isn't a per-repo concern. Restart Cursor (or reload the MCP list in its settings) and confirm the server shows as running. This is the same registration the &lt;a href="https://termal.in/blog/cursor-ssh-access-for-agents/" rel="noopener noreferrer"&gt;Cursor SSH walkthrough&lt;/a&gt; and the &lt;a href="https://termal.in/blog/connect-claude-code-to-your-server-over-ssh/" rel="noopener noreferrer"&gt;Claude Code one&lt;/a&gt; use — one server, and it now carries the database tools alongside the SSH ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — the tools, and a real first task
&lt;/h2&gt;

&lt;p&gt;With the server registered and the switch on, the agent sees four database tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;data_list&lt;/span&gt;     &lt;span class="c1"&gt;# your saved connections: id, name, engine, and whether the agent may write
&lt;/span&gt;&lt;span class="n"&gt;data_tables&lt;/span&gt;   &lt;span class="c1"&gt;# tables / collections / measurements for a connection, with row counts
&lt;/span&gt;&lt;span class="n"&gt;data_schema&lt;/span&gt;   &lt;span class="c1"&gt;# one table's columns: name, type, nullable, primary key
&lt;/span&gt;&lt;span class="n"&gt;data_query&lt;/span&gt;    &lt;span class="c1"&gt;# run a query in the connection's own language; default cap 200 rows
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They mirror how you'd explore a database by hand: list what's connected, see the tables, describe a table, run the query. &lt;code&gt;data_query&lt;/code&gt; speaks whatever the connection speaks — SQL for the relational engines and ClickHouse, Cypher for Neo4j, a Redis command, a &lt;code&gt;collection {filter}&lt;/code&gt; for MongoDB — and returns rows up to a cap you can raise per call (&lt;code&gt;maxRows&lt;/code&gt;), defaulting to &lt;strong&gt;200&lt;/strong&gt; so an agent can't accidentally pull a million rows into its context.&lt;/p&gt;

&lt;p&gt;Now give it a question you'd actually ask, not a demo query:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Signups dropped on Tuesday according to the dashboard. Using the analytics database, figure out what happened.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Watch the agent work the tools in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;data_list&lt;/code&gt;&lt;/strong&gt; — it finds your &lt;code&gt;analytics&lt;/code&gt; connection, sees the engine is Postgres, and notes it's read-only for the agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;data_tables&lt;/code&gt;&lt;/strong&gt; — it lists the tables, spots &lt;code&gt;signups&lt;/code&gt; and &lt;code&gt;signup_events&lt;/code&gt;, and reads the row counts to know what's worth querying.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;data_schema&lt;/code&gt;&lt;/strong&gt; on &lt;code&gt;signups&lt;/code&gt; — it learns the columns (&lt;code&gt;created_at&lt;/code&gt;, &lt;code&gt;source&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;, …) and now knows how to slice by day and by source.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;data_query&lt;/code&gt;&lt;/strong&gt; — it runs something like &lt;code&gt;SELECT date_trunc('day', created_at) AS day, source, count(*) FROM signups WHERE created_at &amp;gt;= now() - interval '10 days' GROUP BY 1, 2 ORDER BY 1&lt;/code&gt; and reads the rows back.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The rows tell the story — maybe one &lt;code&gt;source&lt;/code&gt; went to zero on Tuesday (a broken referral link), maybe the total held but &lt;code&gt;status = 'failed'&lt;/code&gt; spiked (a provider outage). Either way the agent formed its query from the real schema, not a guess, and never held anything more than the ability to ask. If a result truncates at the 200-row cap, it says so and narrows the query rather than pushing for more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the boundaries are
&lt;/h2&gt;

&lt;p&gt;Brief, because the &lt;a href="https://termal.in/blog/give-an-ai-agent-database-access/" rel="noopener noreferrer"&gt;safety post&lt;/a&gt; covers it in full — but the defaults you're relying on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read-only by default.&lt;/strong&gt; On the SQL-family engines only read verbs pass — &lt;code&gt;SELECT&lt;/code&gt;, &lt;code&gt;SHOW&lt;/code&gt;, &lt;code&gt;DESCRIBE&lt;/code&gt;, &lt;code&gt;EXPLAIN&lt;/code&gt;, &lt;code&gt;WITH&lt;/code&gt;, &lt;code&gt;PRAGMA&lt;/code&gt;. On Redis, only read commands; on MongoDB, an aggregation with &lt;code&gt;$out&lt;/code&gt;/&lt;code&gt;$merge&lt;/code&gt; is blocked; on Neo4j, &lt;code&gt;CREATE&lt;/code&gt;/&lt;code&gt;MERGE&lt;/code&gt;/&lt;code&gt;DELETE&lt;/code&gt;/&lt;code&gt;SET&lt;/code&gt;/&lt;code&gt;DROP&lt;/code&gt; are blocked. A confused or hijacked model gets a read, not a &lt;code&gt;DROP&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per connection, not global.&lt;/strong&gt; Your analytics replica is a separate decision from your production primary; raising one to full write access doesn't touch the other.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Off with one toggle&lt;/strong&gt;, and &lt;strong&gt;the app holds the credential&lt;/strong&gt; — nothing was ever shared, so turning it off leaves no copies and no password to rotate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For anything sensitive, do both: point the Data connection at a read replica or a least-privilege database role &lt;em&gt;and&lt;/em&gt; leave the agent read-only. The client-side gate catches mistakes; the database grant sets the ceiling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tool-specific notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cursor&lt;/strong&gt; asks before each tool call by default. Leave that on for the first sessions — you'll see every &lt;code&gt;data_query&lt;/code&gt; before it runs and can read the SQL the agent proposes. Auto-approval is a convenience to extend one tool at a time: &lt;code&gt;data_list&lt;/code&gt; and &lt;code&gt;data_schema&lt;/code&gt; are safe to wave through first, &lt;code&gt;data_query&lt;/code&gt; the one to keep on manual longest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code&lt;/strong&gt; surfaces each tool call inline with its arguments, so the query text is in the transcript before it executes. You're reading the agent's SQL, not trusting a summary of it — and every call is attributed to the agent, so at 2 a.m. you can tell "it read this" from "I read this."&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where Termalin fits
&lt;/h2&gt;

&lt;p&gt;Most "connect your agent to a database" guides end at pasting a connection string into a config file, which quietly grants an agent the same write-and-drop power your app has. Termalin's Data client inverts that: the app is the custodian, the agent gets four scoped tools driven through the running window, and the whole capability is read-only until you decide otherwise and off with a single switch. It's the same custody idea we apply to &lt;a href="https://termal.in/blog/connect-claude-code-to-your-server-over-ssh/" rel="noopener noreferrer"&gt;SSH access for agents&lt;/a&gt; and to &lt;a href="https://termal.in/blog/what-is-an-mcp-server/" rel="noopener noreferrer"&gt;what an MCP server actually is&lt;/a&gt; — a database is just the version where a bad write is measured in rows. Start with one connection, keep it read-only, and ask something real before you consider widening anything.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Termalin is a free, cross-platform SSH and &lt;a href="https://termal.in/database-client/" rel="noopener noreferrer"&gt;database client&lt;/a&gt; with a built-in MCP server and a per-connection agent policy — &lt;a href="https://termal.in/download/" rel="noopener noreferrer"&gt;download it&lt;/a&gt;, or read how it &lt;a href="https://termal.in/security/" rel="noopener noreferrer"&gt;handles credentials safely&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>cursor</category>
      <category>mcp</category>
      <category>database</category>
    </item>
    <item>
      <title>Connect to a database behind a firewall — over an SSH tunnel, without exposing the port</title>
      <dc:creator>Svyatoslav Pavlov</dc:creator>
      <pubDate>Mon, 07 Sep 2026 11:54:33 +0000</pubDate>
      <link>https://dev.to/wolfhound1995/connect-to-a-database-behind-a-firewall-over-an-ssh-tunnel-without-exposing-the-port-1n29</link>
      <guid>https://dev.to/wolfhound1995/connect-to-a-database-behind-a-firewall-over-an-ssh-tunnel-without-exposing-the-port-1n29</guid>
      <description>&lt;p&gt;You've got a database on a server somewhere, and you want to reach it from your laptop — run a query, point a GUI at it, let a migration tool connect. It listens on &lt;code&gt;5432&lt;/code&gt; (Postgres) or &lt;code&gt;3306&lt;/code&gt; (MySQL), and right now your laptop can't get to it, because the database sits on a private network with no route from the outside. So you go looking for the fastest bridge, and the internet offers a shortcut that works immediately and quietly costs you the thing the private network was protecting.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mistake: opening the database port to the world
&lt;/h2&gt;

&lt;p&gt;The shortcut is to make the database reachable: bind it to &lt;code&gt;0.0.0.0&lt;/code&gt; instead of &lt;code&gt;127.0.0.1&lt;/code&gt;, punch a hole in the security group for port &lt;code&gt;5432&lt;/code&gt;, and now you can connect from anywhere. It works on the first try, which is exactly why it's dangerous.&lt;/p&gt;

&lt;p&gt;A database port on the public internet is a standing target. Postgres and MySQL speak on well-known ports, and the whole internet is scanned for them continuously — a listener on &lt;code&gt;5432&lt;/code&gt; starts collecting login attempts within minutes of going up. The only thing between the internet and your data is now the database's own password, one protocol quirk, or one CVE. Managed databases don't let you do this by accident: RDS, Cloud SQL and the rest default to a private subnet with no public IP, precisely because a database is the last thing you want facing the internet. Opening it up anyway — a &lt;code&gt;0.0.0.0/0&lt;/code&gt; rule "just for now" — trades the whole design away for a few minutes saved. The fix costs about as little and gives up nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an SSH tunnel to a database actually does
&lt;/h2&gt;

&lt;p&gt;You almost certainly already have a way onto that network: an SSH login to a server that &lt;em&gt;can&lt;/em&gt; see the database — the app server, a bastion, any box on the same subnet. SSH port forwarding lets you borrow that reach without exposing anything new.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-L&lt;/span&gt; 5432:db.internal:5432 deploy@bastion.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read it left to right: open port &lt;code&gt;5432&lt;/code&gt; &lt;strong&gt;on my laptop&lt;/strong&gt;, and forward anything that arrives there to &lt;code&gt;db.internal:5432&lt;/code&gt; &lt;strong&gt;as resolved from the bastion&lt;/strong&gt;. Then you point your client at &lt;code&gt;localhost&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;psql &lt;span class="nt"&gt;-h&lt;/span&gt; 127.0.0.1 &lt;span class="nt"&gt;-p&lt;/span&gt; 5432 &lt;span class="nt"&gt;-U&lt;/span&gt; app appdb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;psql&lt;/code&gt; connects to your own machine; SSH carries the bytes, encrypted, over the connection you already had; the bastion opens a plain connection to &lt;code&gt;db.internal&lt;/code&gt; from &lt;em&gt;inside&lt;/em&gt; the private network, where it's allowed to. From the database's point of view nothing changed — it still only accepts connections from its own network. And no new port was opened anywhere on the internet. You got reachability without exposure.&lt;/p&gt;

&lt;p&gt;The one detail that trips everyone up: &lt;code&gt;db.internal&lt;/code&gt; is resolved &lt;strong&gt;on the far side&lt;/strong&gt;, by the bastion, not by your laptop. It can be a name only the private network knows, or a raw address your laptop can't route to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# the database only the bastion can see, by private IP&lt;/span&gt;
ssh &lt;span class="nt"&gt;-L&lt;/span&gt; 5432:10.0.0.9:5432 deploy@bastion.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole trick, covered in depth — local, remote and dynamic — in &lt;a href="https://termal.in/blog/ssh-port-forwarding-tunnels/" rel="noopener noreferrer"&gt;SSH port forwarding explained&lt;/a&gt;. Here we only need the local (&lt;code&gt;-L&lt;/code&gt;) case.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bastion is where the tunnel starts
&lt;/h2&gt;

&lt;p&gt;The box you tunnel &lt;em&gt;through&lt;/em&gt; is usually the same gateway you already jump through to get a shell on the private network. If you reach your internal servers with &lt;code&gt;ProxyJump&lt;/code&gt;, you already have the piece a database tunnel needs. The two are complementary: &lt;a href="https://termal.in/blog/ssh-proxyjump-bastion-host/" rel="noopener noreferrer"&gt;ProxyJump&lt;/a&gt; gets your &lt;em&gt;shell&lt;/em&gt; onto a host behind the gateway, and &lt;code&gt;-L&lt;/code&gt; brings a &lt;em&gt;service's port&lt;/em&gt; from the far side back to your &lt;code&gt;localhost&lt;/code&gt;. If you have to hop through &lt;em&gt;two&lt;/em&gt; gateways, chain them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-J&lt;/span&gt; bastion.example.com &lt;span class="nt"&gt;-L&lt;/span&gt; 5432:db.internal:5432 deploy@app.internal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why people still get the tunnel wrong
&lt;/h2&gt;

&lt;p&gt;The mechanism is clean. The day-to-day of running tunnels by hand is where it frays:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The command rots in your shell history.&lt;/strong&gt; Next week you're reconstructing which local port mapped to which database, on which bastion, in which environment. Staging and production both want &lt;code&gt;5432&lt;/code&gt;; you get one at a time on your laptop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The local port is open to every process on your machine.&lt;/strong&gt; &lt;code&gt;-L 5432&lt;/code&gt; listens on &lt;code&gt;127.0.0.1:5432&lt;/code&gt; — any program on your laptop can now reach the production database through it, no SSH auth required, for as long as the tunnel is up. (And &lt;code&gt;-L 0.0.0.0:5432:...&lt;/code&gt; quietly exposes it to your whole LAN.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The tunnel dies mid-session and takes the query with it.&lt;/strong&gt; A laptop sleep, a Wi-Fi change, an idle timeout, and the forward drops silently — usually solved with &lt;code&gt;-N&lt;/code&gt;, &lt;code&gt;-f&lt;/code&gt; and &lt;code&gt;ServerAliveInterval 30&lt;/code&gt;, none of which you remembered the first time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One &lt;code&gt;ssh -L&lt;/code&gt; per database.&lt;/strong&gt; A primary, a read replica, a MySQL box, a Redis — that's four tunnel commands on four local ports, each re-run after every disconnect, none telling you at a glance whether it's still up.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A saved &lt;code&gt;ssh_config&lt;/code&gt; block — &lt;code&gt;LocalForward&lt;/code&gt;, a &lt;code&gt;ProxyJump&lt;/code&gt;, keepalives — smooths most of this over. But you're still starting it by hand, still watching it, still pointing your client at the right loopback port yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Termalin's Data client does it
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://termal.in/features/" rel="noopener noreferrer"&gt;Termalin&lt;/a&gt; is a cross-platform SSH client that recently grew a &lt;strong&gt;Data client&lt;/strong&gt; — a database client in the desktop app and in the web cabinet at &lt;a href="https://termal.in/account" rel="noopener noreferrer"&gt;termal.in/account&lt;/a&gt;. You add a database the way you'd add a server: pick the engine, give it a host, port and credentials, optionally a default database. It speaks a wide spread of engines — PostgreSQL, MySQL, SQL Server, ClickHouse, MongoDB, Redis, Elasticsearch, Neo4j and more. The part this article is about is what happens when that database is behind a firewall — you don't open a port and you don't hand-run &lt;code&gt;ssh -L&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A database on a network you can already route to, you reach directly.&lt;/strong&gt; Termalin dials the host and port you gave it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A database behind a firewall, you reach through a tunnel.&lt;/strong&gt; On the desktop, attach a saved SSH host — your bastion, jump host and all — to the connection; Termalin opens a local forward on that session and points the driver at the resulting &lt;code&gt;127.0.0.1:&amp;lt;port&amp;gt;&lt;/code&gt; for you, so a database that only listens on localhost or sits behind a VPN comes through as if it were local. In the web cabinet, the same job is done by a &lt;strong&gt;Connector&lt;/strong&gt;: Termalin opens the Connector's SSH session, forwards a loopback port through it, and runs the query over that. Either way the encrypted SSH session carries the traffic to a box that can see the database, and &lt;strong&gt;the database itself never faces the internet.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So you keep what the private subnet was there to give you — a database reachable only from inside its own network — while getting a working query editor on your laptop. The tunnel comes up with the connection and goes down when you close it, along with the loopback port it opened. There's no command to remember, no forward to babysit, and no &lt;code&gt;0.0.0.0/0&lt;/code&gt; rule left behind in a security group.&lt;/p&gt;

&lt;h2&gt;
  
  
  A tunnel is reachability, not authorization
&lt;/h2&gt;

&lt;p&gt;Worth stating plainly, because it's the one thing a tunnel doesn't do: getting the connection &lt;em&gt;to&lt;/em&gt; the database isn't the same as deciding what it may &lt;em&gt;do&lt;/em&gt; once it's there. A tunnel is a wire, and a wire is neutral — whoever's on the other end still connects as whatever database user you handed over. Point the connection at a least-privilege role, and if an agent is going to be on the other end of it, keep that a separate decision — the subject of &lt;a href="https://termal.in/blog/give-an-ai-agent-database-access/" rel="noopener noreferrer"&gt;giving an AI agent read-only database access&lt;/a&gt;, not this one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Termalin fits
&lt;/h2&gt;

&lt;p&gt;Every developer should know &lt;code&gt;ssh -L&lt;/code&gt; cold — it's the honest way to reach a database without opening it to the world, and the mental model (open a port on my side, forward it to something the far side can see) is the whole game. The friction was never the concept; it was the bookkeeping: the commands in your history, the ports you juggle, the tunnels that drop, the one forward per database.&lt;/p&gt;

&lt;p&gt;Termalin keeps the tunnel and the database connection as one saved thing. Attach a jump host or a Connector, and reaching a database on a private subnet is a click — the forward opens with the connection, the driver points at the loopback port for you, and the database stays exactly where it belongs: behind the firewall, listening only to its own network.&lt;/p&gt;




&lt;p&gt;*Termalin is a free, cross-platform SSH and database client with built-in tunnels, jump hosts and a &lt;a href="https://termal.in/database-client/" rel="noopener noreferrer"&gt;Data client&lt;/a&gt; — &lt;a href="https://termal.in/download/" rel="noopener noreferrer"&gt;download it&lt;/a&gt;, or see how it &lt;a href="https://termal.in/security/" rel="noopener noreferrer"&gt;handles your keys and credentials safely&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ssh</category>
      <category>portforwarding</category>
      <category>database</category>
      <category>tunneling</category>
    </item>
    <item>
      <title>How to give an AI agent safe access to your database (read-only by default)</title>
      <dc:creator>Svyatoslav Pavlov</dc:creator>
      <pubDate>Fri, 04 Sep 2026 11:24:00 +0000</pubDate>
      <link>https://dev.to/wolfhound1995/how-to-give-an-ai-agent-safe-access-to-your-database-read-only-by-default-1ima</link>
      <guid>https://dev.to/wolfhound1995/how-to-give-an-ai-agent-safe-access-to-your-database-read-only-by-default-1ima</guid>
      <description>&lt;p&gt;An agent that can read your database is genuinely useful. "Why did signups drop on Tuesday?" "Find the orders stuck in &lt;code&gt;pending&lt;/code&gt; for more than an hour." "What's the schema of the &lt;code&gt;events&lt;/code&gt; table?" These are questions the model answers in seconds if it can run a query — and slowly, badly, or never if it can't. So you go looking for how to connect it, and every guide says the same thing: put your database connection string in the agent's environment, or in an MCP server's config, and you're done.&lt;/p&gt;

&lt;p&gt;You are done, in the sense that it works. But you've also made a security decision, whether you meant to or not. The moment an agent can touch a real database, the interesting question isn't "can it write SQL" — of course it can. It's &lt;em&gt;who holds the credential, and what can that credential do.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The naive path, and why it's a trap
&lt;/h2&gt;

&lt;p&gt;A database connection string is a bearer credential. &lt;code&gt;postgres://app:hunter2@db.internal:5432/prod&lt;/code&gt; isn't a &lt;em&gt;pointer&lt;/em&gt; to access — it &lt;em&gt;is&lt;/em&gt; access. Whoever holds those bytes can connect as that user, with everything that user can do. And the user in your app's connection string can almost always do a lot more than &lt;code&gt;SELECT&lt;/code&gt;: &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;, &lt;code&gt;DROP TABLE&lt;/code&gt;, &lt;code&gt;TRUNCATE&lt;/code&gt;. You wanted the agent to answer a question about last week's signups; you handed it the ability to delete them.&lt;/p&gt;

&lt;p&gt;Three things make this worse than it looks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You can't un-share it.&lt;/strong&gt; Once a connection string has passed through a model's context — an env var, a config blob, a tool call you didn't read — you can't prove it didn't end up in a transcript or a log. The only honest remediation is to rotate the database password everywhere it's used.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt injection reaches it.&lt;/strong&gt; A poisoned row, a booby-trapped document, a tool result from elsewhere in the session — any of these can talk a model into running a query you never asked for, or into exfiltrating the very credential it was handed. The agent's context is not a safe place to keep a secret.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;There's no scope and no expiry.&lt;/strong&gt; A raw connection string doesn't come with a "read-only" mode or a clock. If the account can write, the agent can write. If the credential never expires, neither does the access.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rule of thumb is the one we keep coming back to for &lt;a href="https://termal.in/blog/let-an-ai-agent-deploy-without-your-ssh-keys/" rel="noopener noreferrer"&gt;SSH keys&lt;/a&gt;: if revoking a credential means &lt;em&gt;remembering it exists&lt;/em&gt; and rotating it everywhere, you'll put it off, and it will outlive the experiment.&lt;/p&gt;

&lt;h2&gt;
  
  
  What good looks like: the client holds the credential
&lt;/h2&gt;

&lt;p&gt;Flip the model. The agent should never hold the connection string at all.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The client is the custodian.&lt;/strong&gt; A trusted program holds the database credential and does the connecting. The agent doesn't get the secret — it gets the ability to &lt;em&gt;ask&lt;/em&gt; that program to run a query and hand back rows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read-only is the default, not an option you remember to set.&lt;/strong&gt; Write and schema-changing statements are refused unless you've explicitly granted that specific connection more. A confused or hijacked model gets &lt;code&gt;SELECT&lt;/code&gt;, not &lt;code&gt;DROP&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scope is per connection.&lt;/strong&gt; Access to your analytics replica is a separate decision from access to your production primary. Enabling one doesn't enable the other.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Revocation is a toggle.&lt;/strong&gt; Because nothing was ever shared, turning access off costs nothing and leaves no copies behind.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the same custody thesis we apply to servers — in &lt;a href="https://termal.in/blog/agent-ssh-access-blast-radius/" rel="noopener noreferrer"&gt;shrinking an agent's blast radius&lt;/a&gt; and in &lt;a href="https://termal.in/blog/what-is-an-mcp-server/" rel="noopener noreferrer"&gt;what an MCP server actually is&lt;/a&gt;. Databases are just the sharpest version, because the blast radius of a bad write is measured in rows.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Termalin's Data client works
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://termal.in/features/" rel="noopener noreferrer"&gt;Termalin&lt;/a&gt; is a cross-platform SSH client that recently grew a &lt;strong&gt;Data client&lt;/strong&gt; — a database client in the desktop app and in the web cabinet at &lt;a href="https://termal.in/account" rel="noopener noreferrer"&gt;termal.in/account&lt;/a&gt;. You add a connection the way you'd add a server: pick the engine, give it a host and credentials (or, for SQLite, a file), optionally name a default database. Then you get a workspace — a tree of databases and tables on the left, a SQL editor in the middle, results below. Click through the schema, write a query, read the rows. None of that requires an agent; it's a normal, useful database client.&lt;/p&gt;

&lt;p&gt;The Data client speaks a spread of engines, not just Postgres:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL&lt;/strong&gt;, &lt;strong&gt;MySQL / MariaDB&lt;/strong&gt;, and &lt;strong&gt;SQLite&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Microsoft SQL Server&lt;/strong&gt; and &lt;strong&gt;ClickHouse&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MongoDB / DocumentDB&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Redis&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Elasticsearch / OpenSearch&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cassandra / ScyllaDB&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Neo4j&lt;/strong&gt; and &lt;strong&gt;InfluxDB&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RabbitMQ&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloudflare D1&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each speaks its own query language — SQL for the relational engines and ClickHouse, Cypher for Neo4j, a Redis command, a &lt;code&gt;collection {filter}&lt;/code&gt; for MongoDB, and so on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Direct, or through a Connector.&lt;/strong&gt; A database on your own network, you reach directly. A database behind a firewall — the common case in production — you reach through a Termalin &lt;strong&gt;Connector&lt;/strong&gt;: it opens an SSH tunnel to a box that &lt;em&gt;can&lt;/em&gt; see the database, forwards a loopback port through it, and points the driver at that port. The database never faces the internet, and you never open a port to it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where the agent comes in
&lt;/h3&gt;

&lt;p&gt;Termalin ships a built-in &lt;a href="https://termal.in/mcp/" rel="noopener noreferrer"&gt;MCP server&lt;/a&gt;, and once you turn agent data access on it exposes four database tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data_list     # your saved connections: id, name, engine
data_tables   # tables / collections / measurements, with row counts
data_schema   # one table's columns: name, type, nullable, primary key
data_query    # run a query, get rows back (default cap 200)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register the local server and it drives the Data client inside your &lt;em&gt;running, unlocked&lt;/em&gt; app, so the connection details stay with the client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add termalin &lt;span class="nt"&gt;--&lt;/span&gt; &amp;lt;path&amp;gt;/termalin-mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(No desktop running? The connectors for claude.ai and ChatGPT reach an &lt;em&gt;enrolled&lt;/em&gt; server's databases the same read-only way through the hosted endpoint — passwordless, using the server's own local database trust, no local app required.)&lt;/p&gt;

&lt;p&gt;The agent can now discover connections, walk a schema, and run queries. What it &lt;em&gt;can't&lt;/em&gt; do is anything you didn't allow, because two gates sit in front of it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Agent data access is off by default.&lt;/strong&gt; A single master switch in &lt;strong&gt;Settings → Agent&lt;/strong&gt;. Until you flip it, the data tools return "database access for agents is off," full stop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every connection is read-only for the agent by default.&lt;/strong&gt; Even with the master switch on, each query is checked before it runs. On the SQL-family engines only read verbs pass — &lt;code&gt;SELECT&lt;/code&gt;, &lt;code&gt;SHOW&lt;/code&gt;, &lt;code&gt;DESCRIBE&lt;/code&gt;, &lt;code&gt;EXPLAIN&lt;/code&gt;, &lt;code&gt;WITH&lt;/code&gt;, &lt;code&gt;PRAGMA&lt;/code&gt;. On Redis, only read commands. On MongoDB, an aggregation with &lt;code&gt;$out&lt;/code&gt; or &lt;code&gt;$merge&lt;/code&gt; is blocked. On Neo4j, &lt;code&gt;CREATE&lt;/code&gt;, &lt;code&gt;MERGE&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;, &lt;code&gt;SET&lt;/code&gt;, &lt;code&gt;DROP&lt;/code&gt; are blocked. A write only goes through if you've raised &lt;em&gt;that specific connection's&lt;/em&gt; agent policy to full access — a per-connection decision, not a global one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the property you wanted holds. The agent can answer "why did signups drop on Tuesday?" by reading the rows. It cannot delete Tuesday.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "read-only by default" actually gets you, honestly
&lt;/h2&gt;

&lt;p&gt;It's a query gate, and it's worth being precise about its edges. It stops the obvious destructive path — the accidental or injected &lt;code&gt;DELETE&lt;/code&gt;, &lt;code&gt;DROP&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, the aggregation that writes back out. What it is &lt;em&gt;not&lt;/em&gt; is a substitute for a properly scoped database role: a &lt;code&gt;SELECT&lt;/code&gt; can still read data you'd rather it didn't. For anything sensitive, do both — point the Data connection at a read replica or a least-privilege role &lt;em&gt;and&lt;/em&gt; leave the agent read-only. The client-side gate catches the mistakes; the database-side grant sets the ceiling. And because the agent works through your running app, no connection string is written anywhere it can read, and the whole thing switches off with one toggle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to start
&lt;/h2&gt;

&lt;p&gt;Start read-only, with one connection you don't mind an agent poking at — a staging database, an analytics replica, a copy. Leave the per-connection policy where it defaults: read-only. Ask the agent something real — describe a table, count the stuck orders, explain the slow query — and watch how it forms its SQL before you consider granting anything more. If you later want it to write, raise exactly one connection to full access, and know precisely what you traded.&lt;/p&gt;

&lt;p&gt;That's the quiet payoff of the custodian model, whether it's &lt;a href="https://termal.in/blog/manage-servers-from-claude-chatgpt/" rel="noopener noreferrer"&gt;shells&lt;/a&gt; or schemas: you expand one connection at a time, because no step you take is one you can't take back.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Termalin is a free, cross-platform SSH and database client with a built-in MCP server, a key custodian and per-connection agent policy — &lt;a href="https://termal.in/download/" rel="noopener noreferrer"&gt;download it&lt;/a&gt;, or read how it &lt;a href="https://termal.in/security/" rel="noopener noreferrer"&gt;handles credentials safely&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>mcp</category>
      <category>database</category>
      <category>security</category>
    </item>
    <item>
      <title>GitHub / GitLab SSH key not working: the fix, step by step</title>
      <dc:creator>Svyatoslav Pavlov</dc:creator>
      <pubDate>Thu, 03 Sep 2026 08:57:05 +0000</pubDate>
      <link>https://dev.to/wolfhound1995/github-gitlab-ssh-key-not-working-the-fix-step-by-step-aca</link>
      <guid>https://dev.to/wolfhound1995/github-gitlab-ssh-key-not-working-the-fix-step-by-step-aca</guid>
      <description>&lt;p&gt;An SSH key that won't authenticate to GitHub or GitLab is one of the most-Googled developer problems there is, and half the time the key is fine — the diagnosis is just wrong. So before you regenerate anything, run one test and learn to read its answer. Then work the culprits in order of how often they're actually the cause.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 0: the test that tells you if it's even broken
&lt;/h2&gt;

&lt;p&gt;Ask the git host directly whether it recognizes your key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh -T git@github.com        # GitHub
ssh -T git@gitlab.com        # GitLab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now read the response &lt;em&gt;carefully&lt;/em&gt;, because the success message looks like an error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hi &amp;lt;username&amp;gt;! You've successfully authenticated, but GitHub does not
provide shell access.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;That is success.&lt;/strong&gt; "Does not provide shell access" is not a failure — GitHub and GitLab don't give you a shell, they only speak git over SSH, so they authenticate you and then hang up. People see "does not provide" and start troubleshooting a working setup. If you see your username in that line, your key works; your problem is somewhere else (jump to the &lt;em&gt;HTTPS remote&lt;/em&gt; culprit below — that's the usual real issue).&lt;/p&gt;

&lt;p&gt;The genuine failure looks like this instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git@github.com: Permission denied (publickey).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the one we're here to fix. Note the endpoint: &lt;code&gt;Permission denied (publickey)&lt;/code&gt; means the server accepted no key you offered. Work down the list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Culprit 1: the key isn't added to your account
&lt;/h2&gt;

&lt;p&gt;The most common real cause. A key on your laptop does nothing until its &lt;strong&gt;public&lt;/strong&gt; half is registered with the git host. Copy the public key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat ~/.ssh/id_ed25519.pub     # note the .pub — this is the safe half to share
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste that into &lt;strong&gt;GitHub → Settings → SSH and GPG keys → New SSH key&lt;/strong&gt;, or &lt;strong&gt;GitLab → Preferences → SSH Keys&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;One rule, no exceptions: &lt;strong&gt;paste the &lt;code&gt;.pub&lt;/code&gt; file, never the file without &lt;code&gt;.pub&lt;/code&gt;.&lt;/strong&gt; The public key is one line starting &lt;code&gt;ssh-ed25519 AAAA…&lt;/code&gt; (or &lt;code&gt;ssh-rsa AAAA…&lt;/code&gt;). If what you're about to paste starts with &lt;code&gt;-----BEGIN OPENSSH PRIVATE KEY-----&lt;/code&gt;, stop — that's your private key, the secret that &lt;em&gt;is&lt;/em&gt; your identity. Pasting it into a web form hands your credential to anyone who can read that page. Share the public half; guard the private half.&lt;/p&gt;

&lt;p&gt;(No key yet? Generate one — &lt;a href="https://termal.in/blog/ssh-key-types-ed25519-vs-rsa/" rel="noopener noreferrer"&gt;Ed25519 in almost all cases&lt;/a&gt;: &lt;code&gt;ssh-keygen -t ed25519 -C "you@device"&lt;/code&gt;.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Culprit 2: the wrong key is being offered
&lt;/h2&gt;

&lt;p&gt;Your key is registered, but SSH is offering a &lt;em&gt;different&lt;/em&gt; one — common when you carry several keys and the agent tries them in an order the server rejects. See exactly which key is sent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh -vT git@github.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look for the &lt;code&gt;Offering public key:&lt;/code&gt; lines. If the key you registered isn't among them, or a wrong one is being accepted, pin the right key per host in &lt;code&gt;~/.ssh/config&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Host github.com
    HostName github.com
    User git
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;IdentitiesOnly yes&lt;/code&gt; is the key line — it tells SSH to offer &lt;em&gt;only&lt;/em&gt; the &lt;code&gt;IdentityFile&lt;/code&gt; you named and stop spraying every key in your agent at the server (which also cures &lt;code&gt;Too many authentication failures&lt;/code&gt;). Do the same with &lt;code&gt;Host gitlab.com&lt;/code&gt; for GitLab. More on structuring the file in the &lt;a href="https://termal.in/blog/ssh-config-file-guide/" rel="noopener noreferrer"&gt;ssh_config guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Culprit 3: the agent isn't running, or the key isn't loaded
&lt;/h2&gt;

&lt;p&gt;If your key has a passphrase and the agent doesn't hold it, SSH may skip it silently. Check what the agent has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh-add -l
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;The agent has no identities&lt;/code&gt; (or &lt;code&gt;Could not open a connection to your authentication agent&lt;/code&gt;) means you need to start it and load the key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eval "$(ssh-agent -s)"        # start the agent in this shell
ssh-add ~/.ssh/id_ed25519     # load the key (prompts for the passphrase once)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ssh-add -l&lt;/code&gt; should now list your key's fingerprint. (For what the agent actually &lt;em&gt;is&lt;/em&gt; and how it holds keys, see &lt;a href="https://termal.in/blog/what-is-the-ssh-agent/" rel="noopener noreferrer"&gt;what is the SSH agent&lt;/a&gt;.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Culprit 4: you're on an HTTPS remote, not an SSH one
&lt;/h2&gt;

&lt;p&gt;This is the culprit behind "my key works (&lt;code&gt;ssh -T&lt;/code&gt; succeeded) but &lt;code&gt;git push&lt;/code&gt; still asks for a password." Your repository is configured to use HTTPS, which ignores SSH keys entirely and wants a token. Check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git remote -v
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you see &lt;code&gt;https://github.com/you/repo.git&lt;/code&gt;, that's the problem — HTTPS never touches your SSH key. Switch the remote to SSH:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git remote set-url origin git@github.com:you/repo.git      # GitHub
git remote set-url origin git@gitlab.com:you/repo.git      # GitLab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;git remote -v&lt;/code&gt; again to confirm it now starts with &lt;code&gt;git@&lt;/code&gt;. This single fix resolves a huge share of "SSH key not working" reports where the key was fine all along.&lt;/p&gt;

&lt;h2&gt;
  
  
  Culprit 5: permissions on ~/.ssh
&lt;/h2&gt;

&lt;p&gt;SSH refuses to use keys and config that others could tamper with. If verbose output mentions bad modes, or keys are being ignored for no clear reason:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519          # private key
chmod 644 ~/.ssh/id_ed25519.pub      # public key
chmod 600 ~/.ssh/config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows this surfaces after copying keys off a synced drive or between machines; fix it via the file's Security properties, or keep keys out of cloud-synced folders.&lt;/p&gt;

&lt;h2&gt;
  
  
  Culprit 6: passphrase prompts every time
&lt;/h2&gt;

&lt;p&gt;If every &lt;code&gt;git&lt;/code&gt; operation demands your key passphrase, the agent isn't caching it. Load it once with &lt;code&gt;ssh-add&lt;/code&gt; (culprit 3), and make it stick across sessions by adding to &lt;code&gt;~/.ssh/config&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Host github.com gitlab.com
    AddKeysToAgent yes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On macOS, &lt;code&gt;ssh-add --apple-use-keychain ~/.ssh/id_ed25519&lt;/code&gt; stores the passphrase in Keychain so it survives reboots.&lt;/p&gt;

&lt;h2&gt;
  
  
  Culprit 7: two accounts, one host
&lt;/h2&gt;

&lt;p&gt;Work and personal GitHub accounts can't share one key — a given public key belongs to exactly one account. Give each its own key and a &lt;code&gt;Host&lt;/code&gt; alias that selects it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Host github-work
    HostName github.com
    User git
    IdentityFile ~/.ssh/work_ed25519
    IdentitiesOnly yes

Host github-personal
    HostName github.com
    User git
    IdentityFile ~/.ssh/personal_ed25519
    IdentitiesOnly yes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then clone or set the remote with the &lt;em&gt;alias&lt;/em&gt; as the host: &lt;code&gt;git@github-work:org/repo.git&lt;/code&gt;. SSH matches &lt;code&gt;github-work&lt;/code&gt;, picks the work key, and rewrites the connection to &lt;code&gt;github.com&lt;/code&gt; under the hood.&lt;/p&gt;

&lt;h2&gt;
  
  
  Culprit 8: organization SSO not authorized (GitHub)
&lt;/h2&gt;

&lt;p&gt;You authenticate fine to your own repos but get &lt;code&gt;Permission denied&lt;/code&gt; on an org's repos behind SAML SSO. The key exists but isn't authorized for that organization. Go to &lt;strong&gt;Settings → SSH and GPG keys&lt;/strong&gt;, find the key, click &lt;strong&gt;Configure SSO&lt;/strong&gt;, and authorize it for the organization. GitLab has a parallel case where an &lt;em&gt;expired&lt;/em&gt; key is silently rejected — check the key's expiry date in &lt;strong&gt;SSH Keys&lt;/strong&gt; if a previously working key suddenly stops.&lt;/p&gt;

&lt;h2&gt;
  
  
  When it's still &lt;code&gt;Permission denied (publickey)&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;If you've worked the list and still hit the endpoint, you've narrowed it to a genuine key-acceptance problem — the systematic loop for that exact error (reading &lt;code&gt;ssh -vvv&lt;/code&gt;, the server's method list, offered vs. accepted keys) is in &lt;a href="https://termal.in/blog/ssh-permission-denied-publickey/" rel="noopener noreferrer"&gt;permission denied (publickey), a fast systematic fix&lt;/a&gt;, and the broader map of SSH errors is in &lt;a href="https://termal.in/blog/ssh-errors-decoded/" rel="noopener noreferrer"&gt;SSH errors decoded&lt;/a&gt;. But for git hosts specifically, the order above catches nearly everything: register the public key, offer the right one, load it into the agent, and make sure you're actually on an SSH remote.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where a client removes the setup entirely
&lt;/h2&gt;

&lt;p&gt;Most of this friction is bookkeeping — which key belongs to which host, whether it's loaded, whether the remote is even SSH. &lt;a href="https://termal.in/features/" rel="noopener noreferrer"&gt;Termalin&lt;/a&gt; keeps the key with the host it belongs to, unlocks your keys once through a built-in agent so passphrases aren't retyped (and a cancelled prompt never silently skips a key), and — because it speaks the standard ssh-agent protocol — git and your CLI can sign through that same agent. Fingerprints are visible per key, so registering the right public half with GitHub or GitLab is copy-and-paste rather than a hunt through five &lt;code&gt;~/.ssh&lt;/code&gt; folders. The account-and-alias juggling above becomes a per-host setting instead of a config-file puzzle.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Termalin is a free, cross-platform SSH client that manages your keys with a one-unlock agent git can sign through — &lt;a href="https://termal.in/download/" rel="noopener noreferrer"&gt;download it&lt;/a&gt;, or read how it &lt;a href="https://termal.in/security/" rel="noopener noreferrer"&gt;handles keys safely&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ssh</category>
      <category>git</category>
      <category>troubleshooting</category>
      <category>sshkeys</category>
    </item>
  </channel>
</rss>
