Skip to content

Powered by Grav

Extra SSH/SFTP/FTPS accounts per client

Extra SSH/SFTP/FTPS accounts per client

By default an Octopus instance has one tenant account pair (oN / oN.ftp). BOA can provision one extra subaccount per Ægir Client — a separate SSH/SFTP/FTPS account scoped to only that client's sites. The Unix-account side is driven by manage_ltd_users.sh (its _manage_sec secondary-user pass, iterating the main tenant's clients/ directories); the Ægir-side trigger is site ownership — a site node whose Client field points at the Client. Provision maintains the clients/<client>/<uri> symlinks the Unix side consumes.

The granularity is per Client, not per individual developer: each Ægir Client maps to exactly one shell subaccount, shared by everyone assigned to that Client. To give two people independent shell credentials, give each their own Ægir Client (see the accountability discussion below).

The Client + subaccount model

TXT
Octopus instance o1
   └── main account: o1 + o1.ftp           ← operator + primary tenant
   └── Aegir Client "AcmeCorp"
   │      ├── subaccount: o1.acme            ← one shell account for this Client
   │      └── sites: acme-prod.example.com
   │                acme-staging.example.com
   └── Aegir Client "WidgetCo"
          ├── subaccount: o1.widget
          └── sites: widget.example.com

Each subaccount has its own SSH key and password, its own lshell config, sees only sites belonging to its parent Client, and cannot touch other Clients' sites within the same Octopus.

Provisioning lifecycle

A subaccount is requested by making the Client the owner of at least one site — the Client field on the site node, not the Client's Users list. On the site's next Verify/Install, Provision creates the ~/clients/<client>/<uri> symlink in the main tenant account (_provision_client_create_symlink); within a few minutes the cron-driven manage_ltd_users.sh (the */3 root crontab entry) finds a client directory with at least one resolving site symlink and creates the Unix account. A Client owning no surviving sites gets no account — _manage_sec rmdir's emptied client directories, after which _kill_zombies removes the orphaned account.

The subaccount username format is <octopus>.<client> — e.g. o1.acme — where <client> is the Client's directory name reduced to lowercase alphanumerics. It carries no .ftp suffix (unlike the main oN.ftp tenant) and no per-developer segment.

What the subaccount can do

Same as the main oN.ftp account (lshell-restricted), but scoped to the sites of one Client:

  • cd into the Drupal codebase directories belonging to that Client's sites.
  • Run vdrush / composer against those sites only.
  • SSH key + SFTP file upload for those site directories.

It cannot see other Clients' sites, touch the host system, or bypass lshell.

Per-subaccount SSH keys

The subaccount's ~/.ssh/authorized_keys is at the standard path (home is /home/<oN>.<client>, mirroring the username):

TXT
/home/<oN>.<client>/.ssh/authorized_keys

The owner (or a manager with shell access) populates it via SFTP or by editing directly.

Password rotation

Subaccounts carry the same 90-day password expiry as the main oN.ftp account (passwd -w 7 -x 90 at creation). Nothing is emailed anywhere: the generated password is written once to /home/<oN>.ftp/users/<oN>.<client> in the main tenant account, and forwarding it to the developer is the tenant's job. Rotation is file-driven — deleting that marker file makes _kill_zombies remove the account on the next */3 run and _manage_sec recreate it with a fresh password, written back to the same file. The rebuild removes the subaccount's home, including its ~/.ssh/authorized_keys.

Removal lifecycle

Removing the user from the Client's Users list, or otherwise breaking the subaccount's state, is what triggers deletion — there is no dedicated removal Task. manage_ltd_users.sh's _kill_zombies pass (run from the */3 cron) detects a subaccount whose state has diverged and deletes it. State is considered diverged when any of these is true: the ~/sites symlink is missing, its target no longer exists, or the /home/<oN>.ftp/users/<oN>.<client> marker file is gone.

Deletion is immediate on that detection — there is no grace period and nothing configurable about the timing. The account is removed with deluser --remove-home, and the deleted home is written once as a backup tarball under /var/backups/zombie/deleted/<timestamp>/. That tarball is an archival copy for recovery, not a retained or reactivatable home — the live account and its home are gone at that point. A second sweep catches orphaned home directories with no matching passwd entry and moves them to the same backup location as .leftover-<name>.

Why per-Client subaccounts vs. a shared oN.ftp

Sharing the main oN.ftp account across customers carries operational risks that per-Client subaccounts remove:

  • Cross-Client snoopingoN.ftp can see every site on the instance, whereas a Client subaccount sees only its own Client's sites.
  • Blast radius on disable — rotating or disabling oN.ftp affects everyone; a Client subaccount can be retired independently.

This is BOA's preferred pattern for the agency model (one Octopus, one Ægir Client per customer, one shell subaccount per Client).

Because the shell account is per Client, not per person, individual-developer accountability is achieved by mapping one Ægir Client per individual — each person then gets their own oN.<client> account, key and log identity. Putting several developers under a single Client gives them a shared subaccount, so the log shows oN.<client> did X, not which person — the same accountability gap as sharing oN.ftp, just narrowed to one Client's sites.

© 2026 BOA Documentation. All rights reserved.