← Back to dashboard
remote-accesstoolingfresh

Remote Access Integration Guide

What is remote access?

The real model

Identity over network position: keys, least-privilege tunnels, zero-trust mesh, audit.

The design principle is identity over location — prove who you are (Ed25519 keys, MFA, an IdP) and open the minimum path. SSH `-L`/`-R` forward arbitrary TCP through an encrypted channel; `ProxyJump` chains through a bastion so only one box has a public port. ngrok / `cloudflared` expose localhost over HTTPS — the exact Daraja-callback workflow, since Safaricom only POSTs to public HTTPS URLs. Tailscale/WireGuard replace open ports with an identity-gated mesh — ideal for administering a fleet of Raspberry Pis with SSH never touching the public internet.

Six remote-access primitives

Pick by the shape of the problem: shell, expose-localhost, private mesh, or desktop.

Text
██████╗ ███████╗███╗   ███╗ ██████╗ ████████╗███████╗     █████╗  ██████╗ ██████╗███████╗███████╗███████╗
██╔══██╗██╔════╝████╗ ████║██╔═══██╗╚══██╔══╝██╔════╝    ██╔══██╗██╔════╝██╔════╝██╔════╝██╔════╝██╔════╝
██████╔╝█████╗  ██╔████╔██║██║   ██║   ██║   █████╗      ███████║██║     ██║     █████╗  ███████╗███████╗
██╔══██╗██╔══╝  ██║╚██╔╝██║██║   ██║   ██║   ██╔══╝      ██╔══██║██║     ██║     ██╔══╝  ╚════██║╚════██║
██║  ██║███████╗██║ ╚═╝ ██║╚██████╔╝   ██║   ███████╗    ██║  ██║╚██████╗╚██████╗███████╗███████║███████║
╚═╝  ╚═╝╚══════╝╚═╝     ╚═╝ ╚═════╝    ╚═╝   ╚══════╝    ╚═╝  ╚═╝ ╚═════╝ ╚═════╝╚══════╝╚══════╝╚══════╝

Remote Access Integration Guide

Focus: securely reach a machine or service that isn't directly exposed — a box behind a firewall, a localhost dev server Daraja needs to call, a Raspberry Pi on the office LAN. The throughline is identity over network position: prove who you are (keys, MFA, an identity provider), open the minimum path, and audit it — never poke a hole in the firewall and hope.

Overview

"Remote access" is one verb — reach a thing that's elsewhere — answered by a handful of tools that trade off in predictable ways:

  • SSH — the workhorse for administering a server you can route to. Key-based auth, an encrypted shell, and — the underused superpower — port forwarding: carry an arbitrary TCP stream through the SSH connection so a database on a private subnet looks like it's on your localhost.
  • Public tunnels (ngrok, Cloudflare Tunnel) — the inverse problem: you have a service on localhost and you need the public internet to reach it over HTTPS. This is exactly the M-Pesa/Daraja callback workflow — Daraja will only POST to a public HTTPS URL, and your laptop isn't one.
  • VPN & zero-trust (WireGuard, Tailscale, Cloudflare Access) — instead of exposing services one port at a time, put the machines on a private encrypted network and gate entry on identity. The firewall stays shut; access is a function of who you are, not where you are.
  • RDP/VNC — when you need a graphical desktop, not a shell. Almost always tunnelled, never exposed raw.
  • Remote development (VS Code Remote-SSH, GitHub Codespaces) — run your editor locally but execute on the remote box, so the code lives where the CPU, GPU, or private data is.

The decision tree: need a shell on a routable box? SSH. need the public internet to hit your localhost? a tunnel. need many services reachable by your team without opening ports? zero-trust mesh. need a desktop? RDP/VNC over a tunnel. Security is the constant: keys not passwords, least privilege, MFA, and an audit trail.

Official Documentation

SourceURLWhat it covers
OpenSSH ssh manualhttps://www.man7.org/linux/man-pages/man1/ssh.1.html-L/-R forwarding, -J jump host, -i, -N
OpenSSH ssh_confighttps://www.man7.org/linux/man-pages/man5/ssh_config.5.html~/.ssh/config, ProxyJump, IdentityFile
ngrok getting startedhttps://ngrok.com/docs/getting-started/ngrok http, authtoken, auto-HTTPS
Cloudflare Tunnelhttps://developers.cloudflare.com/cloudflare-one/connections/connect-networks/get-started/create-local-tunnel/cloudflared tunnel create/route/run, outbound-only model
Tailscale CLIhttps://tailscale.com/kb/1080/clitailscale up/status/ssh, exit nodes
What is Tailscalehttps://tailscale.com/kb/1151/what-is-tailscaleWireGuard mesh, zero-trust, MagicDNS, ACLs

SSH: keys, config, and tunnels

