Skip to content

Importing users and migrating from other servers ​

The importusers command creates user accounts in bulk from a file, along with a Disk VFS for every home directory or virtual folder that arrives as a path. It reads four formats:

--formatWhat it reads
nativeSyncplify's own CSV, in the classic five column layout or the named layout described below
cerberusThe CSV export of Cerberus FTP Server
sftpgoThe JSON backup of SFTPGo
filezillaA FileZilla Server configuration, of either generation (0.9.x and 1.x)

Nothing has to be converted first: point the command at the file the other product wrote. Every format carries password hashes across as they are, so users keep the passwords they have and nobody ever handles a password in clear text.

Running the command ​

importusers is a verb of ss-webrest, like the commands on the command line reference. It opens the configuration database directly, so run it on a node of the installation, as root through sudo on Linux or from an elevated command prompt on Windows.

ss-webrest importusers --file users.csv --vsite <virtual site ID>
FlagMeaning
--fileThe file to import. Required. --csv is the old name of this flag; it still works but is deprecated.
--vsiteThe ID of the virtual site to import into, not its friendly name. Required, except with --verify. The ID is shown on each card of the SuperAdmin Virtual sites page.
--formatOne of native (the default), cerberus, sftpgo or filezilla.
--dryrunRun every check and write nothing.
--makevfsNative format only: the classic vfs column holds a path to create a Disk VFS for, rather than the ID of an existing VFS.
--cerberus-roundsCerberus format only: the PBKDF2 iteration count of the Cerberus installation. Default 5000. See below.
--verify, --verifypassCerberus and FileZilla formats only: check that one known password reproduces its stored hash, and import nothing.

What the run reports ​

Each account is reported by name as it is processed: imported, skipped because an account of that name already exists in the virtual site, or failed with the reason. Existing accounts are never modified. A summary line follows with the three counts, and the exit code is 0 only when nothing failed, so a script can tell how it went without reading the output.

Some accounts arrive with a warning printed against them. A warning means the account was created but something the source held could not be brought across in full: a setting with no equivalent here, a credential that is not carried, a limit whose unit the file does not state. Warned accounts are counted separately in the summary and are the ones to review afterwards.

A dry run performs the same checks as a real import, including the ones that need the database, such as whether an account or a referenced VFS exists, and reports what would happen.

What every format has in common ​

  • Only users authenticated by Syncplify Server! itself are created, never LDAP or SSO accounts.
  • A home directory or virtual folder that arrives as a path becomes a new Disk VFS named after the user. A VFS created this way is not encrypted at rest, and at rest encryption cannot be turned on afterwards: create such VFSs by hand first and reference them by ID.
  • An account must end up with a way to sign in: a password, or an SSH public key with PKI among its authentication types. One that would have neither is refused by name, on a dry run too.
  • An allow list entry that cannot be translated exactly fails the account. An allow list that arrived incomplete would not be a restriction, it would be the absence of one. Trailing octet wildcards such as 192.168.1.* are converted to CIDR notation.
  • Paths are judged by the platform the server runs on. A Windows path on a Linux node, or a POSIX path on a Windows node, is refused with a message saying so; remap those in the file first.
  • An account that fails after its VFSs were created has those VFSs removed again, so a corrected file can be run again without leaving orphans behind.

What happens to an imported password hash ​

An imported hash keeps the parameters the other product chose, because that is the only way it can verify, and those are almost always weaker than the PBKDF2 work factors Syncplify Server! uses. So the first time an imported user signs in successfully, over any protocol, the password is rehashed with the storage mode configured for the virtual site and the imported hash is discarded. Nothing has to be done for this to happen, and the user notices nothing.

The native CSV format ​

The native format has two layouts, and the command works out which one a file uses from its first row: a row that names columns selects the named layout, anything else is read as the classic layout.

The classic five column layout ​

Five positional columns and no header row: username, password, VFS, subsystems, permissions.

