← Back to apps

Post Transfer

Host your own file transfers on Windows, macOS or Linux. A browser portal plus a desktop client — you choose where your files live: an internal disk, an attached drive, or a mounted network share.

Downloads

I built this to send dailies and deliverables on my own jobs, and I use it daily. I'm sharing it as it is — you're free to use it however works for you. This is a source install — see below for setup on your OS.

Version 1.1.0. Source archives are not executable downloads — build the desktop app before distributing a Windows, Mac or Linux binary (see Run or build the desktop client, below).

What you need

Post Transfer combines a browser portal with a desktop transfer client. SFTPGo manages accounts, storage access and sharing permissions; the portal supplies the interface; Caddy supplies HTTPS for public access. The computer hosting the server must stay powered on and connected while others transfer files.

GoalInstall
Connect to a server someone else operatesDesktop client only — see "Run or build the desktop client"
Use your own files on one computerSFTPGo and the web portal
Share files with people outside your networkAlso configure a domain and HTTPS
Keep the portal running after closing the terminalSet up the service for your OS
This is a manual source install, not an automated installer. These steps describe a new installation — on an existing server, back up its configuration first and check which ports and services are already in use.

Download source and prerequisites

Extract the package so that server, desktop and config sit directly inside the installation folder: C:\PostTransfer on Windows, ~/PostTransfer on macOS, /opt/post-transfer on Linux. Use Node.js 22 or a newer supported LTS — this source was checked with Node.js 22 and the SFTPGo 2.7.5 API.

Windows

  1. Download the Windows SFTPGo installer from the official releases (Assets, x86-64/x64) and run it as administrator, keeping the Windows service option.
  2. Install Node.js LTS from the official download page, keeping npm and the PATH option enabled.
  3. Extract the source to C:\PostTransfer. Confirm C:\PostTransfer\server\server.js exists, with no extra nested folder.
  4. Only if you need public HTTPS: install Caddy from its official page and place caddy.exe in C:\PostTransfer.

Then, in a new PowerShell window:

node --version
npm.cmd --version
Get-Service SFTPGo

Use npm.cmd to avoid PowerShell execution-policy errors. Run service/firewall commands as Administrator.

macOS — with Homebrew:

brew install node@22 sftpgo caddy
export PATH="$(brew --prefix node@22)/bin:$PATH"
node --version
npm --version
brew info sftpgo

Repeat the export PATH line in each new terminal used for installation.

Linux (Ubuntu/Debian) — install Node.js 22+ via the official instructions, then, in the folder with the downloaded SFTPGo .deb:

sudo apt install ./sftpgo_*.deb
sudo systemctl enable --now sftpgo
node --version
npm --version
command -v node

Other distributions: use the SFTPGo installation instructions. Public HTTPS also needs Caddy, via its official package instructions.

Configure the SFTPGo backend

ComponentListen addressPort
SFTPGo admin and API127.0.0.18080
SFTPGo WebDAV127.0.0.110081
Post Transfer portal127.0.0.118090
Caddy public site (if enabled)Public interface80 / 443
SFTP desktop connections (if enabled)Your chosen interface2022

Keep the backend HTTP ports private. Use 10081, not 10080 — Node's Fetch implementation blocks port 10080.

Back up the active SFTPGo configuration before editing it. Typical paths: Windows C:\ProgramData\SFTPGo\sftpgo.json; macOS Homebrew $(brew --prefix)/etc/sftpgo/sftpgo.json; Linux package /etc/sftpgo/sftpgo.json.

$p="C:\ProgramData\SFTPGo\sftpgo.json"
Copy-Item -LiteralPath $p -Destination "$p.backup-$(Get-Date -Format yyyyMMdd-HHmmss)"
notepad.exe $p

On macOS: sudo nano "$(brew --prefix)/etc/sftpgo/sftpgo.json". On Linux: sudo nano /etc/sftpgo/sftpgo.json (back up first).

Edit the existing file, don't replace it. In the first httpd.bindings object: port 8080, address 127.0.0.1, enable_https false — keep enable_web_admin, enable_web_client and enable_rest_api enabled. In the first webdavd.bindings object: port 10081, address 127.0.0.1, enable_https false. For desktop SFTP, set the first sftpd.bindings port to 2022 (address 127.0.0.1 for same-computer use, or 0.0.0.0 for remote, with firewall access limited appropriately). See the SFTPGo configuration reference.

Restart SFTPGo after editing (this interrupts active transfers):

Restart-Service SFTPGo
curl.exe -I http://127.0.0.1:8080/web/admin/login
curl.exe -I http://127.0.0.1:10081/

macOS: sudo brew services restart sftpgo. Linux: sudo systemctl restart sftpgo. Then run the two curl checks above with plain curl. A WebDAV 401 Unauthorized without credentials is expected — it confirms the endpoint is listening. "Connection refused" means the service or binding needs attention.

Create accounts and select storage

