<?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: Nexus Shell</title>
    <description>The latest articles on DEV Community by Nexus Shell (@nexusshell).</description>
    <link>https://dev.to/nexusshell</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%2F4058955%2Fcae7e979-e0c6-4863-8788-35cad3cefca8.png</url>
      <title>DEV Community: Nexus Shell</title>
      <link>https://dev.to/nexusshell</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nexusshell"/>
    <language>en</language>
    <item>
      <title>Why ExitOnForwardFailure=yes does not prove your database is reachable</title>
      <dc:creator>Nexus Shell</dc:creator>
      <pubDate>Thu, 17 Sep 2026 03:12:31 +0000</pubDate>
      <link>https://dev.to/nexusshell/why-exitonforwardfailureyes-does-not-prove-your-database-is-reachable-4o22</link>
      <guid>https://dev.to/nexusshell/why-exitonforwardfailureyes-does-not-prove-your-database-is-reachable-4o22</guid>
      <description>&lt;p&gt;An SSH tunnel can be listening on your laptop while the service behind it is unavailable. &lt;code&gt;ExitOnForwardFailure=yes&lt;/code&gt; is useful, but it does not turn the SSH process into a database health check.&lt;/p&gt;

&lt;p&gt;This distinction matters when a PostgreSQL client says “connection failed” even though &lt;code&gt;ssh -N&lt;/code&gt; is still running. Before changing credentials, separate the local listener, the SSH transport, the destination connection and the database protocol.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small experiment, with measured results
&lt;/h2&gt;

&lt;p&gt;On September 17, 2026, a loopback-only test ran on macOS with OpenSSH 10.3p1, Node.js 25.6.1 and &lt;code&gt;ssh2&lt;/code&gt; 1.17.0. It used the system OpenSSH client, an in-process SSH server and a tiny HTTP target. It did not connect to a VPS or run PostgreSQL.&lt;/p&gt;

&lt;p&gt;The client used local forwarding and &lt;code&gt;ExitOnForwardFailure=yes&lt;/code&gt;. The SSH server attempted an actual TCP connection to a closed loopback port; the operating system returned &lt;code&gt;ECONNREFUSED&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Change in the test&lt;/th&gt;
&lt;th&gt;Observed result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Connect to the local forwarding port while the target is stopped&lt;/td&gt;
&lt;td&gt;The local TCP connection succeeds; the target connection fails; the SSH process remains running&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Start the HTTP target, keeping the same SSH process&lt;/td&gt;
&lt;td&gt;A new request through the existing tunnel returns &lt;code&gt;fixture-ok&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Start a second SSH process with the same local listening port&lt;/td&gt;
&lt;td&gt;The second process reports &lt;code&gt;Address already in use&lt;/code&gt; and exits with code 255&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The test asserts the process state and response body; these are not inferred from the presence of a terminal window.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://man.openbsd.org/ssh_config#ExitOnForwardFailure" rel="noopener noreferrer"&gt;OpenSSH configuration manual&lt;/a&gt; describes this boundary: failure to establish a forwarding listener is different from a later failure to connect to its destination. Keep the option enabled, but test the service separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Map the addresses before changing anything
&lt;/h2&gt;

&lt;p&gt;For a database reachable from an SSH gateway, an illustrative command is:&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;-N&lt;/span&gt; &lt;span class="nt"&gt;-T&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;ExitOnForwardFailure&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;yes&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-L&lt;/span&gt; 127.0.0.1:15432:db.internal:5432 &lt;span class="se"&gt;\&lt;/span&gt;
  developer@gateway.example
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it in a local terminal. Replace the example names with your own authorized connection details, verify the host key, and leave the process running.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Database client on the Mac
  -&amp;gt; 127.0.0.1:15432 on the Mac
  -&amp;gt; SSH connection to gateway.example
  -&amp;gt; db.internal:5432, reached from the gateway
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The database client uses host &lt;code&gt;127.0.0.1&lt;/code&gt; and port &lt;code&gt;15432&lt;/code&gt;. The gateway resolves and connects to &lt;code&gt;db.internal&lt;/code&gt;. If the forwarding destination is instead &lt;code&gt;127.0.0.1:5432&lt;/code&gt;, that loopback address belongs to the SSH server's network context, not your Mac. &lt;a href="https://man.openbsd.org/ssh#L" rel="noopener noreferrer"&gt;OpenSSH's local-forwarding reference&lt;/a&gt; specifies where these connections originate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check the layer that actually failed
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Evidence&lt;/th&gt;
&lt;th&gt;What to investigate next&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SSH cannot authenticate&lt;/td&gt;
&lt;td&gt;SSH account, key selection and server authentication policy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Address already in use&lt;/code&gt; on the Mac&lt;/td&gt;
&lt;td&gt;Another local listener; choose a different unused local port and update the database client&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local TCP connection succeeds, followed by a forwarding-channel error&lt;/td&gt;
&lt;td&gt;Destination hostname, port, service listener, routing and forwarding policy on the SSH side&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PostgreSQL reports an authentication error&lt;/td&gt;
&lt;td&gt;The connection reached a PostgreSQL server; inspect database identity and authentication rules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TLS certificate verification fails&lt;/td&gt;
&lt;td&gt;Database hostname and TLS configuration; do not disable verification to make the error disappear&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A successful local TCP probe alone cannot verify a query, the database identity, or authentication. Use your normal database client with the database credentials and required TLS settings for the final check.&lt;/p&gt;