csv
oneuser,"some password",2iSpTdEgRGPuYh0MVx2uWr5zxR4,"ssh2_scp,ssh2_sftp,ftps,ftpes,https","dirList,fileGet"

The third column is the ID of an existing VFS, or, when --makevfs is given, an absolute path a Disk VFS is created for:

csv
oneuser,"some password","/home/oneuser","ssh2_scp,ssh2_sftp,ftps,ftpes,https","dirList,fileGet"

Usernames are lowercase with no spaces. Quote any field that contains a comma or a space; a password is taken exactly as written, spaces included. The last two columns are comma delimited lists of the values in the vocabulary below.

The named layout ​

Add a first row naming the columns, then supply any subset of them in any order. Column names are matched ignoring case, spaces, underscores and hyphens, and a column this version does not know is ignored rather than shifting the ones after it.

ColumnWhat it holds
usernameThe account name. Required. user, userid and login are accepted spellings.
passwordA password in clear text.
passwordhashA password hash from another system, instead of password. Never both.
mustchangepasswordtrue to require a new password at the first sign in.
emailThe email address.
descriptionFree text.
statusEnabled or Disabled. A disabled column holding true or false works too.
subsystemsComma delimited subsystems.
authtypesComma delimited: Password, PKI, Keyboard-Interactive. Defaults to Password.
allowlistComma delimited IP addresses and networks in CIDR notation.
publickey1, publickey2, and so onOne SSH public key per column, in OpenSSH, RFC 4716 or PuTTY form.
publickeyfileA file of SSH public keys, one per line, as in authorized_keys. A relative path is resolved next to the CSV.
homepathAn absolute path; a Disk VFS is created for it and becomes the home.
homevfsThe ID of an existing VFS to use as the home.
vfs, permissionsThe home the classic way: an ID, or a path under --makevfs. permissions also applies to homepath and homevfs.
vfoldername1, vfolderpath1 or vfoldervfs1, vfolderperms1A virtual folder besides the home, mounted under that name. Number them upward.
csv
username,password,email,status,subsystems,authtypes,allowlist,homepath,permissions
alice,"some password",alice@example.com,Enabled,"ssh2_sftp,https","Password,PKI",10.0.0.0/8,/home/alice,"dirList,fileGet,filePut,fileModify,fileEditMetadata"
bob,"another password",bob@example.com,Disabled,"ftps,ftpes",Password,192.168.1.0/24,/home/bob,"dirList,fileGet"

homepath always means a path and homevfs always means a VFS ID, so neither needs --makevfs. A path may contain the {{username}} variable, exactly as in a parametric VFS; it is resolved when the user signs in.

Include PKI in authtypes for users who sign in with a key; otherwise the keys are stored but the account still expects a password.

Password hashes in the passwordhash column ​

The column takes a hash where a password is not known, written on one line with the salt and the hash in hexadecimal:

text
sha256$<salt-hex>$<hash-hex>
pbkdf2-sha256$<rounds>$<salt-hex>$<hash-hex>

sha1, sha256 and sha512 are single pass digests over the salt followed by the password. pbkdf2-sha256 and pbkdf2-sha512 take the iteration count as the second field, so the file records everything needed to verify the hash and imports the same way in a year. For a product that concatenates the salt onto the password before running PBKDF2, instead of passing it as the salt parameter, use pbkdf2-sha256-prepend or pbkdf2-sha512-prepend.

Subsystems and permissions ​

Subsystems: ssh2_shell, ssh2_command, ssh2_scp, ssh2_sftp, ftp, ftps, ftpes, https, https_sharing.

Permissions: dirList, dirMake, dirRename, dirDelete, dirEditMetadata, fileGet, filePut, fileModify, fileRename, fileDelete, fileEditMetadata, symlink.

These are the values the user editor sets. An unknown value fails the row.

Cerberus FTP Server ​

Export the users from the Cerberus User Manager and import the CSV directly:

ss-webrest importusers --format cerberus --file cerberus-users.csv --vsite <virtual site ID> --cerberus-rounds 5000

