Post Transfer — Installation guide

Post Transfer — installation guide

Host your own file transfers on Windows, macOS or Linux.

Version 1.1.0 · joaofariapost.com

Post Transfer combines a browser portal with a desktop transfer client. You choose where your files live: an internal disk, an attached drive, or a mounted network share. The computer hosting the server must remain powered on and connected while other people transfer files.

This is a manual source installation guide. Copy the commands for your operating system, one block at a time. Replace values marked YOUR_USERNAME and transfer.example.com before using them. No automatic server installer is included.

1. Choose what you need

Goal Install
Connect to a server someone else operates Desktop client only; go to section 9
Use your own files on one computer SFTPGo and the web portal; sections 2–6
Share files with people outside your network Also configure a domain and HTTPS; section 8
Keep the portal running after closing Terminal/PowerShell Follow section 7 for your OS

SFTPGo manages accounts, storage access and sharing permissions. The portal supplies the João Faria interface. Caddy supplies HTTPS for public access. The desktop application is a client; it does not turn a disk into a server by itself.

These examples describe a new installation. On an existing server, first back up its configuration and check which ports and services are already in use. Do not replace an existing SFTPGo database, host keys or Caddy configuration with the examples below.

2. Download the source and prerequisites

Extract this package so that server, desktop and config are immediately inside the installation folder:

Operating system Installation folder used below
Windows C:\PostTransfer
macOS ~/PostTransfer
Linux /opt/post-transfer

Use Node.js 22 or a newer supported LTS release. This source was checked with Node.js 22 and the SFTPGo 2.7.5 API. Check compatibility before upgrading a working deployment to a new major release.

Windows

  1. Open the official SFTPGo releases. Expand Assets and download the Windows installer matching your computer (normally x86-64/x64).
  2. Run that installer as administrator, keeping the Windows service option. This installs the third-party storage backend; Post Transfer itself is installed manually below.
  3. Install Node.js LTS from the official download page, keeping npm and the PATH option enabled.
  4. Extract this source package to C:\PostTransfer. Confirm that C:\PostTransfer\server\server.js exists; avoid an extra nested folder after extraction.
  5. Install Caddy from its official download page only if you need public HTTPS, and place caddy.exe in C:\PostTransfer.

Open a new PowerShell window and check:

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

Using npm.cmd avoids PowerShell script execution-policy errors. Run service and firewall commands below in PowerShell as Administrator.

macOS

With Homebrew installed:

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

Use the export PATH command in each new terminal used for installation. The launch-agent example later uses an absolute Node path, so it does not depend on the interactive shell PATH.

Linux — Ubuntu or Debian

Install Node.js 22 or a newer supported LTS release using the official instructions. For an unattended service, use a system-wide Node binary, or set the service’s ExecStart to a stable absolute path accessible to its account. A private nvm installation under your home directory is not suitable for the example service account.

Install the downloaded SFTPGo .deb matching your CPU from the official releases. In the folder containing that one package:

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

For other distributions, use the corresponding SFTPGo installation instructions. Public HTTPS also requires Caddy, installed using its official package instructions.

3. Configure the SFTPGo backend

The examples use these ports:

Component Listen address Port
SFTPGo administration and API 127.0.0.1 8080
SFTPGo WebDAV 127.0.0.1 10081
Post Transfer portal 127.0.0.1 18090
Caddy public website, if enabled Public interface 80 and 443
SFTP desktop connections, if enabled Your chosen interface 2022

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

Find and back up the active SFTPGo configuration. Typical paths are:

Open the active configuration in a text editor with permission to save it. On a standard new Windows installation, the following commands make a backup and open Notepad:

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

On macOS, use sudo nano "$(brew --prefix)/etc/sftpgo/sftpgo.json"; on Linux, use sudo nano /etc/sftpgo/sftpgo.json, after copying the file to a backup. In nano, save with Ctrl+O, Enter, then exit with Ctrl+X.

Edit the existing JSON file. Within its first httpd.bindings object, set port to 8080, address to 127.0.0.1, and enable_https to false. Keep enable_web_admin, enable_web_client and enable_rest_api enabled. Within its first webdavd.bindings object, set port to 10081, address to 127.0.0.1, and enable_https to false. Preserve every other setting. Do not paste a second JSON document into the file; change only these fields in their existing sections.

