Sending one large file should not require creating another cloud account. Nor should collecting a document from a client mean asking them to email six compressed attachments. A small open-source, self-hosted file-sharing service can handle both jobs: you upload a file, send an expiring link, or give somebody a private guest upload link that drops files into a folder you choose.

That is the appeal of Filux, an AGPL-licensed open-source alternative to WeTransfer-style transfer links. Its clean interface, controlled public shares, and Request Files workflow cover the most common file-transfer jobs without another subscription. This guide covers both Docker Compose and native Linux installations, with HTTPS, restricted network access, and a backup plan.

What Filux does

Filux sits between a full cloud suite such as Nextcloud and a temporary file-transfer service. You get nested folders, multi-file uploads, previews, storage quotas, and user management without having to adopt an entire groupware platform. It is a focused self-hosted file-sharing app, not a complete Google Drive replacement.

The sharing controls are the interesting part:

  • Public download links can have passwords, expiry times, and usage limits.
  • User-to-user shares distinguish viewing, downloading, editing, and deleting.
  • Request Files links reverse the flow, allowing guests to upload into a selected folder.
  • Accounts can use TOTP two-factor authentication, passkeys, or an OIDC identity provider.
  • Administrators can control registration and assign default or per-user storage quotas.

The current Filux repository also documents bulk archive handling, shared-item download counts, HEIC/HEIF previews, and paid-download support. WebDAV is still on the roadmap, so do not choose Filux expecting native WebDAV mounts today.

Its stack is React, Node.js with Fastify, PostgreSQL, Redis, and local filesystem storage. The production container serves the application through Nginx and applies database migrations during startup.

Before you begin

You need a Linux server and a domain such as filux.example.com pointing to it. The Docker method requires Docker Engine and the Compose plugin; the native method has its own dependency checklist below. Check Compose if you will use Docker:

docker compose version 

The Docker examples use /srv/filux for the stack and expose the application only on the server's loopback address. Nginx accepts public traffic and forwards it to Filux. PostgreSQL and Redis do not have host ports at all.

Storage warning: Neither a container volume nor a native upload directory is a backup. Put the uploads on storage with enough free space, set quotas before inviting users, and keep a separate copy of anything you cannot afford to lose.

Create the Docker Compose file

Create the application directory:

sudo mkdir -p /srv/filux cd /srv/filux 

Save this as compose.yaml. It is adapted from the upstream production file, with the application port bound to 127.0.0.1 instead of every network interface:

name: filux services: postgres: image: postgres:17-alpine container_name: filux-postgres environment: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB} volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"] interval: 10s timeout: 5s retries: 5 restart: unless-stopped redis: image: redis:7-alpine container_name: filux-redis command: redis-server --requirepass ${REDIS_PASSWORD} volumes: - redisdata:/data healthcheck: test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"] interval: 10s timeout: 5s retries: 5 restart: unless-stopped filux: image: bmcgonag/filux:latest container_name: filux env_file: - .env.production environment: DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB} REDIS_URL: redis://default:${REDIS_PASSWORD}@redis:6379 APP_URL: ${APP_URL} ports: - "127.0.0.1:8080:80" volumes: - uploads:/app/uploads depends_on: postgres: condition: service_healthy redis: condition: service_healthy healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:80/api/v1/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s restart: unless-stopped volumes: pgdata: redisdata: uploads: 

The three named volumes keep the database, Redis data, and uploaded files independent of the container lifecycle. A normal docker compose down preserves them. Never add -v unless you deliberately intend to delete those volumes.

Configure secrets and the public domain

Create .env.production in the same directory:

# Database POSTGRES_USER=filux POSTGRES_PASSWORD=REPLACE_WITH_A_RANDOM_HEX_VALUE POSTGRES_DB=filux # Redis REDIS_PASSWORD=REPLACE_WITH_A_DIFFERENT_RANDOM_HEX_VALUE # Application secrets JWT_SECRET=REPLACE_WITH_A_THIRD_RANDOM_HEX_VALUE ENCRYPTION_KEY=REPLACE_WITH_EXACTLY_32_RANDOM_ASCII_CHARACTERS # Uploads and sessions UPLOAD_DIR=/app/uploads MAX_UPLOAD_SIZE_MB=10240 SESSION_TTL_HOURS=24 RATE_LIMIT_MAX=5 RATE_LIMIT_WINDOW_MINUTES=15 # Public URL and passkey settings CORS_ORIGIN=https://filux.example.com WEB_AUTHN_RP_NAME=Filux WEB_AUTHN_RP_ID=filux.example.com WEB_AUTHN_RP_ORIGIN=https://filux.example.com APP_URL=https://filux.example.com 