On the server, open http://127.0.0.1:8080/web/admin/, complete the initial administrator setup if prompted, and save that password securely. Then, under Users, add a user:

  1. Set a username, password and enabled status.
  2. Choose Local filesystem storage and an existing home directory (see examples below).
  3. Add permissions for virtual path / — list and download by default; add upload/create-directories when needed, rename/delete only when intended.
  4. Allow HTTP/Web Client and WebDAV, plus SFTP if using the desktop app.
  5. Save, then test the account at http://127.0.0.1:8080/web/client/ before installing the portal.
StorageExample home directory
Windows internal/attached diskD:\TransferData\ClientA
Windows network share\\NAS\Projects\ClientA
macOS disk/Users/Shared/TransferData/ClientA
macOS mounted drive/Volumes/Projects/ClientA
Linux disk/mounted share/srv/transfer/ClientA

The SFTPGo service account needs read (plus write for uploads) access to that directory and its parents, e.g. on a fresh Linux install: sudo install -d -o sftpgo -g sftpgo -m 0750 /srv/transfer/ClientA. On Windows network shares, use a dedicated service account and a UNC path rather than a desktop-mapped drive. Never put SMB passwords in the portal configuration.

Install the web portal

Windows

Set-Location C:\PostTransfer\server
Copy-Item .env.example .env
npm.cmd ci --omit=dev
node --env-file=.env server.js

macOS

cd "$HOME/PostTransfer/server"
cp .env.example .env
npm ci --omit=dev && node --env-file=.env server.js

Linux — extract into /opt/post-transfer, owned by your installation account while installing, then:

cd /opt/post-transfer/server
cp .env.example .env
npm ci --omit=dev && node --env-file=.env server.js

The default .env:

HOST=127.0.0.1
PORT=18090
SFTPGO_URL=http://127.0.0.1:8080
WEBDAV_URL=http://127.0.0.1:10081
PUBLIC_URL=http://localhost:18090
COOKIE_SECURE=false

Only copy .env.example on the first install — preserve .env during updates. Leave the terminal open while testing, and open http://localhost:18090/: /studio to create deliveries, /client to upload/download with a transfer account.

Verify the complete transfer path

  1. Sign in with the user created above.
  2. Download a small known file and verify its contents.
  3. Upload a test file (if permitted) and check it lands in the assigned storage folder.
  4. Create a delivery from /studio; open its link in a private browser window and test it.
  5. Repeat with a large file and check memory use stays reasonable.

At this stage localhost links only work on the same computer — they can't be sent to external clients yet. That needs public HTTPS (below).

Keep the portal running

Press Ctrl+C in the test terminal before installing a service on the same port — never run two copies against port 18090.

Windows service — download WinSW-x64.exe v2.12.0 from its release page into C:\PostTransfer, then:

Set-Location C:\PostTransfer
Copy-Item WinSW-x64.exe PostTransferPortal.exe
Copy-Item config\PostTransferPortal.xml PostTransferPortal.xml
New-Item -ItemType Directory -Path C:\PostTransfer\logs -Force
icacls C:\PostTransfer /grant "*S-1-5-19:(OI)(CI)RX"
icacls C:\PostTransfer\logs /grant "*S-1-5-19:(OI)(CI)M"
.\PostTransferPortal.exe install
Start-Service PostTransferPortal
Get-Service PostTransferPortal

Edit <executable> in the XML first if Node isn't at C:\Program Files\nodejs\node.exe. Logs land in C:\PostTransfer\logs.

macOS login service — survives closing Terminal, starts on your user login (not before):

mkdir -p "$HOME/Library/LaunchAgents" "$HOME/PostTransfer/logs"
cp "$HOME/PostTransfer/config/com.joaofariapost.posttransfer.plist" "$HOME/Library/LaunchAgents/"
command -v node
echo "$HOME"
open -e "$HOME/Library/LaunchAgents/com.joaofariapost.posttransfer.plist"

