A self-hosted web UI for rclone. Browse a remote server from any browser (laptop or phone) and download files straight onto your home server's drives, with fast parallel transfers. Siphon is a small, password-protected front end; rclone does the heavy lifting.
docker compose up -d --build
# then open http://<server-ip>:8080Files download into ./downloads; saved connections live in ./data. rclone is bundled in
the image, so there's nothing else to install.
- Download the binary for your OS from the Releases page
(
siphon-win-x64.exe,siphon-macos-arm64,siphon-macos-x64, orsiphon-linux-x64). - Run it once. It creates a
config.jsonnext to itself. - Open
http://localhost:8080(orhttp://<pc-ip>:8080from another device) and finish first-run setup in the browser.
On macOS/Linux, make it executable first: chmod +x siphon-*. The binaries are unsigned, so
Windows SmartScreen or macOS Gatekeeper may warn once. rclone is fetched automatically on
first run if it isn't already installed.
Set these as environment variables (Docker) or as keys in config.json (single-file app).
Environment variables win if both are set.
| Setting | config.json key |
Default | What it does |
|---|---|---|---|
APP_USERNAME |
appUsername |
admin when auth is enabled |
Login username. Change this from admin. |
APP_PASSWORD |
appPassword |
none | Plaintext password for login. |
APP_PASSWORD_HASH |
appPasswordHash |
none | Scrypt password hash from --hash-password. Takes precedence over APP_PASSWORD. |
LOGIN_MAX_ATTEMPTS |
loginMaxAttempts |
10 |
Failed login attempts before lockout. Set 0 to disable lockout. |
LOGIN_LOCKOUT_MINUTES |
loginLockoutMinutes |
15 |
Lockout duration after too many failed attempts. |
SESSION_TTL_HOURS |
sessionTtlHours |
72 |
Session lifetime before re-login is required. |
TRUST_PROXY |
trustProxy |
false |
Trust reverse-proxy headers for client IP and protocol. |
SECURE_COOKIES |
secureCookies |
auto |
Cookie security mode: auto, true, false. |
DOWNLOAD_DIRS |
downloadDirs |
/downloads |
Where files can be saved. Label=path pairs, comma-separated. |
PORT |
port |
8080 |
Web UI port. |
DATA_DIR |
dataDir |
./data |
Where saved connections are stored. |
DOWNLOAD_DIRS powers the in-app folder picker. List each drive or folder you want to save
into, for example Movies=/mnt/movies,Backup=/mnt/backup. In Docker, mount each of those
paths as a volume. The same folders scope which local files can be uploaded, since uploads read
from the server's local disk into the remote folder you are browsing.
SFTP uploads run single-stream per file (rclone can't multi-thread a single-file SFTP upload). Overall upload throughput across multiple files comes from the Concurrent setting in the transfer queue.
The Segments setting is saved with each download when it is queued. Changing Concurrent does not replace queued or running transfers' segment requests; changing Segments applies only to newly queued downloads. Configured streams can be fewer than requested because each chunk is at least 5 MiB: a 20 MiB file requesting eight segments is configured for four. Requested and configured segment counts are not measurements of rclone's actual runtime streams.
Folder downloads validate the remote tree and create the destination folder structure before queuing each file as its own transfer. Nested paths and empty subfolders inside a non-empty folder are preserved. Concurrent limits how many files transfer at once, while Segments controls the stream limit within each file. Small files can use fewer streams; the queue displays the stream limit, not a measured active-stream count.
Successfully completed transfers show a full green bar and 100%, including empty files and files within folder downloads whose sizes or byte statistics are unavailable. The byte counts still reflect the reported metadata; completion does not invent missing bytes.
Downloads preserve existing files unless you explicitly choose Overwrite. An individual file offers Overwrite / Cancel; a folder or multi-selection also offers Skip this file / Cancel batch. Directories can merge without deleting unrelated content, but a file blocking a required folder, a directory in place of a file, or a symlink is never converted destructively. All choices are resolved before any item in the batch is queued or any destination folder is created. Skipped items are reported as skipped, not as empty folders or completed downloads. There is no saved overwrite-all preference, Keep both, or Resume. An admission is limited to 10,000 files, with at most 10,000 entries per expanded folder. Each recursive folder listing has a 35-second deadline (including response-body reading) and a 32 MiB response limit enforced while streaming. Oversized or stalled listings fail before admission; no destination folders or transfers are created.
The preview is valid for five minutes and is bound to its original source connection. Changing that connection before admission invalidates a normal download preview. Retries instead retain the failed transfer's original source binding and ask for fresh collision authorization. Ambiguous or overlapping file destinations in one batch are rejected.
Each file transfers into a unique private .siphon-download-* directory beside its final
destination. The final destination stays reserved until publication finishes. New files are
published with an exclusive hard link, never a clobbering rename. Existing-file approvals
include file identity, timestamps, size, and a content hash; targets are checked again at
admission, dispatch, and publication. A file appearing or changing while a download waits or
transfers produces a visible conflict instead of silently overwriting it. Overwrite always
downloads fresh bytes, including when existing size and modification time match the source.
Reading existing files for these checks can make large-file previews and overwrites slower.
An approved overwrite moves the previous file into the private directory, verifies the
displaced object, then exclusively publishes the new copy. On a conflict or cancellation,
Siphon attempts non-destructive restoration; a new external file is never replaced during
that rollback. The displaced object is retained even after success, because an external
process might still write through an already-open handle. Transfers and the server log show
its recovery path (.siphon-download-*/previous). Inspect and remove that recovery content
manually only after confirming no old writer still uses it; it consumes space until removed.
If automatic restoration is impossible, the error and recovery path identify the preserved
object rather than hiding it.
Publication requires a filesystem supporting exclusive hard links (for example, NTFS). Unsupported filesystems fail safely; Siphon never substitutes an overwriting rename. An overwrite has a short interval during which the final name is absent, not a portable compare-and-swap replacement. Once exclusive publication succeeds, completion wins over a late cancellation; Siphon cannot safely undo that name after another process can use it. The guarantees concern file-content preservation and cooperative external writers, not hostile concurrent replacement of ancestor directories, mounts, or Siphon's private staging directories. Path confinement and protected-path checks remain enforced, but portable Node filesystem APIs cannot eliminate those adversarial directory races. A crash can leave staging or recovery directories; there is no automatic startup deletion or byte-level recovery.
Confirmed finished or canceled writers have their expendable staging removed. Uncertain writers keep their staging and final-destination quarantine, even if their queue rows are removed. Pending previews, publication, queued transfers, and uncertain download writers block in-app update installation. Do not delete uncertain staging or restart to clear quarantine until the old rclone backend is confirmed stopped.
API clients use POST /api/download/plan with remotePaths, downloadDir, segments, and
connectionRevision, then POST /api/download/commit with the returned planId and an
entryId-keyed decisions map (overwrite or, for batches, skip). Commit returns
{ transfers, skipped, empty }. Identical commits return the same admission while the plan
is retained; different decisions cannot replay it. POST /api/download/plan/:id/release
cancels/releases a preview, and POST /api/download/:id/retry-plan previews a failed
transfer against its original source. Legacy direct download/retry endpoints remain
no-overwrite and return 409 DESTINATION_COLLISION for existing targets. Collision, busy,
changed-destination, expired-plan, and changed-connection errors have distinct code fields.
On first launch with no configured credentials (no APP_PASSWORD / APP_PASSWORD_HASH environment variables and no appPassword / appPasswordHash in config.json), Siphon starts in setup mode.
Operational APIs are blocked until setup is completed. The minimal public
GET /api/health probe returns only the app name and bundled version.
Open the app in a browser and choose one of these:
- Create an admin username and password.
- Run without a password (open mode), only for trusted networks.
Setup mode should be completed before exposing Siphon beyond loopback or a trusted LAN.
If APP_PASSWORD / APP_PASSWORD_HASH or appPassword / appPasswordHash is configured, those credentials override stored auth state and setup is skipped.
- Connect over SFTP with a password or private key.
- Browse remote folders with current-folder filtering, sorting, multi-select, and right-click download.
- Adjust Name, Size, and Modified columns with pointer dragging, keyboard controls, or numeric inputs in Columns, and reveal full names with Wrap filenames.
- Fast parallel downloads (1 to 16 streams) with live progress and speed.
- Upload files and folders that already exist on the Siphon server's local disk into the remote folder you are browsing.
- Downloads keep running on the server even if you close the tab or your phone.
- Save connections so you don't retype credentials. Siphon remembers the last download folder you selected.
Retry appears only for eligible downloads whose writer has finished with a failure, or whose destination validation/publication failed with no writer still running. It starts a new attempt with a new transfer ID and job, not a byte-level resume. The old row keeps its failure diagnostics and shows Retried. Each failed row can create only one successor; repeating the request returns that same successor. If the successor also fails, it can become eligible for its own Retry.
Retries use the original connection snapshot, source path, local destination, and per-item segment request. Switching or disconnecting the browsing connection, or changing or deleting a saved profile, does not redirect the retry or adopt new credentials. A missing source, private key, or connection configuration remains an error. Source size is checked again; the content at the same source path may have changed.
Downloads to the same canonical final local file run one at a time, including retries. A queued row shows "Waiting for another download to this file." Other destinations and uploads still share the Concurrent capacity.
If a submission's outcome is unknown or cancellation has not been confirmed, that target stays blocked for the lifetime of the running server's download manager. Existing queued and newly requested downloads to it show an explicit reason. Not every error is retryable; canceled transfers and uploads cannot Retry. Remove and Clear finished discard the removed rows' diagnostics and retry options, but do not clear uncertain-target protection.
Clear all, reconnecting, closing the browser, waiting for a timer, or a stop request returning does not prove the backend stopped or clear that protection. Before restarting Siphon to unblock a target, confirm that its old supervised rclone backend has actually terminated. A full server restart empties the in-memory queue and forgets target protection; an app restart alone is not proof that old writers stopped. Orphaned or external writers are outside this protection.
Retries are manual: there is no automatic retry, backoff, Retry all, persistent history, or polling-based recovery. Retry uses the same collision decisions and staged publication as other downloads; it does not reuse an old overwrite approval or resume partial bytes.
Name defaults to filling the remaining table width, with sensible default widths for Size and Modified. Drag a column's trailing edge, focus its resize handle and use Left/Right arrows (Shift for larger steps, Home/End for minimum/maximum), or open Columns to enter numeric widths. Wrap filenames reveals full filenames instead of truncating them.
Setting a Name width pins it to a fixed pixel width; enlarged columns may intentionally require horizontal scrolling. Auto-fill Name column returns Name to filling the remaining space. Reset column widths restores auto-fill Name and default metadata widths without changing your Wrap filenames setting.
Widths survive folder navigation and reconnects while the browser component remains mounted. A page reload restores defaults; widths are not saved to persistent storage. At viewport widths <=1024px, the existing stacked, wrapped layout remains and desktop column controls are hidden. On wider screens with coarse-pointer input, filenames wrap by default.
Editing a saved site's connection fields or deselecting it creates a manual connection. Re-enter the password before connecting or saving it. An unchanged selected site still uses its saved credentials.
The active connection is shared across tabs. If another tab changes it, a stale tab shows a connection conflict instead of transferring to the wrong server. Select the desired server and reconnect, then refresh the folder and select the intended files again before starting the transfer. Queued transfers and transfers that already acquired their connection keep running against their original server. Reloading the page reattaches the current shared connection; confirm the server before transferring.
- Docker: already handled (
restart: unless-stoppedindocker-compose.yml). - Windows: run
npm run service:installfrom an Administrator PowerShell (or right-clickwindows-service/Install-SiphonService.ps1> "Run with PowerShell"). It wraps the packageddist-bin/siphon-win-x64.exein WinSW as an auto-start service that restarts on failure and rolls its logs - no system Node.js needed at runtime. For a first-time build of the exe, add-Build. Seewindows-service/for details. - Linux: point a systemd unit at the binary with
Restart=always.
The Updates header button can check and install the latest published stable release for an existing Windows service. Checking is explicit; opening the dialog does not contact GitHub or install anything. Docker, development processes, standalone launches, and other operating systems are not updated.
First, complete the one-time administrator setup.
An existing executable is not rebuilt by the installer's -Build switch. Rebuild with
npm run package:build and npm run package:win, then rerun the service installer, or run
one last update.ps1 followed by the service installer.
Sign in with a password, finish or cancel queued/running transfers, choose Check for updates, then confirm Install and restart. A protected independent SYSTEM task downloads the pinned release while Siphon serves, verifies size and SHA-256, replaces only the existing executable, restarts the service, and verifies its exact version. If startup fails, it restores and verifies the old executable. Data, settings, and rclone are preserved.
The browser reconnects without resubmitting an install. You may need to sign in again after the service restarts. Closing the dialog does not cancel an update. A reconnect deadline reports an unknown outcome, not a proven failure; Retry status resumes reading the result. Retry accepted task retries only the already-pinned request after the helper verifies its task is idle. Failed recovery keeps transfers blocked and preserves the backup for administrator recovery.
Updates require the Windows protocol-v1 manifest published alongside future releases.
Numeric and v-prefixed stable tags are supported; prereleases and snapshot tags are not.
The existing 1.0.2 release can report current without a manifest, but a newer release
without compatible metadata cannot be installed. A future incompatible helper protocol
requires another explicit administrator setup. The installation directory is excluded from
the app's folder and transfer APIs.
- Username + password login is supported. The default username is
adminwhen auth is enabled, change it withAPP_USERNAMEorappUsername. - You can configure the password as plaintext (
APP_PASSWORDorappPassword) or as a scrypt hash (APP_PASSWORD_HASHorappPasswordHash). Generate hashes with:
./siphon --hash-passwordHashed passwords take precedence over plaintext values.
- Login lockout is enabled by default,
loginMaxAttempts=10andloginLockoutMinutes=15. SetloginMaxAttempts=0to disable lockout. - Sessions expire automatically,
sessionTtlHours=72by default. - For HTTPS behind VPN or reverse proxy, set
trustProxy=trueandsecureCookies=true. - Password changes are available in-app for store-managed credentials. Env-managed credentials are read-only and skip the setup wizard.
- Forgot your password, if a hash is configured (
APP_PASSWORD_HASHorappPasswordHash), replace or remove that hash first (or regenerate it with--hash-password) because hash settings take precedence over plaintext. Then set a new password or hash and restart Siphon. - Siphon runs rclone privately on localhost with random credentials, browsers only talk to Siphon's API, never to rclone directly.
- Saved passwords are stored by rclone (obscured in its config), not by Siphon. Downloads are restricted to folders you configure.
- This version does not verify SFTP host keys, run it on a network you trust.
Existing installs that previously ran with no password and no env credentials now start in setup mode and block operational APIs until setup is completed. This is intentional and fail-closed.
Offline recovery:
- Stop Siphon.
- Delete
auth.jsonfromdataDir.- macOS default:
~/Library/Application Support/Siphon/auth.json - Linux default:
~/.local/share/siphon/auth.json - Windows default:
%APPDATA%\Siphon\auth.json
- macOS default:
- Start Siphon again and complete setup.
Deleting auth.json is also how you leave open mode.
Use Node.js ^22.22.2, ^24.15.0, or >=26.0.0 on Windows, macOS, or Linux.
From your checkout or worktree:
npm install
npm run devOpen http://127.0.0.1:5174 when the ready message appears. This starts the real UI with
hot reload, the API with watch reload on 127.0.0.1:8081, and a local SFTP server on
127.0.0.1:2022. No Docker, separate SSH server, or live service is needed. rclone is reused
from RCLONE_PATH or PATH when available; otherwise it is downloaded into the sandbox on
first run, which requires internet access. Automatic extraction uses PowerShell on Windows
and unzip on macOS/Linux.
Finish the normal first-run setup in the browser. In the connection form, use SFTP and the
host, port, username, and generated password printed in the terminal. These are local SFTP
credentials, separate from your app login. Browse the sample remote, download Sample folder
to try nested files, spaces, and an empty folder, or upload Upload sample.txt from the local
file picker. Changes to the UI and API reload without deploying the live service.
All dev state stays in .siphon-dev in the current checkout:
data/: app login, saved connections, and the API's rclone config.remote/: files served by the local SFTP server.downloads/: the confined download destination and files available for upload.credentials.json: randomly generated local SFTP credentials, preserved on restart.config.json,rclone.conf,authorized_keys, andcache/: local configuration and caches.
Inherited production app credentials, auth settings, paths, and rclone settings are not used;
RCLONE_PATH is retained only as a binary override. Loopback binding and local data/download
paths are enforced even if you edit the dev config. You may deliberately connect to a real
remote, but local downloads and upload sources remain confined to the dev downloads folder.
Sample files and config are created only when missing, so edits and saved app state survive
restarts. A damaged credentials file fails explicitly instead of silently changing its password.
Press Ctrl+C to stop the UI, API watcher, and owned rclone processes. To reset everything,
stop the dev instance first, then delete only .siphon-dev in that checkout and run
npm run dev again. Do not delete the normal data/ or downloads/ directories.
For concurrent worktrees or occupied ports, choose three distinct ports from 1 to 65535. The launcher checks all three before writing files or starting services, and does not silently choose another port.
DEV_API_PORT=8082 DEV_UI_PORT=5175 DEV_SFTP_PORT=2023 npm run devIn PowerShell:
$env:DEV_API_PORT = '8082'
$env:DEV_UI_PORT = '5175'
$env:DEV_SFTP_PORT = '2023'
npm run devThe terminal prints the selected UI URL and SFTP connection details.
Other development commands:
npm test # run the tests
npm run typecheck # type-check server + web
npm run lint # lint with oxlint
npm run format # format with PrettierFor manual control, npm run web:server runs the API on :8080 and npm run web:dev runs
the UI at http://127.0.0.1:5174 in a second terminal. Those commands use normal inherited
settings and app paths, not the isolated dev sandbox, and do not start a sample SFTP
server. The UI proxy targets 127.0.0.1:8080; use npm run dev for isolated development.
To build the single-file binaries yourself: npm run package:all (outputs to dist-bin/).
src/
server/ Fastify API: auth, config, sessions, request handling
routes/ HTTP route handlers (auth, browse, downloads, uploads, ...)
rclone/ rclone process supervisor, client, and download manager
ui/ React UI built with Primer (components + hooks in lib/)
web/ Vite entry point that mounts the UI
shared/ Types and the API contract shared between server and UI
test/ Vitest unit tests
scripts/ Isolated full-stack development launcher and sample setup
windows-service/ WinSW-based Windows service install/uninstall (PowerShell)
Dockerfile Container image (bundles rclone)
docker-compose.yml
Contributions are welcome. Please read CONTRIBUTING.md for setup, coding style, and the checks CI runs. By participating you agree to the Code of Conduct.
- Found a security issue? Follow the security policy and report it privately.
- Curious what changed between releases? See the changelog.
Siphon is released under the MIT License.