This guide is for the administrators, operators, and developers who deploy, secure, tune, and extend copyparty. It covers the architecture, the complete configuration model, security hardening, operational procedures, and the internals needed to understand — or reproduce — the project. End-user features (the browser UI, uploading, media playback) live in the User Guide; this guide links there where appropriate.
copyparty is version 1.20.16 ("sftp is fine too"), a portable file server written in Python with accelerated resumable uploads, deduplication, WebDAV/SFTP/FTP/TFTP/SMB protocols, zeroconf discovery, a media indexer, video thumbnails, audio transcoding, and write-only folders. Its single mandatory dependency is jinja2; everything else is optional and feature-gated.
1. Executive Summary
| Aspect | Detail |
|---|---|
| What it is | A single-process, multi-protocol file server runnable from one .py file |
| Language | Python (2.7 and 3.3+), no compiled components required |
| Mandatory deps | jinja2 only (bundled in sfx/exe) |
| Default HTTP port | 3923 (HTTP and HTTPS on the same port) |
| Config model | Global options + per-volume "volflags"; CLI args and/or YAML-ish config files |
| Auth model | Accounts + per-folder permissions; optional IdP / header / IP-range auth |
| Storage | Plain filesystem volumes; optional SQLite index per volume (.hist/up2k.db) |
| Deployment | Standalone, systemd, OpenRC, Docker/Podman, NixOS, behind a reverse proxy |
| License | MIT (with optional GPL tooling kept strictly out-of-process) |
Design philosophy — "inverse unix philosophy": do all the things and do an okay job. The priorities are: run anywhere, support every browser and Python version, minimize mandatory dependencies, and require no build step (the JS and Python are editable in place).
Note
Becoming rich is explicitly not a project goal. This shapes the architecture: simplicity, portability, and plaintext auditability are valued over polish or enterprise integration.
2. Architecture
2.1 High-level component map
┌──────────────────────────────────────────┐
│ SvcHub (svchub.py) │
│ orchestrator: config, logging, lifecycle, │
│ signal handling, service startup/reload │
└───────────────┬────────────────────────────┘
│ owns / starts
┌──────────────┬──────────────┬───┴────────┬──────────────┬───────────────┐
▼ ▼ ▼ ▼ ▼ ▼
┌─────────┐ ┌──────────┐ ┌─────────┐ ┌──────────┐ ┌──────────┐ ┌────────────┐
│ AuthSrv │ │ HttpSrv │ │ Up2k │ │ ThumbSrv │ │ Ftpd / │ │ MDNS/SSDPd │
│ authsrv │ │ httpsrv │ │ up2k.py │ │ th_srv │ │ Sftpd / │ │ zeroconf │
│ .py │ │ .py │ │ │ │ │ │ Tftpd / │ │ │
│ VFS + │ │ per-conn │ │ upload │ │ image/ │ │ SMB │ │ LAN announce│
│ accounts│ │ HttpConn │ │ engine, │ │ video/ │ │ servers │ │ │
│ + vols │ │ +HttpCli │ │ dedup, │ │ audio │ │ │ │ │
└─────────┘ └────┬─────┘ │ index │ │ thumbs │ └──────────┘ └────────────┘
│ └────┬────┘ └──────────┘
▼ ▼
┌─────────┐ ┌──────────┐
│ U2idx │ │ MTag │
│ u2idx.py│ │ mtag.py │
│ search │ │ metadata │
└─────────┘ └──────────┘
TcpSrv (tcpsrv.py) — owns sockets / interface detection / port binding
BrokerMp / BrokerThr — message bus between processes (multiprocessing) or threads
2.2 Core components
| Component | Module | Responsibility |
|---|---|---|
| SvcHub | svchub.py |
Master orchestrator. Sets up DB, logging, signal handlers; starts/stops/reloads FTP/SFTP/TFTP/HTTP servers; owns the broker. |
| AuthSrv | authsrv.py |
Parses accounts, groups, and volumes; builds the VFS (virtual filesystem) tree; resolves per-request permissions; loads IdP/session databases. |
| VFS | authsrv.py |
The virtual filesystem — maps URL paths to real folders, applies volflags, computes access (can_access, get_perms), supports shadowing and bubbling flags. |
| HttpSrv | httpsrv.py |
Accepts HTTP(S) connections, manages worker threads, dispatches each to an HttpConn. |
| HttpConn | httpconn.py |
One TCP connection; handles TLS handshake, reverse-proxy detection, and hands requests to HttpCli. |
| HttpCli | httpcli.py |
The request handler — an ~80-method class implementing every HTTP verb, the HTTP API, uploads, downloads, listings, search, and the web UI. |
| Up2k | up2k.py |
The resumable-upload engine: chunked hashing, dedup (symlink/hardlink/reflink), the SQLite index, file-search backend, lifetime/self-destruct. |
| U2idx | u2idx.py |
Query engine over the up2k index — powers search and file-search. |
| MTag | mtag.py |
Media-tag extraction via Mutagen or FFprobe, plus external parser plugins (mtp). |
| ThumbSrv / ThumbCli | th_srv.py / th_cli.py |
On-demand thumbnail and audio-transcode generation (Pillow / pyvips / FFmpeg), with a disk cache. |
| TcpSrv | tcpsrv.py |
Socket creation, NIC/interface detection, port binding, network monitoring. |
| BrokerMp / BrokerThr | broker_mp.py / broker_thr.py |
Inter-node message bus. BrokerMp spans real OS processes (multiprocessing); BrokerThr is the single-process threaded fallback. |
2.3 Protocol servers
Each non-HTTP protocol is an optional, self-contained server bound to its own port:
| Protocol | Module | Backend library | Default port |
|---|---|---|---|
| FTP / FTPS | ftpd.py |
pyftpdlib (+ pyopenssl for TLS) |
3921 / 3990 |
| SFTP | sftpd.py |
paramiko |
3922 |
| SMB / CIFS | smbd.py |
impacket |
3945 (→445) |
| TFTP | tftpd.py |
partftpy |
3969 (→69) |
| WebDAV | within httpcli.py |
— (shares the HTTP port) | 3923 |
| mDNS / SSDP | mdns.py / ssdp.py (multicast.py) |
bundled stolen/dnslib |
5353 / 1900 UDP |
Important
The HTTP/HTTPS API and WebDAV share port 3923. FTP, SFTP, TFTP, and SMB each require a dedicated port that cannot be shared with HTTP.
2.4 Multiprocessing model
By default copyparty runs single-process with a thread pool. Passing -j N (or -j0 for all cores) spawns multiple worker processes coordinated through BrokerMp. Multiprocessing reduces latency under heavy concurrency and accelerates directory listings when the index is enabled, but it increases filesystem/HDD load and adds inter-process overhead, so the single-process default is best for most deployments.
Caution
Several Prometheus metrics (
cpp_http_conns,cpp_http_reqs,cpp_sus_reqs,cpp_active_bans,cpp_total_bans) are counted incorrectly when multiprocessing is enabled, because each process counts independently.
2.5 Bundled ("stolen") dependencies
To keep the mandatory dependency list at one (jinja2), copyparty vendors a few small libraries under copyparty/stolen/: dnslib (for mDNS), ifaddr (NIC discovery), and qrcodegen.py (QR codes). Each can be forced to use the system version via PRTY_SYS_DNSLIB, PRTY_SYS_IFADDR, PRTY_SYS_QRCG, or all at once with PRTY_SYS_ALL.
3. System Requirements
| Resource | Requirement |
|---|---|
| Python | 2.7, or 3.3 through 3.14. CPython, PyPy, and Jython are all supported. |
| CPU/RAM | Minimal — known to run on an SGI O2 with 64 MiB RAM. RAM scales with thumbnail engine (pyvips uses ~270 % more than Pillow but is faster). |
| Disk | Volume storage + per-volume .hist/ (index DB, thumbnail cache, transcode cache, markdown history). Put .hist/ on an SSD for best performance. |
| OS | Windows, Linux, macOS, FreeBSD, Android, iOS, and many exotic platforms (big-endian, IRIX, etc.). |
Optional dependencies and the features they unlock
| Dependency | Feature |
|---|---|
argon2-cffi |
Hashed passwords in config (--ah-alg argon2) |
pyftpdlib |
FTP server (bundled in sfx) |
pyftpdlib + pyopenssl |
FTPS (TLS) |
paramiko |
SFTP server |
partftpy>=0.4.0 |
TFTP server |
impacket==0.13.0 |
SMB/CIFS server (not recommended) |
mutagen or ffprobe |
Audio/media tag indexing |
Pillow and/or pyvips and/or ffmpeg |
Image thumbnails |
ffmpeg + ffprobe |
Video/audio thumbnails and transcoding |
pyvips / ffmpeg / pillow-heif |
HEIF/HEIC thumbnails |
libraw dcraw_emu or rawpy |
RAW-photo thumbnails |
pyzmq |
ZeroMQ messages from event hooks |
python-magic |
Filetype detection for nameless uploads |
psutil |
Reaping stuck hooks/plugins on Windows |
The pip extras map directly to these: pip install copyparty[all], or granular extras thumbnails, thumbnails2, audiotags, sftp, ftpd, ftps, tftpd, pwhash, zeromq.
Warning
mutagenmakes copyparty's effective license GPL-adjacent and is fast but skips some tags.ffprobeis ~20× slower, catches more tags (key, resolution, framerate), avoids GPL code — but runs FFmpeg on incoming files, which is dangerous if your FFmpeg has an unpatched CVE and you allow anonymous upload.
4. Configuration Mastery
copyparty has two configuration scopes:
- Global options — server-wide; set as CLI flags (
--foo) or in the config-file[global]section. - Volflags — per-volume; set inline on a
-vflag (:c,foo=bar) or in a volume'sflags:block.
Most options work in both scopes. The authoritative, always-current list of every option is copyparty --help, every volflag is --help-flags, and accounts syntax is --help-accounts.
4.1 Volume and account syntax
-a USERNAME:PASSWORD
-v SRC:DST:PERM[,USER...]:PERM[,USER...]:c,VOLFLAG[=VALUE]
SRC= real filesystem path,DST= URL path, then permission groups (see User Guide §6.1), thenc,-prefixed volflags.- Example combining everything:
-v /mnt/ss:i:rw,u1:g:c,fk=4 # /mnt/ss served at /i; read-write for u1; get-only for everyone else; filekey length 4
4.2 Config file structure
[global]
e2dsa # global options, one per line; value after the name
z # boolean flags need no value
lo: /var/log/cpp/%Y-%m-%d.txt
[accounts]
ed: hunter2 # comments need TWO spaces before #
[groups]
admins: ed, k
[/url/path] # a volume
/srv/realpath # the folder it maps to
accs:
r: *
rw: ed
flags:
e2ds
fk: 4
Tip
A config file may pass additional CLI arguments through
[global]. You can chain multiple-c file.conf. SetPRTY_CONFIG=foo.confas an environment variable as an alternative to-c.
4.3 Option groups (from --help)
copyparty --help is organized into these sections — use them as a map of the configuration surface:
| Group | Covers |
|---|---|
| general options | core behavior, ports, -c config files |
| qr options | --qr, --qrs, --qrl, --qr-file, … |
| filesystem options | volumes, --hist, --xdev, --xvol, dedup |
| share-url options | --shr, --shr-adm, --shr-who, --shr-rt |
| upload options | --u2sz, --u2ow, --unpost, lifetime, rules |
| network options | -i, -p, --xff-hdr, --xff-src, --rproxy, --rp-loc |
| SSL/TLS options | --cert, --certkey, --no-crt |
| TLS certificate generator | cfssl-based CA/cert generation, --crt-* |
| IdP / identity provider | --idp-*, --auth-ord, header auth |
| user-changeable passwords | --chpw, --chpw-no, --chpw-db |
| Zeroconf / mDNS / SSDP | -z, --z-on, --zsv, --name |
| SFTP / FTP / WebDAV / TFTP / SMB | each protocol's flags |
| OPDS options | --opds, --opds-exts |
| handlers / event hooks | --xau, --xbu, etc.; --help-hooks |
| grafana/prometheus metrics | --stats, --nos-* |
| yolo / opt-outs / safety | --allow-csrf, -s/-ss/-sss, --no-* |
| salting options | --fk-salt, --dk-salt, --ah-salt |
| logging options | -q, -lo, --flo, --no-ansi, --log-date |
| admin panel / thumbnail / transcoding | --no-thumb, --th-*, --ac-maxage |
| tailing / RSS / textfile / og / ui | viewer + embed + UI tweaks |
| general & metadata db options | -e2*, --db-*, -mte/-mtp/-mtm |
| debug options | diagnostics |
| help sections | --help-accounts, --help-flags, --help-hooks, … |
4.4 File indexing
Indexing powers search, upload-undo, and reliable dedup. Two tables live in .hist/up2k.db: the up2k filetree (-e2d) and metadata tags (-e2t).
| Flag | Effect |
|---|---|
-e2d |
Index files on upload |
-e2ds |
+ scan writable folders on startup |
-e2dsa |
+ scan all volumes (including read-only) on startup |
-e2t |
Index media tags on upload |
-e2ts |
+ scan untagged files on startup |
-e2tsr |
+ delete all tags and do a full reindex |
-e2v / -e2vu / -e2vp |
Verify integrity at startup / patch DB / panic on mismatch |
The same flags work as volflags; d2d, d2ds, d2t, d2ts, d2v disable indexing per-volume.
Note
The recommended baseline for most deployments is
-e2dsa -e2ts(index everything, scan media tags). Folder thumbnails are chosen during indexing, so a periodic rescan keeps them current.
4.5 Database location
By default each volume gets a .hist/ subfolder. Relocate it with:
--hist /path— global default location.hist=volflag — per-volume override (-restores the in-volume default).--dbpath/dbpathvolflag — move just the SQLite DB, separate from thumbnails.landmarkvolflag — require a known file to exist before touching the DB (guards against NFS/HDD outages causing spurious rescans).
Tip
Put
.hist/on an SSD. On Windows,--histtakes a regular path but the volflag path is cyg-style (/c/temp=C:\temp).
4.6 Deduplication
Enable with --dedup (global) or dedup (volflag). When a duplicate is uploaded, copyparty links it instead of storing a second copy:
| Mode | Flag | Trade-off |
|---|---|---|
| Symlink (default) | --dedup |
Per-copy timestamps; obviously not a regular file |
| Hardlink | --hardlink-only / hardlinkonly |
Looks like a regular file; movable by other tools |
| Reflink (CoW) | --reflink / reflink |
Safest; Linux 5.3+, Python 3.14+, btrfs/xfs only |
Warning
Editing the contents of a deduplicated file edits all copies — most surprising with hardlinks, which look like ordinary files. When enabling dedup, also enable indexing (
-e2dsa) so renames/deletes stay safe.--safe-dedup 1speeds uploads by skipping content re-verification (safe only if no other software touches the files).--no-clonedisables dedup detection entirely (useful for expensive cold storage like S3-Glacier).
4.7 Upload rules (volflags)
| Volflag | Effect |
|---|---|
sz=1k-3m |
Allowed filesize range |
df=4g |
Block uploads below this free disk space |
vmaxb=1g / vmaxn=4k |
Cap total volume bytes / file count (needs e2ds) |
nosub |
Disallow uploads into subdirectories |
rotn=1000,2 |
Auto-bucket uploads into subfolders (1000/folder, 2 deep) |
rotf=%Y/%m/%d/%H |
Force date-structured upload paths |
lifetime=300 |
Self-destruct uploads after N seconds |
maxn=250,3600 |
Per-IP: 250 files / hour |
maxb=1g,300 |
Per-IP: 1 GiB / 5 min |
Note
Per-IP transaction limits (
maxn/maxb) assume-j 1. With multiprocessing the effective limit multiplies by the worker count, since each node tracks independently.
4.8 Compression on upload
gz/xzvolflags allow that algorithm;pkforces compression on all files (pk=xz,0= forced xz level 0).- URL params
?pk,?gz,?xzrequest compression per-upload. - Compressed files are indexed after compression, so dedup/file-search won't match the originals.
4.9 Filesystem guards
--xdev/xdev— never traverse into another filesystem (skip cross-device symlinks/bind-mounts).--xvol/xvol— ignore symlinks leaving the volume root (bind-mounts still allowed).
Since v1.7.0 these also block runtime access, not just indexing. Expect up to ~14 % slower listings and ~35 % slower tar downloads.
4.10 Periodic rescan
If other software modifies the filesystem, keep the index fresh:
--re-maxage 60(global) orscan=60(volflag) — rescan every N seconds.- Uploads pause during a rescan; rescans are deferred by
--db-act(default 10 s) while writes are in progress.
4.11 Metadata configuration
-mte— which tags to index and display (and their order); per-volume viamte=.-mth— index + make searchable but hide from default display.-mtm qux=foo,bar— define/redefine a tag mapping (quxfromfooorbar).-mtp .bpm=~/bin/audio-bpm.py— external parser plugin to provide a tag. Plugins support audio-only (ay), non-audio (an), all (ad), extension filters (eexe), timeouts (t5), ordering (p1), and kill modes (kt/km/kn).--db-xattr user.foo,user.bar— index Linux extended attributes (must also appear in-mte).
4.12 Other notable options
--mime gif=image/jif— override a file extension's MIME type (--mimeslists defaults).cachectl/?cache=321— control browserCache-Control.chmod_f/chmod_d/uid/gidvolflags — per-volume filesystem permissions and ownership (uidrequires running as root).dothidden— cosmetically hide files listed in a folder's.hiddenfile (not a security boundary).--unlist— hide a whole volume from HTTP listings (HTTP-only;dothiddenalso affects ftp/sftp).
5. Network, TLS, and Reverse Proxy
5.1 Listening
-isets the bind interface(s); default::(all).-p 80,443listens on multiple ports.- Listen on a unix socket for ~10 % more performance and tighter access control:
-i unix:770:www:/dev/shm/party.sock.
5.2 Binding to ports 80/443
| Platform | Approach |
|---|---|
| Windows / macOS | Just -p 80,443 |
| Linux (recommended) | A reverse proxy (also gives real HTTPS) |
| Linux (NAT) | iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 3923 |
| Linux (capability) | setcap CAP_NET_BIND_SERVICE=+eip $(realpath $(which python)) |
5.3 HTTPS / TLS
Both HTTP and HTTPS are accepted on the same port. copyparty has built-in TLS, but the recommended setup is to terminate TLS at a reverse proxy:
- Reverse proxies follow TLS best practices better and support HTTP/2 and QUIC (copyparty speaks neither — though HTTP/1.1 is often faster anyway).
- If
cfsslis installed, copyparty auto-generates a self-signed CA + server cert on startup (written to--crt-dir); you must installca.peminto clients. - To supply your own cert:
--no-crt --cert server.pem(key+cert+chain concatenated), or--no-crt --cert server.crt --certkey server.key. All files must be PEM.
5.4 Reverse-proxy and real-IP
When behind any proxy (including Cloudflare), real-IP configuration is critical — bans, rate limits, and IP-auth all rely on the true client IP.
[global]
xff-hdr: cf-connecting-ip # header carrying the real IP (x-forwarded-for for most)
# also configure --xff-src (trusted proxy IP) and --rproxy
--rproxy 1if the header contains exactly one (correct) IP; a negative value picks from a multi-IP header (-1= rightmost/nearest hop,-2= second-nearest, …).--rp-loc=/stufffor location-based proxying (slight perf cost; needs correct real-IP first).--rproxy 0only if copyparty directly faces the internet (otherwise CORS misbehaves).
Reference configs ship in contrib/ for nginx (recommended), apache, haproxy, lighttpd, caddy, and traefik.
Caution
Use traefik v3.6.7 or newer — older versions are affected by CVE-2025-66490.
Reverse-proxy performance (uds, fastest first): haproxy > caddy > traefik > nginx > apache > lighttpd. Connecting via a unix socket beats 127.0.0.1 across the board. nginx-QUIC (HTTP/3) is experimental and can make uploads much slower — prefer HTTP/1.1.
5.5 Zeroconf discovery
-zenables both mDNS and SSDP;--z-on/--z-offrestrict to subnets.- mDNS gives a
.localdomain (e.g.partybox.local, from hostname or--name) visible to KDE/GNOME/macOS file managers. - SSDP makes copyparty appear in Windows Explorer; use
--zsvto debug if it doesn't show.
6. Identity, Authentication, and Access Control
6.1 Built-in accounts
Accounts (-a user:pwd) with per-volume permissions are the baseline (see User Guide §6). Bruteforce protection: --ban-pw (default 24 h ban after 9 fails/hour).
6.2 Identity providers (IdP)
--idp-* options replace copyparty's passwords with an external SSO (Authelia, authentik, OAuth, LDAP, passkeys, etc.):
- Config-defined users serve as a fallback for requests without a trusted IdP header;
--auth-ordsets precedence. --idp-login/--idp-logoutrepoint the UI buttons ({dst}expands to the current page).--idp-cookielets requests bearing thecppwscookie bypass a slow IdP.- Header mapping:
--idp-hm-usr '^Tailscale-User-Login^[email removed]^alice'auto-auths by an upstream header.
A docker-compose example (Authelia + traefik) ships in docs/examples/docker/idp-authelia-traefik; see also docs/idp.md.
6.3 IP-based auth
--ipu 192.168.123.0/24=spartacus— autologin a CIDR range as a user.--ipr 192.168.123.0/24,172.16.0.0/16=spartacus— restrict a user to certain CIDRs.
Caution
With IP-auth behind a reverse proxy, real-IP must be correct, and you should null-map the proxy's own IP (
--ipu=172.24.27.9/32=) to avoid auto-logging-in the proxy.
6.4 User-changeable passwords
--chpw lets users change their own passwords in the control panel (default off because account-sharing is common). Stored in --chpw-db (default chpw.json); --chpw-no name1,name2 denies specific accounts. Not compatible with IdP.
7. HTTP API
Authentication order (first non-blank wins): url-param ?pw=, header PW:, basic-auth, then cookie (cppwd on HTTP, cppws on HTTPS).
7.1 Read operations (GET)
| Params | Result |
|---|---|
?dl |
Download (force, don't render in browser) |
?ls / ?ls&dots / ?ls=t / ?ls=v |
List as JSON / +dotfiles / plaintext / terminal |
?opds |
List as an OPDS feed |
?tree=. / ?tree |
List one level of subdirs / each level to URL |
?tar / ?tar=gz:9 / ?tar=xz:9 / ?tar=pax |
Download subtree as tar (variants) |
?zip / ?zip=dos / ?zip=crc |
Download subtree as zip (variants) |
?tar&w / ?tar&j / ?tar&p |
Pre-generate webp / jpg thumbnails / audio waveforms |
?shares / ?dls / ?ups / ?ru |
List shares / active downloads / your uploads / all recent uploads |
?v |
Render markdown, or open media in the player |
?txt / ?txt=iso-8859-1 |
Get file as plaintext (with charset) |
?tail / ?tail=1024 / ?tail=-128 |
Stream a growing file (from byte / from end) |
?th / ?th=opus / ?th=caf |
Thumbnail / transcode audio to opus / caf |
?zls / ?zget=path |
List / fetch a file inside a zip at URL |
?mime=foo |
Force a response MIME type |
7.2 Write operations
| Method | Params / Body | Result |
|---|---|---|
| PUT | (binary) | Upload into file at URL |
| PUT | ?j / ?ck / ?ck=md5 |
+ JSON reply / no checksum / md5 |
| PUT | ?gz / ?xz / ?apnd |
Compress with gzip/xz / append |
| mPOST | f=FILE |
Upload FILE into folder at URL |
| mPOST | ?replace / ?apnd / ?media |
Overwrite / append / return medialink |
| mPOST | act=mkdir, name=foo |
Create directory |
| mPOST | act=tput, body=TEXT |
Overwrite a markdown doc |
| POST | ?copy=/foo / ?move=/foo |
Copy / move-rename |
| POST | ?delete |
Delete URL recursively |
| jPOST | ?delete ["/foo","/bar"] |
Delete multiple |
| jPOST | ?share (payload) |
Create a temporary share URL |
| POST | ?eshare=rm / ?eshare=3 |
Stop sharing / set 3-min expiry |
| uPOST | msg=foo |
Send a message into the server log |
Upload modifiers (header or url-param): want=url/want=json, rand=4, life=30, replace, and checksum selectors ck, ck=md5, ck=sha1, ck=sha256, ck=b2, ck=b2s. copyparty returns a truncated, url-safe base64 sha512 of the upload by default.
7.3 Admin / general
| Params | Result |
|---|---|
?reload=cfg |
Reload config files and rescan volumes |
?scan / ?scan=/a,/b |
Rescan the providing volume / specific volumes |
?stack |
Dump all thread stacktraces (diagnostics) |
?grid / ?imgs / ?thumb |
UI view toggles |
8. Security
8.1 Hardening checklist
- [ ] Set explicit accounts and volume permissions — never rely on the zero-arg default.
- [ ] Behind a proxy, configure real-IP (
--xff-hdr,--xff-src,--rproxy); set--rproxy 0only if directly internet-facing. - [ ] Listen on a unix socket behind the proxy for tighter access control.
- [ ] For untrusted/anonymous-upload volumes, set volflag
nohtmlto neutralize XSS (serves HTML/SVG as plaintext, disables markdown rendering and JS). - [ ] Enable a version-checker feed.
- [ ] Consider password hashing.
- [ ] Sandbox with prisonparty (chroot) or bubbleparty (bubblewrap), especially for SMB.
8.2 Safety profiles
| Flag | Bundles |
|---|---|
-s |
--no-thumb, --no-mtag-ff (use mutagen), --dotpart (hide incoming uploads), --no-robots, --force-js |
-ss |
-s + --unpost 0, --no-del, --no-mv, --hardlink, --vague-403 (404 instead of 401), -nih (hide hostname) |
-sss |
-ss + --no-dav, --no-logues, --no-readme, disk logging, startup symlink scan |
Important
Disabling thumbnails/transcoding via
--no-thumbis strongly recommended when anonymous upload is enabled, because it stops copyparty from running FFmpeg/Pillow/VIPS on attacker-supplied files — a frequent CVE surface.
8.3 Gotchas and XSS vectors
- Users without read access can still see
.prologue.html/.epilogue.html/README.md/PREADME.mdcontents (intentional, for upload instructions). - Users may inject auto-running (sandboxed)
<script>by uploading aREADME.md(block with--no-readme) or renaming a file to.epilogue.html(block with--no-logues/--no-dot-ren). - Markdown documents can carry HTML/
<script>; the directory-listing embed is sandboxed but the markdown editor isn't 100 % safe. The nuclear option is thenohtmlvolflag.
8.4 Password hashing
--ah-alg argon2 hashes config/argument passwords (prints hashed versions on startup to copy back). --ah-cli enters an interactive mode that never writes plaintext to disk. Defaults take ~0.4 s and 256 MiB RAM per password. Keep --ah-salt consistent across environments (inspect with --show-ah-salt). With --usernames, hash as user:password.
8.5 CORS
By default, all requests except GET/HEAD must either omit Origin, match the server domain, or carry the PW header. Configure with --acao / --acam, or disable entirely with --allow-csrf.
8.6 Filekeys and dirkeys
- Filekeys (
fk): per-file access keys from--fk-salt+ path + size + inode.fkamakes weaker keys that survive edits (salt + path only). - Dirkeys (
dk): per-directory keys from--dk-salt+ path.dkyskips the key check (g-users see folder contents but not subfolders);dksallows entering subfolders and zip/tar.
8.7 Malicious-repo / abuse posture
copyparty bans IPs that send 403/422/malicious requests (visible as cpp_sus_reqs / cpp_active_bans in metrics). The version-checker can panic-exit on a known vulnerability (--vc-exit). For GDPR, disable or rotate logging (-lo cpp-%Y-%m%d.txt.xz) and forget uploader IPs after a window (--forget-ip 43200).
9. Operations
9.1 Running as a service
| Init system | Resource |
|---|---|
| systemd | contrib/systemd/copyparty.service (+ prisonparty.service for chroot, copyparty@.service, copyparty-user.service) |
| OpenRC (Alpine/Gentoo) | contrib/openrc/copyparty |
| FreeBSD rc | contrib/rc/copyparty |
| Podman + systemd | contrib/podman-systemd/ |
| NixOS | services.copyparty module |
copyparty understands NOTIFY_SOCKET (systemd Type=notify) and S6_NOTIFY_FD (s6/dinit ready-notification).
Open the relevant firewall ports (complete example, every feature enabled):
firewall-cmd --permanent --add-port={80,443,3921,3922,3923,3945,3990}/tcp
firewall-cmd --permanent --add-port=12000-12099/tcp # passive FTP
firewall-cmd --permanent --add-port={69,1900,3969,5353}/udp
firewall-cmd --reload
(69 tftp, 1900 ssdp, 3921 ftp, 3922 sftp, 3923 http/https, 3945 smb, 3969 tftp, 3990 ftps, 5353 mdns, 12000+ passive-ftp)
9.2 Version-checker
Subscribe to a security-advisory feed to be warned of known vulnerabilities:
[global]
vc-url: https://api.copyparty.eu/advisories # or -panic / -all
vc-age: 3 # hours between checks
vc-exit # panic-exit if current version is vulnerable
Without --vc-exit, a warning shows in the control panel for admin (a/A) users.
9.3 Logging
stdout by default (terminal / journalctl / docker logs):
[global]
q # disable stdout logging (small perf win)
lo: /var/log/cpp/%Y-%m-%d.txt # daily logfiles; %Y-%m-%d rotates by date
flo: 2 # no colors in the logfile
log-date: %Y-%m-%d # show dates on stdout too
--no-ansi removes colors everywhere; --rlo controls collision behavior when a logfile name is taken.
9.4 Configuration reload
systemctl reload copyparty (or kill -s USR1) reloads accounts and volumes from config files without dropping connections; the [reload cfg] control-panel button does the same for admin users. [global] changes still require a restart. The HTTP API equivalent is GET ?reload=cfg.
Note
The SMB hot-reload does not pick up
[global](commandline) changes.
9.5 Prometheus metrics
Enable with --stats (and usually -e2dsa); scrape /.cpr/metrics (OpenMetrics 1.0.0). Only admin accounts can read it.
scrape_configs:
- job_name: copyparty
metrics_path: /.cpr/metrics
basic_auth:
password: wark
static_configs:
- targets: ['192.168.123.1:3923']
Key metrics: cpp_uptime_seconds, cpp_active_dl, cpp_http_conns, cpp_http_reqs, cpp_sus_reqs, cpp_active_bans/cpp_total_bans, plus per-volume cpp_vol_bytes, cpp_vol_files, cpp_dupe_bytes/cpp_dupe_files, cpp_disk_size_bytes/cpp_disk_free_bytes, and queue gauges (cpp_hashing_files, cpp_tagq_files, cpp_mtpq_files). Disable subsets with --nos-hdd / --nos-vol / --nos-vst / --nos-dup / --nos-unf.
9.6 Thumbnail pregeneration
Thumbnails are on-demand by default, cached for --th-maxage (1 week). To pre-generate on startup, set th-pregen / th_pregen to a comma-list of formats (j,jf,jf3,j3,w,wf,...,opus,mp3,flac,wav). Budget: regular thumbnails ~16 KiB, triple-size ~96 KiB each. Audio transcodes are treated as thumbnails (expiry via --ac-maxage).
9.7 Event hooks and external integration
--xau/--xbu(and move/delete equivalents) run a program before/after an event; see--help-hooksandbin/hooks/.- Hooks can instead emit ZeroMQ messages:
--xau zmq:pub:tcp://*:5556(PUB),--xau t3,zmq:push:tcp://*:5557(PUSH),--xau t3,j,zmq:req:tcp://localhost:5555(REQ with JSON). - The older
mtp-based upload-event mechanism (bin/mtag/) is non-blocking, multithreaded, gives access to FFmpeg tags, and fires only on new unique files. - Handlers (
bin/handlers/) replace 403/404 behavior.
9.8 Performance tuning
- Disable HTTP/2 and HTTP/3 → up to 5× faster uploads.
-q(disable stdout logging) helps even with-lo.--histon SSD speeds listings, searches, and thumbnails.--dedupavoids writing dupes;--safe-dedup 1speeds upload verification.--no-dirsz→ ~30 % faster listings (shows inode size, not content total).--no-hash .when indexing a network disk if you only need names/tags searchable.- Larger
--iobuf/--s-rd-sz/--s-wr-sz(e.g. 1–4 MiB) help on NFS/SMB/S3. -j0enables real multiprocessing — helps with many connections and saturated 20 Gbps links, but usually reduces performance otherwise.- On musl distros,
mimalloc(LD_PRELOAD) trades 2× RAM for higher throughput. - On Windows/macOS,
--casechk=nis much faster (with the usual case-insensitive-FS caveats).
9.9 Cloud storage as a volume
There is no native S3 support. Mount cloud storage with rclone / geesefs / JuiceFS as a local folder, then point a copyparty volume at it. Tune with --iobuf / --s-rd-sz / --s-wr-sz; try the default, sparse, and nosparse volflags to find the best throughput for your stack.
10. Packaging and Distribution Internals
| Artifact | Notes |
|---|---|
sfx (copyparty-sfx.py) |
Self-extractor; unpacks an embedded tar.gz into $TEMP. copyparty-en.py is English-only/smaller. Repackable to shrink. |
| exe | copyparty.exe (win8+, bundles Pillow), copyparty32.exe (win7, Python 3.7.9 — LAN only, never internet). |
zipapp (copyparty.pyz) |
Normal zipfile, runs without unpacking to disk; fewer features, can't benefit from newer jinja2. |
| PyPI | copyparty package; console scripts copyparty, u2c, partyfuse. |
| PyInstaller | Build scripts in scripts/pyinstaller/. |
| Docker | scripts/docker/; image variants min/ac/im/iv/dj bundle increasing dependency sets. |
The version is the single source of truth in copyparty/__version__.py (VERSION = (1, 20, 16), BUILD_DT = (2026, 5, 26)), consumed by both setuptools and hatch.
Caution
Docker images and the bootable build cannot decode HEIF/HEIC or H.265/HEVC for legal reasons (
docs/bad-codecs.md) — iPhone media thumbnails will fail there. Use a native install withpyvipsfor those.
11. Code Quality and Conventions
| Aspect | Detail |
|---|---|
| Scale | ~92 classes, ~825 functions across the copyparty/ package; the largest module is httpcli.py (HttpCli with ~80 methods). |
| Formatting | black (pinned 21.12b0, targeting py27), isort (black profile). |
| Linting | ruff (line-length 120), pylint (py3.11), bandit (skips B104/B110/B112). |
| Types | mypy --strict over copyparty/. |
| Python floor | py2.7 / py3.3 still targeted — hence the deliberately conservative idioms (no f-strings in core, broad compatibility shims). |
| No build step | JS and Python are editable in place; this is a stated design constraint. |
The codebase makes heavy use of small dedicated classes per concern (e.g. Garda for ban tracking, Cooldown, FHC file-handle cache, MTHash multithreaded hashing, Pebkac/WrongPostKey exception types) in util.py. Per-protocol servers subclass their library's handler (FtpHandler(FTPHandler), SFTP_Srv(paramiko.SFTPServerInterface), etc.), keeping copyparty's VFS logic centralized in AuthSrv/VFS.
Feature kill-switches (chickenbits / beefybits)
A distinctive pattern: per-feature environment variables disable a feature when it misbehaves (chickenbits) or force-enable it on broken platforms (beefybits). Examples:
| Env var | Effect |
|---|---|
PRTY_NO_SQLITE |
Disable all DB features (indexing, dedup logic) |
PRTY_NO_MP |
Disable multiprocessing |
PRTY_NO_TLS |
Disable native HTTPS (terminate TLS at a proxy) |
PRTY_NO_FFMPEG / PRTY_NO_FFPROBE |
Disable transcoding / tag-scan via FFmpeg |
PRTY_NO_PIL / PRTY_NO_VIPS |
Disable a thumbnail engine (saves RAM/startup) |
PRTY_FORCE_MP |
Force multiprocessing on macOS and other broken platforms |
PRTY_NO_* (dependency chickenbits) |
One per optional dep — argon2, paramiko, partftpy, dcraw, magic, mutagen, psutil, pyftpd, rawpy, and Pillow sub-features (HEIF/AVIF/JXL/WEBP) |
Run with PRTY_MODSPEC=1 to trace which module copyparty loads each dependency from.
12. Troubleshooting (Operator)
Error: OperationalError('database is locked')
Cause: The up2k.db is on a network disk (SMB/NFS) or a filesystem with broken locking (VirtualBox vboxsf, mergerfs), or the share dropped briefly.
Solution: Move the DB to a local disk with --hist / hist volflag, or just the DB with --dbpath. Visit /?stack to capture live thread stacks for a bug report.
Prevention: Keep .hist/ on a reliable local SSD; set a landmark volflag to gate DB access on network volumes.
Error: Everyone keeps getting thank you for playing
Cause: copyparty bans the IP it thinks is the client; behind a proxy that's often the proxy's IP, so one bad actor bans everyone.
Solution: Configure real-IP (--xff-hdr, --xff-src, --rproxy); see §5.4 and docs/xff.md.
Error: copyparty sees HTTP though clients use HTTPS
Cause: The proxy isn't forwarding X-Forwarded-Proto: https (or an intermediate like Cloudflare terminates HTTPS early).
Solution: Ensure the proxy chain forwards the header end to end.
Error: SMB clients see only ~400 files in large folders
Cause: An impacket limitation (impacket#1433), worked around by default.
Solution: With impacket ≥0.12, disable the workaround with --smb-nwa-1 (but expect a performance hit).
Error: Thumbnails fail after sandboxing with prisonparty/bubbleparty + mimalloc
Cause: mimalloc needs /proc and /sys, which the sandbox hides.
Solution: Grant the sandbox access to /proc and /sys.
Issue: Custom FFmpeg binary names (e.g. macports ffmpeg8)
Cause: copyparty looks for ffmpeg/ffprobe in $PATH.
Solution: Set PRTY_FFMPEG_BIN and PRTY_FFPROBE_BIN to the correct name or full path.
Recovering from a Firefox WSOD during upload
Firefox 87 can crash the whole browser mid-upload. In the up2k tab's devtools (F12) you can recover the upload state:
var tabs = { ok:[], ng:[], bz:[], q:[] }; for (var a of up2k.ui.tab) tabs[a.in].push(a); tabs
13. Potential Improvements
These are observations for operators and contributors, drawn from the project's own notes and code shape:
- Multiprocessing metric accuracy — connection/request/ban counters are per-process under
-j, so aggregate dashboards under-report. A shared counter via the broker would fix this. - SMB backend maturity — the maintainer explicitly states low confidence that read-only is truly read-only and warns of possible path-traversal; it isn't fully integrated with the VFS. Treat SMB as LAN-only and sandbox it. A VFS-integrated SMB layer would close this gap.
- HTTP/2 and QUIC — copyparty speaks neither; it relies on a reverse proxy for them, and notes HTTP/1.1 is often faster anyway. Native HTTP/2 could simplify single-binary deployments.
- Bidirectional sync — intentionally unsupported; the documented path is to pair with Syncthing. Operators needing two-way sync must run a second tool.
httpcli.pysize —HttpCliis a single ~80-method class handling the entire request surface. It is load-bearing and well-tested but a natural target for decomposition if contributing.
See docs/TODO.md in the repo for the maintainer's planned features and fixes, and docs/devnotes.md for build instructions (sfx repack, build-from-scratch).
14. Quick Reference
Default ports
| Port | Protocol |
|---|---|
| 3923/tcp | HTTP + HTTPS + WebDAV |
| 3921/tcp | FTP |
| 3990/tcp | FTPS |
| 3922/tcp | SFTP |
| 3945/tcp | SMB (NAT from 445) |
| 3969/udp | TFTP (NAT from 69) |
| 5353/udp | mDNS |
| 1900/udp | SSDP |
| 12000-13000/tcp | FTP passive range (--ftp-pr) |
Essential commands
# minimal share of current folder
python copyparty-sfx.py
# indexed music server, read-only, with one read-write user
python copyparty-sfx.py -v /mnt/nas/music:/music:r -e2dsa -e2ts \
-a kevin:okgo -v /mnt/nas/inc:/inc:rw,kevin --no-robots --theme 2
# run from a config file
python copyparty-sfx.py -c /etc/copyparty/copyparty.conf
# reload config without restart
systemctl reload copyparty # or: kill -s USR1 <pid>
# see all options / volflags / accounts syntax
copyparty --help
copyparty --help-flags
copyparty --help-accounts
Key environment variables
| Variable | Purpose |
|---|---|
PRTY_CONFIG |
Path to config file (alternative to -c) |
XDG_CONFIG_HOME / APPDATA |
Where copyparty looks for default config |
NOTIFY_SOCKET / S6_NOTIFY_FD |
Service readiness notification |
PRTY_NO_* |
Feature/dependency chickenbits |
PRTY_FORCE_MP / PRTY_FORCE_MAGIC |
Beefybits (force-enable) |
PRTY_SYS_* |
Use system instead of vendored libraries |
PRTY_FFMPEG_BIN / PRTY_FFPROBE_BIN |
Custom FFmpeg/FFprobe paths |
PRTY_MODSPEC |
Debug: trace module loading |
Reporting bugs
Primary: github.com/9001/copyparty (mirrors on GitLab and Codeberg). Include command-line arguments, config, and — for freezes — the thread stacks from /?stack or journalctl.