&lt;p&gt;SSH authentication and database authentication are separate. If the gateway connects onward to another machine, the SSH encryption ends at the gateway; database TLS may still be required for the remaining hop. PostgreSQL documents this distinction in its &lt;a href="https://www.postgresql.org/docs/current/ssh-tunnels.html" rel="noopener noreferrer"&gt;SSH tunneling guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduce the transport test
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://gist.github.com/viewer12/3a6e6d85dd0c4a6ec0104c00dbe56468" rel="noopener noreferrer"&gt;complete test script and assertions are in this public Gist&lt;/a&gt;. Inspect it before running it. Save &lt;code&gt;tunnel-lab.cjs&lt;/code&gt; into a fresh temporary directory, then run these commands from that directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--ignore-scripts&lt;/span&gt; &lt;span class="nt"&gt;--omit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;optional &lt;span class="nt"&gt;--no-audit&lt;/span&gt; &lt;span class="nt"&gt;--no-fund&lt;/span&gt; ssh2@1.17.0
node tunnel-lab.cjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected output from the tested environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PASS: local TCP connect succeeded; target returned ECONNREFUSED; SSH remained running.
PASS: the same SSH process forwarded an HTTP response after the target started.
PASS: a second tunnel with the occupied local port exited 255.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fixture binds only to &lt;code&gt;127.0.0.1&lt;/code&gt;, pins its generated host key in a temporary known-hosts file, and ignores your SSH configuration and authentication agent. It closes its child processes and removes that temporary host-key file afterward. The temporary package directory remains yours to remove.&lt;/p&gt;

&lt;p&gt;The miniature server accepts authentication solely for this local experiment and permits forwarding to one fixed loopback destination. It offers no shell and is not a deployable SSH server. Do not expose it to a network. The test establishes TCP-forwarding behavior only; it does not test PostgreSQL authentication, TLS, a production OpenSSH server's policy or a desktop SSH application's implementation.&lt;/p&gt;

&lt;p&gt;Disclosure: This report and its code were generated by Codex. Codex executed the local test and checked the technical explanation against the linked upstream documentation. It is an automated experiment report, not a claim of a human production incident.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>database</category>
      <category>networking</category>
      <category>debugging</category>
    </item>
    <item>
      <title>Choosing an SFTP Workflow on Mac: CLI, Finder Mount, or Dual-Pane Client?</title>
      <dc:creator>Nexus Shell</dc:creator>
      <pubDate>Tue, 18 Aug 2026 02:11:39 +0000</pubDate>
      <link>https://dev.to/nexusshell/choosing-an-sftp-workflow-on-mac-cli-finder-mount-or-dual-pane-client-l87</link>
      <guid>https://dev.to/nexusshell/choosing-an-sftp-workflow-on-mac-cli-finder-mount-or-dual-pane-client-l87</guid>
      <description>&lt;p&gt;Moving a file to a Linux server from a Mac sounds like a one-command task. In practice, the right workflow depends on what happens around the transfer: whether you need repeatability, visual confirmation, remote editing, or a terminal open beside the files.&lt;/p&gt;

&lt;p&gt;There are three sensible approaches. None is universally best.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Use the built-in &lt;code&gt;sftp&lt;/code&gt; command
&lt;/h2&gt;

&lt;p&gt;macOS already includes the OpenSSH SFTP client. For a quick transfer, the command line has the smallest setup cost:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sftp &lt;span class="nt"&gt;-i&lt;/span&gt; ~/.ssh/id_ed25519 deploy@example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside the session, &lt;code&gt;put&lt;/code&gt;, &lt;code&gt;get&lt;/code&gt;, &lt;code&gt;ls&lt;/code&gt;, &lt;code&gt;cd&lt;/code&gt;, and &lt;code&gt;lcd&lt;/code&gt; cover the basics. It also supports jump hosts, alternate SSH config files, recursive transfers, resuming partial transfers, and batch mode.&lt;/p&gt;