For desktop SFTP access, set the first sftpd.bindings port to 2022. Use address 127.0.0.1 for same-computer use, or 0.0.0.0 to accept remote IPv4 connections, with firewall access limited appropriately. These are field edits, not a replacement configuration. See the SFTPGo configuration reference.

Restart SFTPGo after editing. Restarting interrupts its active transfers:

Windows:

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, using the packaged Homebrew service:

sudo brew services restart sftpgo
curl -I http://127.0.0.1:8080/web/admin/login
curl -I http://127.0.0.1:10081/

Linux:

sudo systemctl restart sftpgo
curl -I http://127.0.0.1:8080/web/admin/login
curl -I http://127.0.0.1:10081/

A WebDAV 401 Unauthorized response is expected without credentials: it confirms that the authenticated endpoint is listening. Connection refused means the service or binding still needs attention.

4. Create accounts and select storage

On the server computer, open http://127.0.0.1:8080/web/admin/. Complete the initial administrator setup if prompted. Save that administrator password securely. If you see a login page instead, use the administrator already configured on that instance; there is no default password supplied by this package.

After signing in, open the user-management area, choose Users, and add a user:

  1. Set a username, password and enabled status.
  2. Choose Local filesystem storage and set an existing home directory using the examples below.
  3. Add permissions for virtual path /, following the recommendations below.
  4. Allow HTTP/Web Client and WebDAV, plus SFTP if using the desktop app.
  5. Save, then test this account in http://127.0.0.1:8080/web/client/ before installing the portal. Confirm it can list and download a small file.

Create a separate SFTPGo user for transferring files; administrator credentials are not a transfer account. The native SFTPGo administration pages keep their own interface; the Post Transfer theme applies to the included portal and desktop app.

Choose the local filesystem storage provider and set the user’s home directory to an existing folder, for example:

Storage Example home directory
Windows internal or attached disk D:\TransferData\ClientA
Windows network share \\NAS\Projects\ClientA
macOS disk /Users/Shared/TransferData/ClientA
macOS mounted drive /Volumes/Projects/ClientA
Linux disk or mounted share /srv/transfer/ClientA

The SFTPGo operating-system service account needs read access, plus write access for uploads, to this directory and access through its parent directories. For example, on a new Linux package installation:

sudo install -d -o sftpgo -g sftpgo -m 0750 /srv/transfer/ClientA

Do not change ownership of an existing shared project tree blindly. On Windows, a network share normally needs a dedicated service account with both share and filesystem permissions. A drive mapped in your own desktop session may not exist for a service; use a UNC path. On macOS/Linux, ensure the network volume mounts before the service needs it. Do not put SMB passwords in the portal configuration.

Give the transfer user only the permissions needed under /: list and download; add upload and create directories when required. Add rename/delete only when intended. Enable HTTP, WebDAV and SFTP access as needed in the user’s protocol restrictions. For staff who create delivery links, allow public sharing in SFTPGo’s user settings; disable it for clients who should not create shares.

The /studio and /client pages are different interfaces, not independent security roles. SFTPGo permissions enforce access to files and sharing. When first-login password changes are required, complete them in the native Web Client if a particular portal screen cannot handle that flow.

The packaged Homebrew SFTPGo service runs with elevated privileges; grant explicit user permissions rather than *, and do not grant chmod/chown to clients. For a dedicated deployment, configure an unprivileged account with access only to its data. SFTPGo service-account guidance.

5. Install the web portal

Windows

Set-Location C:\PostTransfer\server
Copy-Item .env.example .env
npm.cmd ci --omit=dev
if ($LASTEXITCODE -ne 0) { throw "Dependency installation failed" }
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 the package into /opt/post-transfer, with your installation account owning it while dependencies are installed. Then:

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

The default .env contains:

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 installation; preserve .env during updates. Match the two backend URLs to the actual ports you configured.

Leave the terminal open while testing. Open http://localhost:18090/ on that same computer. Use /studio to create deliveries, or /client to upload and download with a transfer account. The native SFTPGo client is available locally at http://127.0.0.1:8080/web/client/.

6. Verify the complete transfer path

  1. Sign in with the user created in section 4.
  2. Download a small known file and verify its contents.
  3. Upload a test file, if permitted, and check that it appears 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 that memory use remains reasonable.