Generate four independent values:

openssl rand -hex 32 # POSTGRES_PASSWORD openssl rand -hex 32 # REDIS_PASSWORD openssl rand -hex 32 # JWT_SECRET openssl rand -base64 24 # ENCRYPTION_KEY: exactly 32 ASCII characters 

Use hexadecimal passwords for PostgreSQL and Redis so the connection URLs do not need percent-encoding. Filux currently converts ENCRYPTION_KEY to a 32-byte UTF-8 key; the base64 command produces exactly 32 ASCII characters. Do not reuse the example placeholders.

Replace filux.example.com everywhere with your real hostname. WEB_AUTHN_RP_ID is the bare domain, while APP_URL, CORS_ORIGIN, and WEB_AUTHN_RP_ORIGIN include https:// and no trailing slash. These values must match the public address or passkeys and redirects will fail.

Protect the file and restrict its ownership to the account that administers the stack:

chmod 600 .env.production 

Anybody who can read this file or inspect the containers can recover the application secrets. Membership in the docker group is effectively root access, so do not hand it out casually.

Start Filux and check its health

Run Compose with --env-file explicitly:

cd /srv/filux docker compose --env-file .env.production pull docker compose --env-file .env.production up -d docker compose --env-file .env.production ps 

That flag matters: the service's env_file: directive passes variables into the container, but --env-file also lets Compose substitute values such as ${POSTGRES_USER} while constructing the database and Redis connection URLs.

Wait for the health checks, then query the application from the server:

curl --fail http://127.0.0.1:8080/api/v1/health 

If it is not ready, inspect the logs rather than repeatedly recreating the containers:

docker compose --env-file .env.production logs --tail=100 filux postgres redis 

Put Nginx and HTTPS in front

Because port 8080 is bound to loopback, it is not exposed to the LAN or internet. Install Nginx and Certbot, then create /etc/nginx/sites-available/filux:

server { listen 80; server_name filux.example.com; client_max_body_size 10g; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_request_buffering off; proxy_read_timeout 3600s; proxy_send_timeout 3600s; } } 

The body-size setting matches MAX_UPLOAD_SIZE_MB=10240. Lower both if you do not want 10 GB uploads. Disabling request buffering avoids Nginx first spooling the entire upload to its own temporary storage, but you should still monitor disk space while testing large transfers.

Enable the site, test the configuration, and request a certificate after DNS resolves correctly:

sudo ln -s /etc/nginx/sites-available/filux /etc/nginx/sites-enabled/filux sudo nginx -t sudo systemctl reload nginx sudo certbot --nginx -d filux.example.com 

Only ports 80 and 443 need to be reachable from the internet. Do not add a firewall rule for port 8080. The Nginx reverse proxy guide explains the forwarding headers and certificate flow in more detail.

Native installation on Linux without Docker

Use this section instead of the Docker Compose and Docker-specific Nginx configuration above if you want to run Filux directly on the host. The application has two parts: Nginx serves the compiled React client, while a systemd service runs the Fastify API on port 3001.

For a new native installation, use Node.js 22 LTS, PostgreSQL 17 or newer, Redis 7, Git, Python 3, and a C/C++ build toolchain. Install them through your distribution or the projects' official repositories. The build tools are needed if native Node.js modules cannot use prebuilt binaries. Check the toolchain before continuing:

node --version npm --version psql --version redis-server --version git --version python3 --version make --version g++ --version 

Create the service account and database

Create an unprivileged account and directories for the source and uploads:

sudo useradd --system --user-group --home-dir /srv/filux --shell /usr/sbin/nologin filux sudo mkdir -p /srv/filux/uploads /etc/filux sudo chown -R filux:filux /srv/filux sudo -u filux -H git clone https://forge.routemehome.org/brian/Filux.git /srv/filux/source 