&lt;p&gt;The CLI is usually the right choice when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the transfer belongs in a script or runbook;&lt;/li&gt;
&lt;li&gt;you already know the exact remote path;&lt;/li&gt;
&lt;li&gt;the same command should be repeatable in CI or from another machine;&lt;/li&gt;
&lt;li&gt;you want the full behavior of your OpenSSH configuration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Its weakness is not capability. It is context. When you are comparing several local and remote files, checking timestamps, or moving a group of items between changing directories, remembering which side of the session each path refers to becomes the work.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Mount the server as a drive
&lt;/h2&gt;

&lt;p&gt;A mounted workflow makes the remote directory appear in Finder. This is useful when the remote files should behave like ordinary Mac files and several local apps need to open them.&lt;/p&gt;

&lt;p&gt;Finder's built-in Connect to Server documentation lists SMB, NFS, FTP, and WebDAV, but not SFTP. An SFTP mount therefore needs an additional tool or filesystem layer.&lt;/p&gt;

&lt;p&gt;A mount is a good fit when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the remote directory should stay available for a long working session;&lt;/li&gt;
&lt;li&gt;Finder, an IDE, and other apps all need access to the same files;&lt;/li&gt;
&lt;li&gt;browsing is more important than seeing individual transfer jobs;&lt;/li&gt;
&lt;li&gt;the connection is stable enough to behave like a filesystem.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The trade-off is that network failure now appears as filesystem behavior. Sleep, reconnects, caching, and partial writes deserve more attention than they do in an explicit upload/download workflow. For production configuration files, it is worth confirming how the mount handles interrupted saves and conflicts before relying on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Use a dual-pane SFTP client
&lt;/h2&gt;

&lt;p&gt;A dual-pane client keeps the local and remote directories visible at the same time. Transfers remain explicit jobs instead of being hidden behind filesystem operations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F34bead97feah75bcoq0a.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F34bead97feah75bcoq0a.webp" width="800" height="525"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This model works well when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you regularly move groups of files between a project folder and a server;&lt;/li&gt;
&lt;li&gt;you want visible progress, cancellation, and conflict handling;&lt;/li&gt;
&lt;li&gt;remote editing needs to upload the saved file back to the correct path;&lt;/li&gt;
&lt;li&gt;you switch between terminal commands and file operations on the same host.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The main cost is another interface to learn. It is also less suitable than the CLI for automation, and less transparent than a mount when many unrelated Mac apps need continuous access to the remote tree.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical way to choose
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Best starting point&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;One file, known path&lt;/td&gt;
&lt;td&gt;Built-in &lt;code&gt;sftp&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scripted or repeatable transfer&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;sftp&lt;/code&gt; batch mode, &lt;code&gt;scp&lt;/code&gt;, or &lt;code&gt;rsync&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Remote files should behave like a Mac drive&lt;/td&gt;
&lt;td&gt;SFTP mount&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frequent visual upload/download work&lt;/td&gt;
&lt;td&gt;Dual-pane SFTP client&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terminal and remote files are used together&lt;/td&gt;
&lt;td&gt;SSH client with integrated SFTP&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I keep the CLI available even when using a GUI. A good desktop client should shorten repetitive work, not make the underlying protocol mysterious.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Nexus Shell fits
&lt;/h2&gt;

&lt;p&gt;I develop &lt;a href="https://nexusshell.app/en/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=mac_sftp_workflow_aug2026" rel="noopener noreferrer"&gt;Nexus Shell&lt;/a&gt;, a native macOS SSH client. Its file view uses the third model: local and remote directories side by side, drag-and-drop and multi-select transfers, visible transfer progress, conflict handling, remote editing, and a terminal for the same saved server.&lt;/p&gt;

&lt;p&gt;It is deliberately Mac-only and requires Apple Silicon with macOS 14.2 or later. If you need cross-platform clients, a Finder mount, or a workflow that can be fully scripted, one of the other approaches above is likely a better fit. The Free tier covers basic personal SSH use; creating an account includes a seven-day Pro trial for testing the complete file workflow.&lt;/p&gt;

