open-transfer

Self-hosting

Open Transfer runs happily on a laptop you start when needed, but also as an always-on service on a home server, NAS or Raspberry Pi.

Docker

The repository includes a Dockerfile and docker-compose.yml.

git clone https://github.com/itsonu/open-transfer.git
cd open-transfer
docker compose up -d

Edit docker-compose.yml to set options through environment variables:

    environment:
      OPEN_TRANSFER_PUBLIC_URL: "http://192.168.1.24:5000"   # this host's LAN address
      OPEN_TRANSFER_PIN: "4821"
      OPEN_TRANSFER_MAX_SIZE: "8G"

Why PUBLIC_URL? Inside a container the app only sees the container’s private address, so it can’t know which address your phone should use. PUBLIC_URL sets the link and QR code shown in Add a device. On Linux you can alternatively use network_mode: host (and remove ports:) so the app detects the real address itself.

Files are stored in ./uploads on the host (mounted at /data). The container runs as UID 1000; if you mount a folder owned by another user, add user: "UID:GID" to the service.

Release images are published to ghcr.io/itsonu/open-transfer for linux/amd64 and linux/arm64 (Raspberry Pi 4/5).

systemd service (Linux)

sudo useradd --system --create-home --home-dir /srv/open-transfer open-transfer
sudo -u open-transfer python3 -m venv /srv/open-transfer/venv
sudo -u open-transfer /srv/open-transfer/venv/bin/pip install git+https://github.com/itsonu/open-transfer

/etc/systemd/system/open-transfer.service:

[Unit]
Description=Open Transfer
After=network-online.target
Wants=network-online.target

[Service]
User=open-transfer
ExecStart=/srv/open-transfer/venv/bin/open-transfer /srv/open-transfer/files --no-browser --no-qr --pin 4821
Restart=on-failure
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/srv/open-transfer
PrivateTmp=true

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now open-transfer
journalctl -u open-transfer -f      # see who sent what

HTTPS with a reverse proxy

Plain HTTP is fine on a network you trust. For encryption (or a friendly name like files.home.arpa), put a reverse proxy in front and tell Open Transfer about it:

open-transfer /srv/files --host 127.0.0.1 --behind-proxy \
  --public-url https://files.example.com --allow-host files.example.com

Caddy

files.example.com {
    reverse_proxy 127.0.0.1:5000
}

Caddy streams request bodies and has no upload size limit by default.

nginx

server {
    listen 443 ssl;
    server_name files.example.com;
    # ssl_certificate ... ;

    client_max_body_size 0;          # no upload limit (Open Transfer enforces --max-size)
    proxy_request_buffering off;     # stream uploads instead of spooling them
    proxy_buffering off;             # stream downloads and ZIPs
    proxy_read_timeout 1h;
    proxy_send_timeout 1h;

    location / {
        proxy_pass http://127.0.0.1:5000;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $host;
    }
}

Exposing Open Transfer to the public internet is not recommended. If you do, always use a strong PIN (letters and digits) and HTTPS, and consider --receive-only.

Tips