Platform Tools is a beginner-friendly, cross-platform desktop interface for Cloudflare Tunnel. It publishes local web, SSH, RDP, SMB, database, and custom TCP services without requiring users to learn cloudflared commands.
A separate Linux CLI supports publishing and connecting on servers without a desktop. It shares the protocol and process engine with the GUI, supports existing .ptlink files, and ships as PlatformTools-CLI-<version>-linux-<architecture>-portable.tar.gz. See CLI commands and Linux setup.
./platform-tools publish --url http://localhost:8080 --export web.ptlink
./platform-tools login
./platform-tools publish --url ssh://localhost:22 --hostname ssh.example.com --tunnel my-ssh --export ssh.ptlink
./platform-tools connect --file ssh.ptlink --user alice --port 2222Publishing and non-web connections stay in the foreground. Use another terminal for the SSH command shown by the CLI; Ctrl+C stops the proxy. GUI and CLI run independently.
- Create a temporary
trycloudflare.comURL without signing in (HTTP/HTTPS only). - Sign in to Cloudflare and publish a service on their own hostname.
- Choose public access or protect a hostname with Cloudflare Access email rules.
- Export a connection as a
.ptlinkfile or share code. Share files never contain tunnel credentials or API tokens. - Import a share profile and automatically start the client-side proxy required by SSH, RDP, SMB, databases, and custom TCP services.
- Switch between Simplified Chinese and English, light and dark themes.
- Check and update the bundled official
cloudflaredbinary.
- Open Platform Tools and select Publish a local service.
- Select Add service, enter a name, choose the service type, and confirm the local address.
- For a web service, choose Temporary URL and press Save and start. Copy the generated address from its service card when it appears.
- For an own-domain or non-HTTP service, open Settings → Cloudflare account and sign in in the browser. Check the displayed account status, then choose Use my own domain, enter a tunnel name and full hostname, and publish. The status in the top bar also opens Settings. Use Refresh status to verify saved credentials; a network verification failure does not remove them.
- Save the generated
.ptlinkfile when another computer needs the Platform Tools connector.
Non-HTTP services are not ordinary publicly exposed ports. Cloudflare requires cloudflared on the connecting computer. Platform Tools includes and controls it automatically. RDP opens Microsoft Remote Desktop on Windows; SSH and database modes display the local command/address to use.
Import a .ptlink file or share code, choose a local port, and select Connect. The connector binds only to 127.0.0.1. It reports Local proxy ready after verifying that the listening socket belongs to its own cloudflared process, without opening a test connection that could trigger Access authentication. Startup can be cancelled and times out after 20 seconds. Unexpected process exits restore the connection controls and retain a bounded, redacted runtime log. Readiness confirms the local listener, not remote authentication or application availability.
The connector currently manages one client session. Disconnect before importing another profile or changing its port. A failed import keeps the previous profile available. Closing the app waits for client proxies as well as published tunnels to stop.
| Service | Local connection workflow |
|---|---|
| SSH | Default suggested port: 2222. Enter your SSH username to copy a command. A stable HostKeyAlias keeps host keys separate per remote hostname; verify the fingerprint on first use. SSH keys/passwords remain in your SSH client. |
| RDP | Default suggested port: 13389. On Windows, Remote Desktop opens after the proxy is ready and can be opened again without restarting the proxy. On macOS/Linux, enter the displayed address in an installed RDP client. |
| SMB | Enter the share name. Windows defaults to local port 445 and displays a UNC path; if that port is occupied, use a client supporting custom ports or configure WARP private access separately. The app does not disable Windows file sharing. Other platforms display an SMB URL with the chosen port; client support and mounting permissions vary. |
| Database / TCP | Choose MySQL, PostgreSQL, Redis, SQL Server or generic TCP when publishing. Presets set origin/client port defaults and provide client command examples. Replace example username/database placeholders and configure credentials and TLS in the database client. |
Origin and client ports are independent. Choose an available port provides a suggestion; binding is checked again during startup. SMB clients that cannot select a custom port still require their supported port. Source-side Check origin port, available in the editor and service card, performs a three-second TCP reachability check and does not claim application authentication succeeded. Named-tunnel ingress is validated by cloudflared before DNS routing.
Access authentication may begin when the local application connects; a valid cached session can avoid another browser prompt. Application authentication (SSH, database, SMB or Windows login) is still required. For database TLS, a loopback connection can require a separate remote certificate hostname setting; do not disable certificate verification to work around it. Network interruptions can end active sessions; reconnect in the application. UDP and protocols needing additional dynamic ports are outside this TCP proxy workflow.
Share format remains version 1. The optional ClientPreset field contains only a database preset identifier; older clients can continue using the generic TCP endpoint. Imports are limited to 16 KiB of text. Usernames entered on the connection page, passwords, keys and Access tokens are not exported.
Windows local-proxy lifecycle and ingress validation have been exercised with cloudflared 2026.9.1. Linux x64 CLI startup, listener ownership and process cleanup have also been exercised on Ubuntu/WSL. Remote protocol operations, real Access policies, Linux GUI, ARM64 and macOS compatibility still require environment-specific validation; see the implementation checklist and validation record.
Open Publish → Add service to save a service or save and start it. Each service has its own cloudflared process, connection state, public address, and bounded runtime log. Starting or stopping one service leaves the others running. A service is shown as running after cloudflared registers a connection; startup failure and unexpected process exit are shown per service.
Configurations are saved in config/services.json and restored stopped (or pending deletion) after restarting the app. API tokens and runtime URLs are not saved there. Fixed-domain services must use distinct hostnames and tunnel names, and DNS records are not silently overwritten. Stop a service before editing it. Deleting a service asks for confirmation, stops it, removes the matching DNS CNAME and Cloudflare tunnel, and then removes its local tunnel credentials and service configuration. Conflicting DNS records belonging to other targets are preserved. Failed cleanup retains the service for retry and blocks editing/restarting it; deletion progress is saved in config/deletions. Once cloud cleanup is confirmed, retries only finish local cleanup and do not require network access or login. Read-only attributes on the selected tunnel files are cleared for deletion; shared credentials and filesystem permissions are unchanged. Use the original Cloudflare account and authorized zone. If login credentials lack cleanup permissions, the app requests a one-operation API token with Cloudflare Tunnel Edit, DNS Edit, and Zone Read permissions. Shared login credentials and Access applications/policies are retained. Exiting with active services asks for confirmation and stops all of them before closing.
The home page shows service, running, and failure counts. Each running service can copy its address, copy a share code, or export a .ptlink file. Protected services request an Access API token at startup and reuse their existing Access application and policy when possible.
If a hostname fails with “An A, AAAA, or CNAME record with that host already exists”, edit the stopped service to use an unused hostname, or restore the original tunnel name if this hostname belongs to a previously published service. An existing route to the same tunnel is accepted. To migrate a hostname intentionally, first review its current DNS record and dependencies in Cloudflare, then configure a proxied CNAME pointing to the tunnel target shown in the error message. The application does not overwrite conflicting DNS records automatically.
Protected mode asks for a Cloudflare Account ID, an API token with Access: Apps and Policies Write, and one or more allowed email addresses. The token exists only in the input control and operation memory and is cleared after use. It is not persisted, logged, or exported.
All persistent app data is stored in config beside the executable, independent of the working directory:
config/.cloudflared/: Cloudflare login certificate (cert.pem), tunnel credentials (<tunnel-id>.json), and client Access authorization cache.config/settings.json: settings and recent connections.config/services.json: saved service definitions, restored stopped on launch.config/tunnels/<tunnel-id>/config.yml: generated tunnel configuration (regenerated when publishing after moving the application).
On first use, missing settings and credentials are copied from the previous user-profile locations. Existing portable files take precedence, and the original files are retained. Keep the entire config directory when upgrading or moving the app; use a writable application directory. API tokens are still kept only in memory. The config directory contains secrets and must not be included in shared release packages.
Settings → Software update checks stable releases from ifnor/Platform-Tools when first opened. You can also check manually, view release notes, and choose whether to download. The app selects the package for its operating system and process architecture and verifies it against the release's SHA256SUMS.txt. Drafts, prereleases, and older versions are ignored.
Application and cloudflared version checks and downloads share five channels in order: GitHub → gh-proxy.org → edgeone.gh-proxy.org → hk.gh-proxy.org → cdn.gh-proxy.org. The four fallback nodes belong to GH-Proxy. Metadata requests and download headers time out after 8 seconds; downloads switch channels after 15 seconds without data or a retryable network/content error. The UI shows the current channel. Each retry starts a fresh file and verifies its size and SHA256; cancellation stops further attempts. Requests carrying a GitHub Token use GitHub directly only. See update channel details.
On Windows, a confirmed update downloads and verifies the portable package, stops active services, replaces application files, and restarts the app. The config directory, credentials, and service definitions are preserved. If file replacement fails, the updater restores the previous files. Backups remain under config/updates for recovery; the next Settings visit shows the installation result. Use a writable installation directory.
On macOS and Linux, the app downloads and verifies the DMG or AppImage and opens its directory for manual installation. Private repository access supports an optional GitHub token held only in memory. Existing versions without this feature need one manual installation; subsequent newer tagged releases can be discovered in Settings.
| Platform | Architectures | Outputs |
|---|---|---|
| Windows | x64, ARM64 app | Setup EXE, portable ZIP |
| macOS | Intel, Apple Silicon | .app, DMG, portable tar.gz |
| Linux | x64, ARM64 | AppImage, DEB, portable tar.gz |
| Linux CLI | x64, ARM64 | Self-contained CLI portable tar.gz |
Cloudflare does not currently publish a native Windows ARM64 cloudflared; the Windows ARM64 package therefore uses Cloudflare's official x64 binary through Windows 11's x64 emulation.
Published executables are self-contained and do not require users to install .NET. Public distribution should sign/notarize the packages with the publisher's own Windows code-signing certificate and Apple Developer identity.
Requirements: .NET 10 SDK. The desktop UI uses Avalonia 12.
dotnet restore PlatformTools.slnx
dotnet test PlatformTools.slnx -c Release
dotnet run --project src/PlatformTools.App/PlatformTools.App.csprojBuild Windows packages:
./scripts/package-windows.ps1 -Version 0.1.0 -Runtime win-x64On Linux or macOS:
./scripts/package-unix.sh 0.1.0 linux-x64
./scripts/package-unix.sh 0.1.0 osx-arm64The GitHub Actions release workflow tests and packages every supported runtime on a native runner. Native runners are required for DMG, DEB, and AppImage creation.
- Quick Tunnels are intended for development/testing, have no uptime SLA, allow at most 200 in-flight requests, and do not support SSE.
- Own-domain and non-HTTP publishing requires a Cloudflare account and a domain managed by Cloudflare.
- SSH, TCP, RDP, and SMB clients require a client-side
cloudflaredprocess or a Cloudflare One/WARP private-network setup. - Creating Access protection requires the account-level Access API permission named above.
See THIRD_PARTY_NOTICES.md for bundled component notices.
Push a version tag to build and publish all platforms:
git push origin main
git tag -a v0.3.5 -m "Release v0.3.5"
git push origin v0.3.5The tag must contain the release workflow and scripts. Tags use vMAJOR.MINOR.PATCH, optionally followed by a prerelease suffix such as -beta.1. The workflow tests the tagged source, builds all six runtimes on native runners, verifies all 15 packages (including Linux x64/ARM64 CLI archives), generates SHA256SUMS.txt, then publishes a GitHub Release with generated notes. Prerelease tags are marked as prereleases. No personal access token is needed: only the publish job receives contents: write through GITHUB_TOKEN.
Release assets: Windows x64 portable ZIP and Setup EXE; Windows ARM64 portable ZIP; Linux x64/ARM64 GUI portable tar.gz, DEB and AppImage, plus separate CLI portable tar.gz archives; macOS Intel/Apple Silicon portable tar.gz and DMG. Windows ARM64 uses the official x64 cloudflared binary under emulation.
If a build fails, no release is published. Rerun failed jobs, or use Actions → Release → Run workflow with the existing tag. Partial uploads stay in a draft until publishing succeeds. A published release is never overwritten by a rerun; create a new version tag instead. Actions artifacts are retained for 14 days; published release assets remain available.
Portable archives retain ./config beside the executable. Installed Linux launchers use ${XDG_DATA_HOME:-$HOME/.local/share}/platform-tools/config; the macOS app launcher uses ~/Library/Application Support/Platform Tools/config. These launchers set PLATFORMTOOLS_DATA_HOME so read-only installation directories are not used for credentials. Signing and Apple notarization are not configured; these require the publisher's own certificates and credentials.
A 429 Too Many Requests response during startup is a Cloudflare management API limit, not proof that the cloudflared update damaged credentials. Named-tunnel lookups use a server-side name filter; account verification also uses a filtered query instead of enumerating every tunnel. Within the app, cloudflared management commands are serialized. A 429 is reported without a local cooldown or automatic retry; the next manual attempt sends a new request immediately after any ongoing command finishes. Cloudflare may still reject that request while its own limit remains active. Running tunnels are unaffected. Credentials and service definitions are retained.
