Remote boxes through Cloudflare
devlanes needs only a working ssh to the box. Ports, hostnames and synced folders all travel over one SSH session, the box’s services stay on its loopback, and the client binds the same port numbers on its own loopback. The box therefore needs one route for SSH and no public hostname per service. This page covers reaching the box’s sshd through Cloudflare Tunnel, either with cloudflared as a ProxyCommand behind a Cloudflare Access application, or with the Cloudflare One Client (formerly WARP) on a private network route. Remote setup covers the rest of preparing both machines.
On the box
Section titled “On the box”Install cloudflared on the box and create a tunnel. For the ProxyCommand setup, add a published application route on a hostname in your Cloudflare zone, such as ssh.example.com, with the service type SSH and localhost:22 (Cloudflare’s SSH guide). A locally managed tunnel does the same in its configuration file:
tunnel: <tunnel-uuid>credentials-file: /etc/cloudflared/<tunnel-uuid>.jsoningress: - hostname: ssh.example.com service: ssh://localhost:22 - service: http_status:404Protect the hostname with a self-hosted Access application and a policy that allows the people who connect. Cloudflare lists this step as recommended; without it, sshd’s own authentication is the only check. Run the tunnel as a service so it comes back after a reboot.
sshd keeps its usual configuration. AllowTcpForwarding no is fine, because devlanes opens only session channels. Once SSH through the tunnel works, port 22 can be closed to the internet. devlanes itself is installed and kept alive as in remote setup.
On the client
Section titled “On the client”Install cloudflared on the client too, and add a Host entry that uses it:
Host acme-box HostName ssh.example.com User acme ProxyCommand /usr/local/bin/cloudflared access ssh --hostname %h%h is replaced with the HostName. Write the full path to cloudflared: the ssh that devlanes runs is a child of the local daemon and sees the daemon’s PATH, which may not include Homebrew’s /opt/homebrew/bin. cloudflared access ssh-config --hostname ssh.example.com prints a block with the path of the cloudflared you ran it from. Then:
ssh acme-box # in a terminal first, see below; then on the box:devlanes daemon pair # a one-time code for the first connectionexitdevlanes remote connect acme-box --code K7QM-3XWDThe code pairs this machine with the box on its first connection (pairing); later connections present the token it got.
Log in before connecting
Section titled “Log in before connecting”cloudflared access ssh needs an Access token for the hostname. Without one it starts a login: it tries to open a browser, prints the login URL on its stderr and waits until you finish. That works in a terminal but not under devlanes. The ProxyCommand runs inside the background daemon with stderr discarded, so the URL never reaches the terminal running remote connect, and whether a browser opens depends on the OS and the session the daemon runs in. devlanes relays only the prompts ssh itself asks (passphrase, one-time password, host key), not cloudflared’s login. ssh also gives up when the box has not answered within 15 seconds, which is rarely enough to finish a login even when the browser does open.
Get the token in a terminal first. ssh acme-box true logs in to Access when needed and also checks the host key and your key. cloudflared access login https://ssh.example.com gets the token without ssh. cloudflared stores tokens under ~/.cloudflared (%USERPROFILE%\.cloudflared on Windows) and every later run reuses them, including the runs devlanes starts.
Session expiry
Section titled “Session expiry”The token lasts for the session duration set on the Access application or policy, 24 hours by default. When it expires, cloudflared gets a new one without a browser as long as your Access global session is still valid; once that has expired too, the next run needs the browser login again (session management).
An expired token does not end a connection that is already up, since Access decides when an SSH connection may start, not how long it lasts. It matters when devlanes connects again: after sleep, a network change or a dropped connection, after a daemon restart, and on Windows on every reconnect. Cloudflare also notes that long-lived cloudflared connections can close unexpectedly, so expect the odd reconnect. A reconnect without a valid token fails and is retried with backoff, and every retry runs cloudflared again, which may open another login page each time.
To recover, log in again in a terminal with ssh acme-box true, then run devlanes remote connect acme-box. devlanes remote disconnect acme-box stops the retries in the meantime. A longer session duration for this application makes this rarer. A client that must reconnect with nobody at a browser can use an Access service token with a Service Auth policy, or the Cloudflare One Client below.
Windows clients
Section titled “Windows clients”Win32-OpenSSH runs the ProxyCommand as well. Write the full path to cloudflared.exe (where.exe cloudflared prints it), in double quotes when it contains spaces:
Host acme-box HostName ssh.example.com User acme ProxyCommand "C:\tools\cloudflared\cloudflared.exe" access ssh --hostname %hWindows’ OpenSSH has no ControlMaster, so every connect and every reconnect starts a new ssh.exe and a new cloudflared, and each one needs a valid token. Both run in a job with no window and end with the connection, so a login started there cannot show its URL either. Run ssh acme-box true in PowerShell or Windows Terminal before connecting, and again after the session expires.
Cloudflare One Client instead of a ProxyCommand
Section titled “Cloudflare One Client instead of a ProxyCommand”With the Cloudflare One Client enrolled in your Zero Trust organization, the box is reachable at a private IP or private hostname and ssh connects to it directly. On the box, the tunnel gets a CIDR route for the box’s address or a private hostname route instead of a published application. On the client, Split Tunnels must send that address through the client. Gateway network policies decide who may connect, and sshd checks your usual keys.
Host acme-box HostName 10.0.0.12 User acmeThere is no per-connection Access login, so reconnects after sleep or a network change need nobody at the keyboard as long as the client is connected. Connections fail while it is disconnected.
Access for Infrastructure builds on the same route and adds per-user short-lived certificates signed by a Cloudflare SSH CA that the box trusts. It does not support port forwarding, ProxyJump or agent forwarding. devlanes needs none of them, but a ProxyJump in the Host entry does not work there. An infrastructure application closes connections when its session duration ends (24 hours by default), and Cloudflare guarantees Gateway connections for 10 hours; devlanes reconnects after either.
Short-lived certificates
Section titled “Short-lived certificates”devlanes runs the system ssh, so certificates work as they do in a terminal. With Access for Infrastructure nothing changes on the client. For a published application, Cloudflare’s older short-lived certificates are set up with:
cloudflared access ssh-config --hostname ssh.example.com --short-lived-certIt prints a Match host ssh.example.com exec "... access ssh-gen --hostname %h" block. Before each connection ssh runs ssh-gen, which needs a valid Access token like access ssh does and writes a new certificate to ~/.cloudflared. Since the block matches the hostname, connect with devlanes remote connect ssh.example.com, or adapt the block to your alias. On Windows, check the block with ssh ssh.example.com true before using it with devlanes.
sshd checks a certificate only when ssh authenticates. On macOS and Linux the devlanes connection stays up after its certificate expires, and a new certificate is needed only when it connects again; on Windows every reconnect authenticates. With ssh-gen in the Match exec line that happens by itself while the token is valid. Certificates you renew some other way must be renewed before they expire: a reconnect with an expired certificate fails with Permission denied, and the box waits in needs_auth until you renew the certificate and run devlanes remote connect again.
Checking
Section titled “Checking”ssh acme-box true # Access login, host key and key, in a terminaldevlanes remote connect acme-boxdevlanes remote ls # connected, reconnecting, needs_auth or needs_pairing; the SSH error while reconnectingdevlanes status --json | jq .clients # on the box: who is connected| Message or symptom | Cause | Fix |
|---|---|---|
remote connect keeps reconnecting with Connection timed out during banner exchange or Connection closed | no valid Access token, so cloudflared is waiting for a login nobody sees | ssh acme-box true in a terminal, then remote connect again |
| an Access login page opens on its own | a reconnect ran cloudflared after the session expired | finish the login, or remote disconnect, ssh acme-box true, connect again |
cloudflared: not found or command not found | the ProxyCommand has no full path and the daemon’s PATH lacks cloudflared | write the full path in ~/.ssh/config |
Permission denied | Access let you through but sshd refused the key or certificate; devlanes stops retrying | fix the key or renew the certificate, ssh acme-box true, connect again |
Host key verification failed | the box’s key is not in known_hosts under this name | answer the prompt in the remote connect terminal, or run ssh acme-box true |
| connection drops after 10 or 24 hours | Gateway’s connection limit or the infrastructure application’s session duration | nothing to do; devlanes reconnects |
| timeouts with the Cloudflare One Client | client disconnected, or Split Tunnels do not include the box’s address | connect the client; check the device profile’s Split Tunnels |