Create a PostgreSQL login and database. The --pwprompt option avoids putting the database password in your shell history:

sudo -u postgres createuser --pwprompt filux sudo -u postgres createdb --owner=filux filux 

Bind PostgreSQL and Redis to loopback, and do not expose ports 5432 or 6379 to the network. Configure Redis with a strong requirepass value in your distribution's Redis configuration, restart Redis, and use that password in REDIS_URL below.

Build the client and API

Build the browser application first, then the server and its generated Prisma client:

cd /srv/filux/source/client sudo -u filux -H npm ci sudo -u filux -H npm run build cd /srv/filux/source/server sudo -u filux -H npm ci sudo -u filux -H npx prisma generate sudo -u filux -H npm run build 

The client build creates static files in client/dist; the server build creates executable JavaScript in server/dist. Do not run the Vite development server as a production service.

Configure the native service

Create /etc/filux/filux.env with your own secrets and domain. Use the same four random-value commands from the Docker section:

NODE_ENV=production PORT=3001 DATABASE_URL=postgresql://filux:POSTGRES_PASSWORD@127.0.0.1:5432/filux REDIS_URL=redis://default:REDIS_PASSWORD@127.0.0.1:6379 JWT_SECRET=REPLACE_WITH_A_RANDOM_HEX_VALUE ENCRYPTION_KEY=REPLACE_WITH_EXACTLY_32_RANDOM_ASCII_CHARACTERS UPLOAD_DIR=/srv/filux/uploads MAX_UPLOAD_SIZE_MB=10240 SESSION_TTL_HOURS=24 RATE_LIMIT_MAX=5 RATE_LIMIT_WINDOW_MINUTES=15 CORS_ORIGIN=https://filux.example.com WEB_AUTHN_RP_NAME=Filux WEB_AUTHN_RP_ID=filux.example.com WEB_AUTHN_RP_ORIGIN=https://filux.example.com APP_URL=https://filux.example.com 

Replace POSTGRES_PASSWORD and REDIS_PASSWORD inside the connection URLs. If either password contains characters such as @, :, /, or #, percent-encode it or use the hexadecimal values shown earlier. Keep this file readable by root and the service group only:

sudo chown root:filux /etc/filux/filux.env sudo chmod 640 /etc/filux/filux.env 

Apply the committed database migrations with the production command, not prisma migrate dev:

sudo -u filux -H sh -c 'set -a; . /etc/filux/filux.env; set +a; cd /srv/filux/source/server; npx prisma migrate deploy' 

Keep the API running with systemd

Check where Node.js is installed with command -v node. If it is /usr/bin/node, save this as /etc/systemd/system/filux.service; otherwise adjust ExecStart to the reported path:

[Unit] Description=Filux API After=network-online.target Wants=network-online.target [Service] Type=simple User=filux Group=filux WorkingDirectory=/srv/filux/source/server EnvironmentFile=/etc/filux/filux.env ExecStart=/usr/bin/node /srv/filux/source/server/dist/index.js Restart=on-failure RestartSec=5 UMask=0027 NoNewPrivileges=true PrivateTmp=true ProtectHome=true ProtectSystem=strict ReadWritePaths=/srv/filux/uploads [Install] WantedBy=multi-user.target 

ProtectSystem=strict makes the host filesystem read-only to the service except for the upload directory listed in ReadWritePaths. Start the API and check it from the host:

sudo systemctl daemon-reload sudo systemctl enable --now filux systemctl status filux curl --fail http://127.0.0.1:3001/api/v1/health 

If the service fails, use journalctl -u filux -n 100 --no-pager to see the actual error. The upstream API currently listens on 0.0.0.0:3001, so keep port 3001 blocked by the host firewall even though Nginx connects through loopback. Only Nginx should be public.

Serve the native client through Nginx

For a native installation, use this /etc/nginx/sites-available/filux instead of the Docker proxy block:

server { listen 80; server_name filux.example.com; root /srv/filux/source/client/dist; index index.html; client_max_body_size 10g; location /api/ { proxy_pass http://127.0.0.1:3001; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_request_buffering off; proxy_read_timeout 3600s; proxy_send_timeout 3600s; } location / { try_files $uri $uri/ /index.html; } } 

The try_files fallback is required because Filux is a single-page application: Nginx should serve real files when they exist and send other client-side routes to index.html. Enable the site and use the same nginx -t, reload, and Certbot commands shown in the Docker section. Then continue with the first-run checklist below.

For later native updates, take a backup, pull the chosen source revision, repeat the client and server install/build steps, run prisma migrate deploy, and restart the filux service. Test login, uploads, and existing shares before returning the instance to normal use.

Complete the first-run setup carefully

Open your HTTPS URL and register the first account. According to the upstream documentation, the first registered user becomes the administrator automatically. Complete this step yourself immediately after deployment; do not leave a fresh public instance unattended.

After signing in:

  1. Disable open registration if the instance is only for people you know.
  2. Create users manually and assign storage quotas appropriate to your disk.
  3. Enable TOTP or a passkey on the administrator account.
  4. Configure SMTP if you want email shares and password-reset messages to work.
  5. Upload a harmless test file and verify both a public link and a Request Files link from a private browser window.

That last test catches mismatched APP_URL values, proxy timeouts, and permission problems before somebody sends you an urgent upload.

Share files without sharing your whole server

For an ordinary outbound share, upload the file or folder, open the sharing dialog, and create a public link. Use an expiry and download limit that match the purpose. A password adds a useful barrier, but it does not identify the person downloading the file: anybody who obtains both the link and password may be able to use them.

If a link leaks, revoke or delete it and create a new one rather than assuming the old URL will expire before someone finds it.

Request Files makes Filux particularly useful for collecting documents and other uploads. Instead of giving a collaborator an account, you create an inbound link for a destination folder. The guest opens that link and uploads directly to your server.

Use a separate destination folder for each outside party or job. Set a password, expiry, and upload limit where appropriate. Treat every uploaded file as untrusted: do not run guest-supplied scripts, open documents with macros enabled, or automatically extract archives merely because they came through your own website.

Back up three different things

A useful Filux backup includes more than uploaded files:

  1. The PostgreSQL database, which holds users, permissions, shares, and application state.
  2. The uploaded files, in the Docker uploads volume or the native /srv/filux/uploads directory.
  3. The environment and configuration, including .env.production for Docker or /etc/filux/filux.env, Compose, Nginx, and systemd for native. Preserve the encryption key needed to recover encrypted application secrets.

Create a consistent database dump through PostgreSQL rather than copying its live data directory. For Docker:

mkdir -p /srv/filux/backups chmod 700 /srv/filux/backups docker compose --env-file .env.production exec -T postgres \ pg_dump -U filux -Fc filux \ > "/srv/filux/backups/filux-$(date +%F-%H%M).dump" 

For a native installation, dump through the local PostgreSQL service instead:

sudo -u postgres pg_dump -Fc filux \ > "/srv/filux/backups/filux-native-$(date +%F-%H%M).dump" 

Back up the Docker volume or native upload directory with your normal filesystem/restic process, and store the environment file separately in an encrypted backup. Do not publish it alongside the Compose file. The self-hosted backup strategy explains how to turn these pieces into local and off-site copies.

Most importantly, restore a database dump and open some restored files on a test instance. A backup job that has never been restored is only a promise.

Before updates, take a fresh backup and read the project's release notes. The upstream Docker example uses bmcgonag/filux:latest; for a stable production system, record the digest of the image you tested and pin it rather than allowing every pull to select a new build. For native updates, follow the build and migration sequence in the native section. After an update, test login, uploads, existing public links, and file requests—not just the home page.

Filux or Nextcloud?

Choose Filux when the main job is focused file storage, controlled public downloads, and collecting files from other people. Its Request Files links are a natural fit for designers collecting assets, accountants receiving documents, or a small team that needs a private alternative to transfer links.

Choose Nextcloud when you want desktop and mobile synchronization, calendars, contacts, collaborative apps, and a broader personal cloud. It carries more moving parts because it solves a bigger problem. My Nextcloud self-hosting guide covers that broader setup.

The important distinction is operational, not just visual. Filux can make sharing simple for other people, but you still own the disk capacity, HTTPS renewal, updates, account security, and restores. Start with a small quota and a test folder, prove the backup, and only then send the link to somebody who depends on it.