Replace the Node placeholder and every /Users/YOUR_USERNAME with the printed paths (XML needs absolute paths — ~ and $HOME don't expand there). Then:

plutil -lint "$HOME/Library/LaunchAgents/com.joaofariapost.posttransfer.plist"
launchctl bootstrap "gui/$(id -u)" "$HOME/Library/LaunchAgents/com.joaofariapost.posttransfer.plist"
curl -I http://127.0.0.1:18090/

Linux systemd service — for a new install:

sudo useradd --system --home-dir /opt/post-transfer --shell /usr/sbin/nologin posttransfer
sudo chown -R root:posttransfer /opt/post-transfer/server
sudo chmod -R g+rX,o-rwx /opt/post-transfer/server
sudo chmod 640 /opt/post-transfer/server/.env
sudo cp /opt/post-transfer/config/post-transfer.service /etc/systemd/system/
command -v node
sudo nano /etc/systemd/system/post-transfer.service

Set ExecStart to the real Node path if not /usr/bin/node, then:

sudo systemctl daemon-reload
sudo systemctl enable --now post-transfer
sudo systemctl status post-transfer --no-pager
curl -I http://127.0.0.1:18090/

Enable public HTTPS access

Use your own subdomain, e.g. transfer.example.com. Point its DNS to your server's public IP, forward TCP 80/443 if behind a router, and confirm no other web server already owns those ports. Edit the portal's .env: PUBLIC_URL=https://transfer.example.com (your real domain) and COOKIE_SECURE=true, keeping HOST=127.0.0.1. Restart the portal service, then configure Caddy using config/Caddyfile.example with your domain — it proxies the portal and the native SFTPGo client/API; admin pages stay local.

Windows HTTPS service — copy the example to C:\PostTransfer\Caddyfile, add this global block at the very top:

{
    storage file_system {
        root C:/PostTransfer/caddy-data
    }
}

Then, in Administrator PowerShell:

Set-Location C:\PostTransfer
New-Item -ItemType Directory -Path C:\PostTransfer\caddy-data -Force
icacls C:\PostTransfer\caddy-data /inheritance:r /grant:r "*S-1-5-18:(OI)(CI)F" "*S-1-5-32-544:(OI)(CI)F" "*S-1-5-19:(OI)(CI)M"
.\caddy.exe validate --config C:\PostTransfer\Caddyfile --adapter caddyfile
Copy-Item WinSW-x64.exe PostTransferCaddy.exe
Copy-Item config\PostTransferCaddy.xml PostTransferCaddy.xml
.\PostTransferCaddy.exe install
New-NetFirewallRule -DisplayName "Post Transfer HTTPS" -Direction Inbound -Action Allow -Protocol TCP -LocalPort 80,443 -Program C:\PostTransfer\caddy.exe
Restart-Service PostTransferPortal
Start-Service PostTransferCaddy

macOS HTTPS — copy the edited example to $(brew --prefix)/etc/Caddyfile (merge if Caddy already serves other sites):

caddy validate --config "$(brew --prefix)/etc/Caddyfile" --adapter caddyfile
sudo brew services start caddy
launchctl kickstart -k "gui/$(id -u)/com.joaofariapost.posttransfer"

Linux HTTPS — add the edited site block to /etc/caddy/Caddyfile:

sudo caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
sudo systemctl restart post-transfer
sudo systemctl enable --now caddy
sudo systemctl reload caddy

If UFW is your firewall: sudo ufw allow 80/tcp and sudo ufw allow 443/tcp. Never expose backend ports 8080, 10081 or 18090 publicly.

External check — open the real HTTPS address from a different connection (e.g. a phone on mobile data). Verify login, a file download, an upload if allowed, and a new public delivery link. Any existing link containing localhost needs to be recreated with the public domain.

Run or build the desktop client

The desktop source is under desktop. It can connect to a separately installed SFTPGo server or open an HTTPS public delivery link — you don't need the portal on every client's computer.

Windows

Set-Location C:\PostTransfer\desktop
npm.cmd ci
npm.cmd start

macOS

cd "$HOME/PostTransfer/desktop"
npm ci && npm start

Linux (as a regular desktop user, in the extracted desktop source folder)

npm ci && npm start

For login: server hostname or IPv4 (no https:// prefix), SFTP port, username, password, and the server's verified SHA256 host-key fingerprint — get this from the server administrator through a trusted channel, never from an unverified remote scan.

To build local application packages (build on the matching OS):

npm.cmd run dist -- --win portable --x64 --publish never
npm run dist -- --mac dmg zip --arm64 --publish never
npm run dist -- --linux AppImage deb --x64 --publish never

Outputs land in desktop/dist. Source archives aren't executable downloads — build before distributing a binary, and sign/notarize with your own publisher credentials where appropriate (none are included here).

Updates, backups and troubleshooting

Back up the portal .env, the SFTPGo configuration/database/host keys, Caddy's configuration/certificate data, and the actual transferred files — the application source is not a backup of your storage. To update: stop the portal service, preserve .env, replace the application source, run npm ci --omit=dev inside server, then restart the service (this invalidates in-memory login sessions).

SymptomCheck
WebDAV 401 in an unauthenticated curl testExpected — retry through the portal with a transfer user
ECONNREFUSEDBackend running, WebDAV enabled, matching port 10081
Fetch reports "bad port"Replace 10080 with 10081 in both backend and .env
502 Bad GatewayPortal/backend listener, proxy address, logs
Login works but files return 403User home permissions, protocol restrictions, forced password change
Public link only works on the serverWrong PUBLIC_URL, DNS, port forwarding, firewall or CGNAT
HTTPS fails to startPort conflict, DNS, certificate reachability, service storage permissions
Low speedCompare local backend vs local portal vs public HTTPS; check disk, CPU, network, user limits
This package includes source and configuration examples, not a remotely managed service. SFTPGo, Node.js, Electron and Caddy remain independent third-party projects — preserve their license notices when distributing builds (see BUILD.md / THIRD-PARTY-NOTICES.md in the package).

Contact

Created by João Faria — joaofariapost.com

Questions or feedback? hello@joaofariapost.com