At this stage, localhost links work only on the same computer. They cannot be sent to external clients. The desktop application’s public-link feature requires an HTTPS delivery URL; use section 8 before testing that feature.

7. Keep the portal running

Press Ctrl+C in the portal’s test terminal before starting a service on the same port. Service installation is manual and optional. Do not run two copies against port 18090.

Windows service

Download WinSW-x64.exe version 2.12.0 from the official release and put it in C:\PostTransfer. Use the matching XML file supplied in config:

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
if ($LASTEXITCODE -ne 0) { throw "Service installation failed" }
Start-Service PostTransferPortal
Get-Service PostTransferPortal
curl.exe -I http://127.0.0.1:18090/

The XML runs Node as LocalService, with automatic startup and restart on process failure. If Node is installed somewhere other than C:\Program Files\nodejs\node.exe, edit <executable> first. It does not need filesystem access to the transferred files; SFTPGo performs those operations. Keep the installation directory writable only by administrators and the log directory writable by the service. These examples follow WinSW’s v2 XML format.

If installation reports that the service already exists, inspect Get-Service PostTransferPortal rather than repeatedly installing it. Logs are in C:\PostTransfer\logs.

macOS login service

This launch agent survives closing Terminal and starts when your user logs in. It is not a boot-time server before login. Prevent the Mac from sleeping during transfers.

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"

In the file, replace the Node placeholder with the full path printed above, and every /Users/YOUR_USERNAME with the printed home path. XML paths must be absolute; ~ and $HOME do not expand there. Save, 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/

Logs are in ~/PostTransfer/logs. If already loaded, restart with launchctl kickstart -k "gui/$(id -u)/com.joaofariapost.posttransfer". A dedicated Mac server requiring operation before login needs a LaunchDaemon configured for a dedicated account, with persistent storage mounts; that deployment is outside this login-service example.

Linux systemd service

For a new installation, create a dedicated account and give it read access to the application. This example assumes the account does not already exist:

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 actual system-wide Node path if it is 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/

Read logs with sudo journalctl -u post-transfer -n 50 --no-pager. The system account needs no direct access to the shared files.

8. Enable public HTTPS access

Use your own subdomain, such as transfer.example.com. The branding website is not a hosted transfer endpoint provided with this package.

  1. Point that domain’s DNS to the server’s public IP; configure dynamic DNS if the address changes.
  2. Forward TCP ports 80 and 443 to this computer when it is behind a router. Check for CGNAT with your ISP if inbound access is unavailable.
  3. Confirm no other web server owns those ports. Integrate into an existing reverse proxy instead of starting a competing one.
  4. Edit the portal’s .env: set PUBLIC_URL=https://transfer.example.com using your real domain and COOKIE_SECURE=true. Keep HOST=127.0.0.1.
  5. Restart the portal service, then configure Caddy below.

Use config/Caddyfile.example, replacing its domain. It proxies the portal and the native SFTPGo client/API. Administration pages remain local; administer from the server or an SSH tunnel.