&lt;p&gt;Sources: &lt;a href="https://support.apple.com/guide/terminal/connect-to-servers-trml1018/mac" rel="noopener noreferrer"&gt;Apple Terminal User Guide&lt;/a&gt;, &lt;a href="https://support.apple.com/guide/mac-help/servers-shared-computers-connect-mac-mchlp3015/mac" rel="noopener noreferrer"&gt;Apple's supported Finder server types&lt;/a&gt;, and the &lt;a href="https://manp.gs/mac/1/sftp" rel="noopener noreferrer"&gt;macOS &lt;code&gt;sftp(1)&lt;/code&gt; manual&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>macos</category>
      <category>ssh</category>
      <category>devops</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Fixing macOS "No route to host" when an SSH app never asks for Local Network access</title>
      <dc:creator>Nexus Shell</dc:creator>
      <pubDate>Wed, 05 Aug 2026 15:43:46 +0000</pubDate>
      <link>https://dev.to/nexusshell/fixing-macos-no-route-to-host-when-an-ssh-app-never-asks-for-local-network-access-11na</link>
      <guid>https://dev.to/nexusshell/fixing-macos-no-route-to-host-when-an-ssh-app-never-asks-for-local-network-access-11na</guid>
      <description>&lt;p&gt;While testing an SSH app on newer macOS versions, I hit a confusing failure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connecting to public hosts worked.&lt;/li&gt;
&lt;li&gt;Connecting to a private IP or a &lt;code&gt;.local&lt;/code&gt; host returned &lt;code&gt;No route to host&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;macOS never showed the Local Network permission prompt.&lt;/li&gt;
&lt;li&gt;The app did not appear under &lt;strong&gt;System Settings &amp;gt; Privacy &amp;amp; Security &amp;gt; Local Network&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The app already had &lt;code&gt;NSLocalNetworkUsageDescription&lt;/code&gt; in its &lt;code&gt;Info.plist&lt;/code&gt;. That string explains the permission, but it does not itself trigger the TCC check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the prompt can be missed
&lt;/h2&gt;

&lt;p&gt;The SSH connection in this app is handled by a spawned system &lt;code&gt;ssh&lt;/code&gt; process. In some macOS environments, relying on traffic from that child process did not reliably make the parent app appear in the Local Network privacy list.&lt;/p&gt;

&lt;p&gt;The practical fix was to let the app process itself briefly touch the local endpoint before starting the normal SSH flow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Network&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;triggerLocalNetworkPermission&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UInt16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;nwPort&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;NWEndpoint&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Port&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;rawValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;NWConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nv"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;NWEndpoint&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;nwPort&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;using&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tcp&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stateUpdateHandler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;global&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;qos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utility&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;global&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;qos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utility&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asyncAfter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This connection is only a permission trigger. Its result should not decide whether SSH is allowed to continue; the real connection still owns success, authentication, host-key verification, and error handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Only probe local destinations
&lt;/h2&gt;

&lt;p&gt;Do not create an extra connection for every host. I limit the trigger to destinations that can be identified locally:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RFC 1918 IPv4 ranges: &lt;code&gt;10/8&lt;/code&gt;, &lt;code&gt;172.16/12&lt;/code&gt;, &lt;code&gt;192.168/16&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;IPv4 link-local: &lt;code&gt;169.254/16&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;mDNS names ending in &lt;code&gt;.local&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;IPv6 link-local and unique-local addresses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also cache the host in memory so it is touched only once per app launch.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few implementation details that matter
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Keep &lt;code&gt;NSLocalNetworkUsageDescription&lt;/code&gt;; the in-process connection complements it rather than replacing it.&lt;/li&gt;
&lt;li&gt;Start the trigger before every real connection path converges, so terminal, file, and monitoring flows behave consistently.&lt;/li&gt;
&lt;li&gt;Cancel quickly. If the permission sheet is waiting for user input, the connection can remain in &lt;code&gt;preparing&lt;/code&gt; or &lt;code&gt;waiting&lt;/code&gt;, so a timeout is still needed.&lt;/li&gt;
&lt;li&gt;Avoid claiming that every &lt;code&gt;No route to host&lt;/code&gt; error is a permission issue. Routing, VPNs, firewalls, and offline servers can produce the same message.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This came from a real fix in &lt;a href="https://github.com/viewer12/Nexus-Shell-Releases/releases/tag/v1.6.7" rel="noopener noreferrer"&gt;Nexus Shell v1.6.7&lt;/a&gt;, the native macOS SSH app I develop. The same pattern may help other macOS apps that delegate local-network work to child processes.&lt;/p&gt;

</description>
      <category>swift</category>
      <category>networking</category>
      <category>ssh</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