The export is read by column name, so an export from any Cerberus release imports, whatever order its columns come in.

Verify the iteration count first ​

Cerberus has stored passwords as PBKDF2 since their version 7.0, and the iteration count is not in the export: it is a server wide setting in their settings.xml. The default is 5000, and that is what the command assumes. If the count was ever raised, the wrong assumption is not detected at import time: every account imports, and every user is then rejected with the correct password.

Before importing, create a throwaway account in Cerberus with a password you choose, export, and verify it:

ss-webrest importusers --format cerberus --file cerberus-users.csv --verify migrationtest --verifypass "the password you chose"

This reads the file, recomputes the hash and reports whether the assumed parameters reproduce it. It opens no database and needs no virtual site, so it cannot import anything. When the parameters do not match, it sweeps the plausible iteration counts and reports the one that does, together with the flag value to import with. Older exports whose passwords are salted SHA1, SHA256 or SHA512 digests are read as well; no iteration count is involved, and --verify says which kind the file holds.

WARNING

Do not skip this step. A wrong iteration count is discovered when users cannot sign in, not before.

What comes across ​

CerberusSyncplify Server!
Username, email, description, disabled, password change requiredThe same
Allowed IP addressesThe allow list, with trailing octet wildcards converted to networks
Protocol FTP, FTPS, SFTP, HTTP or HTTPSftp; ftps and ftpes; ssh2_sftp; https. SSH shell, command and SCP are never granted
Virtual directories (DirName, DirPath, DirPermissions)The first is the home, the others are virtual folders. %USER% in a path becomes {{username}}
Upload bitfilePut, fileModify and fileEditMetadata together, because resuming, appending and time stamping are part of an upload in practice
Share download or share upload bitsThe https_sharing subsystem
Show hidden files, zip and unzip bitsDropped, no equivalent
Auth SSH methodPassword, public key, or both, with "password and public key" imported as requiring both

Cerberus exports the path of a user's public key file on the Cerberus machine, never the key itself, so an account signing in with a key arrives without it. The command names those accounts at the end of the run; add their keys afterwards. Groups are not in the export and are not read.

SFTPGo ​

SFTPGo keeps its accounts in a data provider and exports them all as one JSON document, from the Maintenance section of its WebAdmin or through the dumpdata endpoint of its REST API. Import that document:

ss-webrest importusers --format sftpgo --file sftpgo-backup.json --vsite <virtual site ID>

No verification pass exists for this format, because none is needed: SFTPGo writes every parameter of a hash inside the hash string. Their bcrypt, PBKDF2 and Argon2id hashes all come across. The Unix crypt and bare digest formats SFTPGo can hold but never writes itself are refused by name.

What comes across ​

SFTPGoSyncplify Server!
Username, email, description, status, require password changeThe same
Public keysSSH public keys, with PKI among the authentication types
Denied login methods, denied protocolsWhatever is not denied is granted: SSH becomes ssh2_sftp only, FTP becomes ftp, ftps and ftpes, HTTP becomes https and https_sharing. WebDAV has no equivalent
Allowed IP addressesThe allow list
Expiration dateAutomatic disabling on that date, when it is still in the future
Home directory and virtual folders on local storageThe home and virtual folders, as new Disk VFSs
Permissions per pathFolder permissions, and permission overrides for paths inside a folder. chmod and chown have no equivalent

What fails ​

An account whose home or virtual folder is not on local storage fails to import, by name and with the reason:

  • Cloud and remote storage (S3, Google Cloud Storage, Azure Blob, SFTP, HTTP): the credentials are encrypted with SFTPGo's own key and are not in the dump in any usable form. Create the equivalent VFS here first, then import that account through a native CSV that references the VFS by ID.
  • Their encrypted filesystem: the data on disk is ciphertext under a key held by SFTPGo. Decrypt it there before migrating.

Groups are not read. A setting an account inherits from a group is not on the account in the dump, so those accounts arrive with defaults and are worth reviewing.