transfer.example.com {
    @backend path /web/client /web/client/* /static/* /api/v2/*
    handle @backend {
        reverse_proxy 127.0.0.1:8080
    }
    handle {
        reverse_proxy 127.0.0.1:18090
    }
}

Caddy manages HTTPS certificates for eligible public names when the required network access is available. Keep its certificate storage persistent. Caddy operating guidance.

Windows HTTPS service

Copy the example to C:\PostTransfer\Caddyfile and edit the domain. Add this global block at its very beginning so the service uses a known writable certificate directory:

{
    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
if ($LASTEXITCODE -ne 0) { throw "Caddy configuration invalid" }
Copy-Item WinSW-x64.exe PostTransferCaddy.exe
Copy-Item config\PostTransferCaddy.xml PostTransferCaddy.xml
& .\PostTransferCaddy.exe install
if ($LASTEXITCODE -ne 0) { throw "HTTPS service installation failed" }
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
Get-Service PostTransferPortal,PostTransferCaddy

macOS HTTPS

For a new Caddy installation, copy the edited example to $(brew --prefix)/etc/Caddyfile. Preserve an existing file and merge your site block 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"

Only start after validation succeeds. Allow inbound connections to Caddy if macOS asks. The Homebrew Caddy service stores state under its configured Homebrew data directory; do not delete it. The portal login-agent limitation still applies, even if Caddy runs at boot. Homebrew Caddy service definition.

Linux HTTPS

With Caddy installed from its official system package, add the edited site block to /etc/caddy/Caddyfile. Then:

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

Only continue after successful validation. Allow inbound TCP 80/443 in your host and provider firewalls. If UFW is already your firewall, use sudo ufw allow 80/tcp and sudo ufw allow 443/tcp. Do not expose backend ports 8080, 10081 or 18090.

External check

Open the real HTTPS address from a different connection, such as a phone on mobile data. Verify a login, file download, upload if allowed, and a new public delivery link. Existing links containing localhost need to be copied again with the public domain. Do not publish a deployment that only works with certificate verification disabled.

9. Run or build the desktop client

The desktop source is included under desktop. It can connect to a separately installed SFTPGo server or open an HTTPS public delivery link. You do not need to install the portal on every client’s computer.

Open a terminal in desktop, then run:

Windows:

Set-Location C:\PostTransfer\desktop
npm.cmd ci
if ($LASTEXITCODE -ne 0) { throw "Dependency installation failed" }
npm.cmd start

macOS:

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

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

npm ci && npm start

Electron needs a graphical desktop session and the OS libraries documented in Electron’s development prerequisites. Do not run the desktop app as root or disable its sandbox to work around installation problems.

For account login, enter the server hostname or IPv4 address (no https:// prefix), SFTP port, username, password and the server’s verified SHA256 host-key fingerprint. Get the fingerprint from the server administrator through a trusted channel. The administrator can use ssh-keygen -lf on the active SFTPGo host public-key file and supply its SHA256:... value; locate the active keys through the SFTPGo configuration. Do not accept an unverified fingerprint from a remote scan as proof of identity.

For remote SFTP, permit and forward TCP 2022 to SFTPGo if that is the port configured in section 3. SFTP does not travel through the HTTPS Caddy proxy. Public-link downloads use HTTPS port 443 and do not require an SFTP account.

To produce local application packages, build on the matching operating system. These commands do not use GitHub artifact storage:

Windows portable application:

npm.cmd run dist -- --win portable --x64 --publish never

macOS Apple Silicon (use --x64 on an Intel build host):

npm run dist -- --mac dmg zip --arm64 --publish never

Linux x64:

npm run dist -- --linux AppImage deb --x64 --publish never

Outputs are in desktop/dist. Linux build hosts may need additional packaging tools. Source archives are not executable downloads: build the applications before advertising Windows, Mac or Linux binaries on your website. Sign/notarize public builds using your own publisher credentials where appropriate; none are included here.

Transfer speed is displayed in Mbps or Gbps. Transferred amounts and file sizes use byte units. A percentage and remaining-time estimate require a known total size; streams without that metadata display indeterminate progress. Estimates settle as data arrives and are not a guaranteed completion time. Browser folder downloads without ZIP require the directory-picker API, so support varies by browser.

10. Updates, backups and troubleshooting

Back up the portal .env, SFTPGo configuration/database/host keys, Caddy configuration/certificate data and the actual transferred files. The application source is not a backup of your storage.

Update during a quiet period: stop your portal service, preserve .env, replace application source, run npm ci --omit=dev inside server, then start the service and repeat section 6. Restarting the portal invalidates its in-memory login sessions. The service’s restart policy handles process failures; it is not an end-to-end uptime monitor.

Symptom Check
WebDAV 401 in an unauthenticated curl test Expected; retry through the portal with a transfer user
ECONNREFUSED Backend running, WebDAV enabled, matching port 10081
Fetch reports bad port Replace 10080 with 10081 in both backend and .env
502 Bad Gateway Portal/backend listener, configured proxy address and logs
Login works but files return 403 User home permissions, protocol restrictions, required password change
Public link works only on the server Wrong PUBLIC_URL, DNS, port forwarding, firewall or CGNAT
HTTPS fails to start Port conflict, DNS, certificate challenge reachability, service storage permissions
Network folder unavailable after logout/reboot Service-account access and persistent mount
Missing ETA or percentage Unknown total length or no data yet; inspect transfer state and bytes
Low speed Compare the same file and client through local backend, local portal and public HTTPS; check disk, CPU, network and configured user limits

Native services avoid the Docker Desktop network path used in some deployments, but no fixed throughput is promised. Performance depends on the complete storage and network route.

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 THIRD-PARTY-NOTICES.md.