SFTP access
Each site can have an SFTP server of its own, so the people who work on it can reach its files
with any SFTP client — FileZilla, WinSCP, Cyberduck, VS Code, sftp, rsync — without an
account on the server and without seeing anything but what they’re given.
The server is SFTP (file transfer over SSH), not FTP or FTPS: one TCP port per site, no passive port range to open, password and/or public key logins, and support in every client.
Each site’s server runs in a container of its own, sirio-sftp-<site id>, built from Sirio’s own
binary: no third-party image, and no change to the host’s SSH. Its accounts are the site’s users,
not Sirio’s: they never log in to the panel, and the panel never shows their passwords.
Turning it on
Section titled “Turning it on”SFTP is off by default, since it opens ports to the outside. In the systemd unit:
| Variable | Default | Description |
|---|---|---|
SIRIO_SFTP_ENABLED |
false |
per-site SFTP servers |
SIRIO_SFTP_PORTS |
2222-2299 |
the host ports the servers may take, one per site |
SIRIO_SFTP_BIND |
0.0.0.0 |
the host address their ports are published on (:: for IPv6 too) |
The range may not contain Sirio’s own port (SIRIO_LISTEN), nor 80, 443 and the panel’s port when
the proxy publishes them: Sirio refuses to start otherwise. Whoever manages a site’s SFTP picks a
port within the range; the range is the administrator’s choice.
Docker publishes the ports past ufw and firewalld, so they’re reachable as soon as a server runs.
The Ports page lists them like every other published port. Bind
them to a single address with SIRIO_SFTP_BIND if they must not face every interface.
Sirio’s binary must be a static Linux build, as the releases are: the servers’ image holds that
binary alone. A build with cgo (make build on a Linux machine with a C compiler) is refused with
an explanation; build it with CGO_ENABLED=0.
A site’s server
Section titled “A site’s server”In the site’s page, SFTP access → Turn on creates the server: it takes the first free port of the range (or the one you set in Server settings) and a new ed25519 host key. The card then shows how to connect:
sftp -P 2222 USER@your-serverand the host key’s fingerprint. On their first connection, clients show it and ask whether to trust it: it must match the one in the panel. The key belongs to the site’s server and stays the same across restarts and updates; turning the server off keeps it too.
Turn off removes the server’s container. Accounts and their access are kept, ready for when it’s turned on again.
The status next to the title says whether the server is running, or why it isn’t — typically a port something else on the host already uses. Sirio tries again every minute, and after every change.
Accounts
Section titled “Accounts”An account has a username, a password, public keys (ed25519, ECDSA, RSA from 2048 bits) or both, and the mounts it’s granted, each one read and write or read only. An account always keeps at least a password or a key. A disabled account can’t log in, and keeps its settings.
Every change takes effect right away: the server reloads its configuration, and closes the open sessions of the accounts that changed (a removed key, a revoked mount), leaving the others alone.
What can be exposed
Section titled “What can be exposed”What an account can be granted are the site’s mounts: the Docker volumes and host directories its containers mount. The card lists them. A mount is exposed only if an account is granted it: nothing else is mounted into the server’s container.
Some mounts are never exposed, whichever container mounts them, and the card says why:
- System directories of the host, and any directory containing one:
/,/etc,/usr,/root,/boot,/proc,/sys,/dev,/run,/tmp,/var/lib,/var/log,/var/spooland the like. Writing there is a way to run code as root. - The Docker socket, Docker’s own data directory, and Sirio’s data directory.
- Home directories, and the hidden files and directories inside them (
.ssh,.bashrc,.config…). A folder inside a home, such as/home/marco/www, is ordinary data. - A database’s data: the volumes and directories of a recognized database container (MySQL, MariaDB, Percona, PostgreSQL and their variants), and any directory containing them or inside them. Editing a running database’s files corrupts it; the backups are the way to its content.
- What another site also mounts, or a container that belongs to no site: the same volume, or a host directory overlapping one of theirs either way round.
- A directory inside another exposed one: the outer one already reaches it.
- Single files (a bind of
wp-config.php): only directories can be exposed. - Volumes of a driver other than
local. - Paths Sirio can’t follow to where they really lead. Sirio’s systemd unit hides
/homefrom it (ProtectHome=true), so host directories under/homeshow up this way; move the data elsewhere (/srv,/var/www) to expose it.
Host paths are checked as written and after following their symlinks, and the server’s container is created with where they really lead: a symlink can’t point a checked directory somewhere else. All of this is decided again from what the containers mount now, on every change and every minute. A deploy that stops mounting a directory, or a container of another site that starts mounting it, takes it out of the server; an account’s grant stays saved, and works again if the mount comes back.
Who owns the uploaded files
Section titled “Who owns the uploaded files”What users upload must belong to the user the application runs as, or the application can’t read
or change it. By default a new file or directory takes the owner of the directory it’s created
in: in a WordPress volume, where everything belongs to www-data, uploads belong to www-data
too. Owner sets a fixed UID and GID for a mount instead. Ownership and the setuid, setgid and
sticky bits can’t be changed from a client.
How the server is confined
Section titled “How the server is confined”- The container keeps only the
CHOWN,DAC_OVERRIDEandFOWNERcapabilities (to hand files to their owner and reach those of any owner inside the mounts), runs withno-new-privileges, a read-only root filesystem and bounded processes and memory, on a network of its own where containers can’t reach each other. It mounts its configuration read-only and the mounts granted; a mount granted read-only to everyone is read-only for Docker too. - The server only speaks the SFTP subsystem: there is no shell, command execution or port forwarding to refuse. Symlinks and hard links can’t be created, renames can’t cross mounts.
- Each mount is opened as an
os.Root: neither..nor a symlink planted in a volume leads out of it. - Password failures turn an IP away for 15 minutes after 10 attempts; connections are capped overall and per IP, and idle sessions are closed after 15 minutes.
- The client’s IP is what the container sees. With Docker’s usual iptables port publishing it’s
the real one; with the userland proxy (
"userland-proxy": trueand IPv6 clients reaching an IPv4-only container, or Docker Desktop) every client looks like Docker’s gateway, and the per-IP limits apply to everyone at once.
Logins, uploads, downloads, removals and renames are logged as JSON lines in the container’s log
(docker logs sirio-sftp-<site id>, or the container’s page). Changes to servers, accounts, keys
and owners go to the activity log.
Updates
Section titled “Updates”The servers’ image is built from Sirio’s binary and named after its hash: after an update of Sirio, its servers are recreated on the new one and open sessions are interrupted. Old images are removed.
Permissions
Section titled “Permissions”The site area SFTP lets a user see the site’s server, mounts and accounts; its action Edit turns the server on and off and manages accounts, their credentials and access, and owners; Delete removes accounts. As for every area, the global Sites permission covers every site.