FileZilla Server ​

Both configuration generations are read, and the command tells them apart by itself.

FileZilla ServerPoint --file at
1.0 and laterusers.xml in the configuration directory. A groups.xml beside it is read too.
0.9.xFileZilla Server.xml in the installation folder, which holds everything.

A FileZilla Server 1.x Windows service normally keeps its configuration under C:\Windows\System32\config\systemprofile\AppData\Local\filezilla-server, or wherever its --config-dir option says. FileZilla Server 0.9.x kept its single XML in the installation folder, typically C:\Program Files (x86)\FileZilla Server.

ss-webrest importusers --format filezilla --file users.xml --vsite <virtual site ID>

NOTE

The older format imports more faithfully. FileZilla 1.0 replaced ten per directory permission flags with four access modes, so a 0.9.x configuration says more about what each account may do. If both exist, import the 0.9.x one.

Passwords ​

SchemeWritten byComes across
PBKDF2 with HMAC SHA2561.0 and laterYes
Salted SHA digest0.9.55 and laterYes
Unsalted MD50.9.54 and earlierNo

Syncplify Server! implements no MD5 anywhere in its password handling. An account whose password is still an MD5 hash imports without a password, is named in the report, and is then refused unless it has another way to sign in. FileZilla Server 1.x rewrites an MD5 hash as PBKDF2 the first time its owner signs in, so have those users sign in there once, export again, and their passwords come across intact.

A single 1.x users.xml may hold all three schemes at once, because FileZilla still verifies the old ones and rewrites them on sign in. That is the same arrangement described above, so a mixed file transfers as it is.

The legacy digests do not state which end of the password the salt goes on, and --verify settles it with one disposable account, exactly as for Cerberus:

ss-webrest importusers --format filezilla --file users.xml --verify migrationtest --verifypass "the password you chose"

What comes across ​

  • FileZilla Server serves FTP only, so every account arrives with ftp, ftps and ftpes, never with ssh2_sftp. A 0.9.x account marked ForceSsl loses plain ftp, as it had it there. If the configuration came from FileZilla Pro Enterprise Server and its accounts used SFTP, add that subsystem afterwards; the command says so once at the start of the run.
  • 1.x mount points become the home (the one at /) and virtual folders. Read only, read write and write only access modes map to the matching permissions; creating, deleting and renaming directories is granted only where the mount point allowed structure modification.
  • 0.9.x directories become the home (the one flagged as such) and virtual folders named after their aliases. A directory with no alias was unreachable in FileZilla too and is not imported. An account's own settings win over its group's, which fills in only what the account lacks, as FileZilla itself resolved them.
  • Allowed IP addresses become the allow list.
  • A 1.x account without the enabled attribute is imported disabled and told so. An import may leave an account switched off by mistake, never switch one on.
  • Speed limits, session limits and open file limits are reported and not imported: the file does not state their unit, and a limit imported off by a factor of 1024 either throttles an account to uselessness or lifts the restriction, both silently. Set them on the account here if they still apply.
  • 1.x group membership is not inherited. FileZilla does not document the element, and an unread setting is reported per account rather than guessed at.

What fails ​

  • A disabled mount point. It is an explicit denial, usually of a subdirectory whose parent another mount point exposes. Skipping it would leave that path reachable.
  • A per account deny list. There is a per user allow list and no per user deny list here, so the restriction would be lost. Ban those addresses on the virtual site's Shield, remove the entry in FileZilla, or create that account by hand.
  • No mount point at / (1.x), or no directory flagged as home (0.9.x). FileZilla synthesizes a root listing from the other mount names; there is no equivalent here to a home that is not a directory.
  • An address that cannot be translated, as in every format.

Before version 8.1.1 ​

Syncplify Server! 6.2.50 through 8.1.0 ship the original command: the file flag is --csv, there is no --format, only the classic five column layout is read, and only password authentication comes across. --vsite, --makevfs and --dryrun work as described. The feature does not exist before 6.2.50.