Key-based auth (never passwords)

Generate a modern Ed25519 key, then copy the public half to the server. The private key never leaves your machine.

Bash
# Generate an Ed25519 keypair (smaller + faster than RSA, no reason to use RSA in 2026)
ssh-keygen -t ed25519 -C "you@codeamani.com" -f ~/.ssh/id_ed25519

# Install the PUBLIC key on the server's authorized_keys
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server.example.com

# Connect with an explicit identity
ssh -i ~/.ssh/id_ed25519 user@server.example.com

On the server, harden /etc/ssh/sshd_config: PasswordAuthentication no, PermitRootLogin no, PubkeyAuthentication yes. Passwords are brute-forceable; keys are not.

~/.ssh/config — stop typing flags

A config block turns a long command into ssh prod. It's also where bastion routing lives.

SSH-CONFIG
# A jump/bastion host — the only box with a public SSH port
Host bastion
    HostName bastion.example.com
    User jumpuser
    IdentityFile ~/.ssh/id_ed25519

# A private box reachable ONLY through the bastion
Host app-internal
    HostName 10.0.1.50         # private IP, no public exposure
    User appuser
    ProxyJump bastion          # SSH hops through bastion automatically
    IdentityFile ~/.ssh/id_ed25519

Now ssh app-internal transparently tunnels through bastion. The equivalent one-shot flag is -J:

Bash
ssh -J jumpuser@bastion.example.com appuser@10.0.1.50

Port forwarding (-L / -R)

This is SSH's most useful and least-known feature. -N means "no shell, just hold the tunnel open."

Bash
# LOCAL forward (-L): pull a remote/private service onto YOUR localhost.
# Reach a Postgres on a private subnet as if it were local:5432.
ssh -N -L 5432:db.internal:5432 user@bastion.example.com
#        └─local─┘ └──remote target──┘
# → psql -h localhost -p 5432  now hits db.internal through the encrypted tunnel.

# REMOTE forward (-R): push YOUR localhost out to a port on the remote box.
# Expose your laptop's :3000 on the server's :8080 (e.g. a quick demo).
ssh -N -R 8080:localhost:3000 user@server.example.com

Local vs remote, the mnemonic: -L brings something to you (Local), -R sends something out to the remote.


Public tunnels: exposing localhost for HTTPS callbacks

Daraja, Stripe, Clerk and every other webhook provider POST to a public HTTPS URL. Your dev server on http://localhost:3000 is invisible to them. A tunnel gives localhost a temporary public HTTPS front door.

ngrok — fastest path

Bash
# One-time: register your account's authtoken
ngrok config add-authtoken <YOUR_TOKEN>

# Expose localhost:3000 over public HTTPS (TLS managed for you)
ngrok http 3000
# → Forwarding  https://abc123.ngrok-free.app -> http://localhost:3000

Take that https://…ngrok-free.app URL and register it as your Daraja callback URL. The inspector at http://127.0.0.1:4040 shows every request/response — invaluable for debugging callback payloads.

Cloudflare Tunnel (cloudflared) — durable, no open ports

cloudflared makes an outbound-only connection to Cloudflare's edge; your firewall stays fully closed to inbound traffic, and you get a stable hostname on your own domain (great when a provider whitelists callback domains).

Bash
cloudflared tunnel login                       # browser auth, picks a zone
cloudflared tunnel create daraja-dev           # creates a named tunnel + UUID
cloudflared tunnel route dns daraja-dev cb.codeamani.com   # map a hostname

# ~/.cloudflared/config.yml
#   tunnel: <UUID>
#   credentials-file: ~/.cloudflared/<UUID>.json
#   ingress:
#     - hostname: cb.codeamani.com
#       service: http://localhost:3000
#     - service: http_status:404

cloudflared tunnel run daraja-dev              # bring it up
ngrokCloudflare Tunnel
Setupsecondsa few minutes
URLrandom (paid: reserved)your own domain hostname
Best forthrowaway webhook testinglonger-lived previews, IP-restricted providers
Inbound portsnonenone (outbound-only)

VPN & zero-trust

Tunnels and bastions expose one path at a time. A zero-trust mesh flips the model: machines join a private encrypted network and access is granted by identity + policy, not by where a packet originates.

Tailscale (WireGuard mesh, managed)

Tailscale builds an encrypted peer-to-peer WireGuard mesh (a "tailnet"). No central gateway to bottleneck, no inbound ports, and devices authenticate against your existing IdP (Google, GitHub, etc.).

Bash
tailscale up                       # auth via browser/IdP, join the tailnet
tailscale status                   # list peers + their tailnet IPs
tailscale ip -4                    # this device's 100.x.y.z address
tailscale up --ssh                 # enable identity-gated Tailscale SSH
tailscale ssh user@raspberry-pi    # SSH over the mesh, keys handled for you

