Engineering note

Connect a local API to a cloud Mac with an SSH reverse tunnel

Remote Mac ·~6 min read

Connect a local API to a cloud Mac with an SSH reverse tunnel

You have an API running on your development computer that has not been deployed yet, while the Xcode project and build scripts run on a cloud Mac. Opening a router port is slow and increases the attack surface, while temporarily replacing repository URLs with a public domain can easily leak into committed configuration. A safer approach is to initiate an SSH connection from the development computer and use a reverse tunnel to map the local port to the cloud Mac’s loopback interface.

Confirm the traffic direction first

A reverse tunnel allows the cloud Mac to access a local service. Suppose the API runs at 127.0.0.1:8080 on the development computer, and you want the cloud Mac to reach it through 127.0.0.1:18080. The development computer initiates the SSH connection to the cloud Mac, and traffic received on the cloud-side port is carried back to the local port.

Local forwarding works in the opposite direction: the development computer needs to access a debugging service that listens only on the cloud Mac’s loopback interface. For example, a cloud service at 127.0.0.1:9000 can be mapped to 127.0.0.1:19000 on the development computer.

Goal SSH option Where the connection starts Access endpoint
Access a local API from the cloud Mac -R Development computer Cloud 127.0.0.1:18080
Access a cloud debugging service locally -L Development computer Local 127.0.0.1:19000

Before troubleshooting the tunnel, use a real client to confirm that the source service is available:

curl -i http://127.0.0.1:8080/health
lsof -nP -iTCP:8080 -sTCP:LISTEN

If the application does not provide a health-check endpoint, temporarily run a test server that listens only on the local machine:

python3 -m http.server 8080 --bind 127.0.0.1

Create a minimally exposed reverse tunnel

Set the cloud address on the development computer, then run:

export CLOUD_MAC_IP="你的节点地址"
ssh -N \
  -o ExitOnForwardFailure=yes \
  -o ServerAliveInterval=30 \
  -o ServerAliveCountMax=3 \
  -R 127.0.0.1:18080:127.0.0.1:8080 \
  dev@"$CLOUD_MAC_IP"

-N tells SSH not to run a remote command, so the connection is used only for forwarding. ExitOnForwardFailure prevents SSH from leaving behind a session that appears healthy when the port could not be bound. The two keepalive options help detect dead connections, but they do not automatically recreate the tunnel after a network interruption.

Open another terminal on the cloud Mac and verify the tunnel:

lsof -nP -iTCP:18080 -sTCP:LISTEN
curl -i http://127.0.0.1:18080/health

In the lsof output, the listening address should be 127.0.0.1:18080, not *:18080. Put the integration endpoint in a development environment variable rather than hard-coding it in the application source:

export DEV_API_BASE_URL="http://127.0.0.1:18080"
xcodebuild -scheme DemoApp -configuration Debug test

The tunnel encrypts traffic and restricts the entry point, but it does not replace the API’s own authentication. Even when the port listens only on the loopback interface, retain test tokens, authorization checks, and redacted logging.

Use SSH configuration to reduce mistakes

Repeatedly typing a long command makes it easy to reverse the local port, remote port, or destination address. Create a dedicated entry in ~/.ssh/config on the development computer:

Host minid-api-tunnel
    HostName CLOUD_MAC_IP
    User dev
    RemoteForward 127.0.0.1:18080 127.0.0.1:8080
    ExitOnForwardFailure yes
    ServerAliveInterval 30
    ServerAliveCountMax 3

Replace CLOUD_MAC_IP with the actual node address and ensure that the configuration file has the correct permissions:

chmod 700 ~/.ssh
chmod 600 ~/.ssh/config
ssh -N minid-api-tunnel

Do not overload one entry with many unrelated forwards. Use one alias per project and assign port ranges by purpose—for example, 18080 for an API and 19000 for a debugging dashboard. This makes it easier to identify the associated project from the listening port and reduces the chance of conflicts when multiple people share a node.

When you need to access a cloud service from the development computer, create a separate local forward:

ssh -N \
  -o ExitOnForwardFailure=yes \
  -L 127.0.0.1:19000:127.0.0.1:9000 \
  dev@"$CLOUD_MAC_IP"

Then access http://127.0.0.1:19000 only from the development computer. Neither forwarding method requires the application service to listen on a public network interface.

Troubleshoot connection failures by layer

Check tunnel issues in three layers—source service, SSH forwarding, and destination client—to avoid changing firewall rules, application configuration, and project code all at once.

Source service layer

Run curl and lsof on the development computer. If the API listens only on the IPv6 address ::1 while the forwarding destination is 127.0.0.1, the connection will be refused. Use the same address family on both sides or change the forwarding destination to the actual listening address. Also confirm that a local proxy is not intercepting loopback traffic. You can temporarily run:

curl --noproxy '*' -i http://127.0.0.1:8080/health

SSH forwarding layer

Enable verbose output to inspect the port-forwarding request:

ssh -vv -N \
  -o ExitOnForwardFailure=yes \
  -R 127.0.0.1:18080:127.0.0.1:8080 \
  dev@"$CLOUD_MAC_IP"

If SSH reports that remote port forwarding failed, first try an unused port. If it still fails, confirm that the SSH service on the cloud Mac allows TCP forwarding. Do not switch to a public listening address for convenience, and do not broadly allow connections from every source.

Destination client layer

A successful curl from a cloud terminal does not mean every runtime target automatically shares the same network conditions. Build scripts, GUI applications, and simulators may read different environment configurations, and HTTP requests may also be affected by project security policies. Record the calling process, destination URL, response code, and timeout separately before deciding whether the failure is in the network or the application layer.

Preflight checks and cleanup

After integration testing, stop any test tasks that depend on the tunnel before closing the SSH session. Use lsof to confirm that the mapped port is gone, then remove temporary endpoints, test tokens, and debug logs from the project.

Before committing code, check at least the following:

  • The API still listens only on the development computer’s loopback interface.
  • The mapped port on the cloud Mac listens only on 127.0.0.1.
  • SSH private-key permissions are 600, and the configuration directory permissions are 700.
  • The project selects the integration endpoint through an environment variable, and release configuration does not reference the port.
  • Logs contain no request tokens, keys, complete user data, or authentication headers.
  • For shared projects, the port number, responsible person, and end time have been documented.

This approach works well for temporary APIs, callback validation, and remote build integration. If a service must be accessed by multiple nodes over the long term, use a proper deployment with independent authentication, access control, and audit logs instead of treating a temporary tunnel as a production endpoint.

Frequently asked questions

Does an SSH reverse tunnel expose the local API to the internet?

Not when the remote listener is explicitly bound to 127.0.0.1. The port then remains reachable only from the cloud Mac. Avoid binding it to 0.0.0.0 and verify the listener with lsof.

What should I check if SSH connects but the forwarded port does not respond?

Confirm that the source API is listening on the development computer, then inspect the cloud Mac port with lsof. A port conflict or disabled TCP forwarding on the SSH server is usually responsible.

Can an app launched from Xcode use the tunnel address directly?

Build scripts running on the cloud Mac can use the tunnel loopback address. Simulators and applications also require checks for their network context and the project’s HTTP security policy.

MiniD Cloud Mac

Rent a dedicated physical Mac mini by the day, week, or month

Every plan runs on a dedicated physical Mac mini with remote desktop and SSH access; current models, regions, and terms are shown on the order page.

View ordering options