Access between devices is governed by ACLs (a policy file), and MagicDNS lets you use names (raspberry-pi) instead of IPs. This is the cleanest way to administer a fleet of Raspberry Pis without exposing SSH to the internet at all.

WireGuard (the raw protocol)

Tailscale is WireGuard with identity + NAT-traversal bolted on. Plain WireGuard (wg, wg-quick up wg0) is the DIY option — you manage keys and peer config yourself. Reach for it when you want a single self-hosted VPN concentrator and no third party in the path.

Cloudflare Access (zero-trust for HTTP)

Pairs with Cloudflare Tunnel: put an internal app behind a Tunnel, then enforce an identity policy (email domain, IdP group, MFA) at Cloudflare's edge before any request reaches your origin. No VPN client, no open port — the app is private but reachable by exactly the right people.


RDP / VNC (graphical desktops)

When you need a screen, not a shell:

  • RDP (Windows) and VNC (cross-platform) are graphical remote-desktop protocols.
  • Never expose RDP/VNC directly to the internet — RDP brute-forcing is a top ransomware vector. Tunnel them: either over SSH (ssh -L 5900:localhost:5900 user@host then point your VNC client at localhost:5900) or, better, over Tailscale/WireGuard so the desktop is only reachable inside the private mesh.

Remote development

Run the editor locally, execute remotely — so code lives next to the data, GPU, or private network it needs.

  • VS Code Remote-SSH — VS Code connects over your existing ~/.ssh/config entry (so ProxyJump/bastion routing just works), installs a small server on the remote, and you edit/run/debug as if local. Zero extra infrastructure.
  • GitHub Codespaces — a fully managed cloud dev container; no machine of your own to administer. Good for onboarding (a new dev is coding in minutes) and for heavy builds you don't want on a laptop.

Security

Remote access is the front door to your infrastructure — treat every item here as mandatory, not optional.

  • Keys, never passwords. PasswordAuthentication no on every server; Ed25519 keys; protect private keys with a passphrase + an agent.
  • Least privilege. One bastion with a public port; everything else private and reached via ProxyJump. Per-user accounts, not a shared root.
  • MFA / identity. Gate SSH and apps behind an IdP + MFA (Tailscale, Cloudflare Access) rather than a bare key on a stolen laptop.
  • No raw exposure. RDP/VNC/databases never face the internet — tunnel or mesh them.
  • Outbound-only where possible. Cloudflare Tunnel / Tailscale need zero inbound firewall rules — nothing to scan, nothing to brute-force.
  • Rotate & revoke. Remove departed users from authorized_keys / the tailnet ACL immediately. Short-lived certs > long-lived keys for fleets.
  • Audit. Log SSH sessions and access decisions; Tailscale/Cloudflare give you a who-reached-what trail out of the box.
  • Disable agent forwarding unless needed (ForwardAgent opens your keys to a compromised hop); prefer ProxyJump.
  • Tunnel auth tokens are secrets. ngrok authtoken, cloudflared credentials JSON, WireGuard private keys → .env.local / secret managers, never committed.

codeAmani notes

  • Daraja callback tunneling is the everyday use case. Daraja only POSTs to public HTTPS. Locally: ngrok http 3000, register the https://…ngrok-free.app URL as your callback, and test against sandbox shortcode 174379 / test phone 254708374149 before going live. For a stable hostname a provider can whitelist, use cloudflared mapped to cb.codeamani.com → http://localhost:3000. Either way the callback lands on app/api/mpesa/callback/route.ts — and remember Daraja callbacks are effectively single-shot, so dedupe on CheckoutRequestID and pair with a status-query reconciliation job (see the webhooks guide).
  • Raspberry Pi administration. A Pi on the office LAN should not have SSH exposed to the internet. Put it on the tailnet (tailscale up --ssh on the Pi) and reach it with tailscale ssh pi@raspberry-pi from anywhere — identity-gated, no port-forwarding on the router, no dynamic-DNS hacks. The raspberry-pi guide covers the device side.
  • Zero-trust over open ports. For any internal tool (a staging dashboard, an admin panel), prefer Cloudflare Tunnel + Access or Tailscale over opening a port on a Vercel-adjacent box. The firewall stays shut; access is a function of identity, which is also what our Clerk-everywhere posture wants.
  • Bastion pattern for managed DBs. When a Supabase/Neon-style resource sits behind a private network, a one-line ssh -N -L 5432:db.internal:5432 user@bastion gives you local psql/migration access through an encrypted tunnel — no need to widen the DB's IP allowlist.
  • Secrets hygiene. cloudflared credential JSONs, ngrok authtokens, and WireGuard keys are credentials. They live in .env.local / the secret manager and are caught by the pre-push gitleaks gate — never paste them in chat or commit them.