Portalis / Documentation / v1.0.27

Portalis documentation

Everything below is written against the shipping product and shot on a running instance. Where something is not built yet, it says so rather than describing what it ought to do.

What Portalis is

Portalis is a self-hosted SSH gateway. You install it on one machine, add the servers your team works on, and from then on people reach those servers through Portalis instead of holding the credentials themselves. Every session runs through the gateway, so every session is recorded, every command can be checked against a rule, and access can be handed out and taken back in one place.

Two doors lead in, and both end up in the same audit log:

  • The browser. A full terminal, a file manager and a database browser in the web interface. Nothing to install on the laptop.
  • An ordinary SSH client. ssh you@your-gateway -p 2222 gives a menu of the servers you may reach. Your own key, your own terminal, your own tooling.

Portalis holds the credentials for the servers. The people using it never see them, and when someone leaves you switch off one account rather than rotating a password on forty machines.

The Portalis dashboard: servers online, live sessions, users with 2FA, and a quick-connect list grouped by server group.
The dashboard after signing in. Quick connect lists every server you may reach, grouped the way you grouped them.

What you need

A machine for PortalisAny 64-bit Linux host with Docker and Compose v2. A small VPS is plenty: 1 vCPU and 1 GB of RAM run it comfortably, 2 GB if you keep a lot of recordings. The installer offers to install Docker if it is missing.
Two ports8090 for the web interface and 2222 for the SSH gateway. Both can be changed at install time.
ReachabilityPortalis has to reach your servers on their SSH port. Your servers do not need to reach Portalis, and they need nothing installed on them.
A name and a certificateNot required to start, but put Portalis behind a reverse proxy with HTTPS before real people use it. Caddy, Nginx, Traefik and Cloudflare Tunnel all work.
A licenceThe Free plan covers five servers and needs no key. Pro and Enterprise come with a key you paste in once.

Portalis is not an agent. Nothing is installed on the servers you connect to. It signs in over SSH the way you would, with a key it keeps to itself.

Install it

One line on the machine that will run Portalis:

curl -fsSL https://get.portalis.sh | sudo bash

The installer does four things and tells you which it is on:

  1. Checks prerequisites. Docker and Compose v2 must be there. If they are not, it asks whether to install Docker from get.docker.com; answer n and it stops without touching anything.
  2. Prepares /opt/portalis. A compose file, a generated .env with fresh secrets, and systemd units for the update checker. Re-running the installer on an existing install keeps your .env and your data.
  3. Pulls the image and starts the containers. Portalis itself, PostgreSQL for its own data, and Redis.
  4. Waits until it answers. Then it prints the URL and the one-time bootstrap password.

Write that password down before you close the terminal. It is shown once and it only works for the first sign-in.

Choosing ports, a version or a directory

Every knob is an environment variable in front of the command:

# a different web port, and pin the version
curl -fsSL https://get.portalis.sh | PORTALIS_PORT=9000 PORTALIS_VERSION=1.0.27 sudo -E bash
VariableDefaultWhat it does
PORTALIS_PORT8090Web interface port
PORTALIS_SSH_PORT2222SSH gateway port
PORTALIS_BIND0.0.0.0Address to bind to; set 127.0.0.1 when a reverse proxy sits in front
PORTALIS_DIR/opt/portalisWhere the install lives
PORTALIS_VERSIONlatestPin a version instead of following the newest
PORTALIS_INSTANCE_NAMEthe host's nameHow this install is labelled in your account portal
PORTALIS_INSTALL_DOCKERask1 installs Docker without asking, for unattended runs

The installer writes everything it did to /var/log/portalis-install.log, with the bootstrap password masked.

Behind a reverse proxy

Bind Portalis to localhost and let the proxy hold the certificate. A Caddy example, which is the whole file:

portalis.example.com {
  reverse_proxy 127.0.0.1:8090
}

Then tell Portalis to trust that proxy, so the audit log records the visitor's address instead of the proxy's. In /opt/portalis/.env:

PORTALIS_TRUST_PROXY=1
PORTALIS_TRUSTED_PROXIES=127.0.0.1

The SSH gateway on port 2222 is a raw TCP listener, not HTTP. It cannot go through an HTTP reverse proxy — expose the port itself, or put a TCP proxy in front of it.

First login

Open the URL the installer printed and sign in as superadmin with the bootstrap password. Portalis then makes you do two things before it lets you anywhere:

  1. Set up two-factor authentication. Scan the code with any authenticator app. This is not optional: an SSH gateway with a password-only administrator is a bad trade.
  2. Replace the bootstrap password. The one from the installer stops working the moment you do.

You are also handed a set of recovery codes. Each one works once, in place of an authenticator code. Put them where you keep your other break-glass credentials.

The Portalis sign-in page, with the product introduction on the left and the sign-in card on the right.
The sign-in page. Your logo and colours replace the defaults under Settings → Branding.
The second step of signing in, asking for a two-factor or recovery code.
Password first, then the code. A recovery code is accepted in the same box.

Your licence

A fresh install runs on the Free plan: five servers, the browser terminal, the SSH gateway, recordings and the audit log. No key needed, nothing expires.

Pro and Enterprise are bought at my.portalis.sh and lift the server limit and switch on the paid modules. After paying you get a licence key by email. Paste it under Settings → License & Modules and it applies straight away — no restart.

Settings, License and Modules tab, showing the current plan, server usage and which modules are on.
The licence tab: which plan you are on, how many servers it covers, and which modules that plan unlocks.

Changing plan later needs no copy-and-paste. When you upgrade or downgrade in the portal, the key is re-signed for the same licence and your instance picks it up at its next check-in, then logs license.updated_from_portal. Nothing to do on the server.

 FreeProEnterprise
Servers5UnlimitedUnlimited
Browser terminal, SSH gateway, recordings, audit logYesYesYes
File manager, command guard, just-in-time accessYesYes
Database browserYes
Single sign-onYes

Your instance checks in with the licence server once a day. If it cannot reach it, paid features keep working for a week of grace before they fall back to Free. An instance that never reaches the internet can run air-gapped — ask us for a key issued that way.

Add a server

A server in Portalis is one machine it can sign in to. Go to Servers → Add server.

The Add a server dialog with name, host, type, group, SSH username, port and sign-in method.
Adding a server. Test the connection before saving — the save button stays off until the test passes.
NameHow the server appears in lists and, more importantly, in the audit log. Use the name your team already says out loud.
Host or IP addressWhatever Portalis itself can resolve and reach. A private address is fine and usually better.
TypeLinux (SSH) for ordinary servers. The panel types add a link to that panel's own interface; the SSH side is identical.
GroupThe server group it belongs to. Groups are how access is handed out, so this field is worth getting right — but you can move a server later.
SSH usernameThe account Portalis signs in as: root, deploy, whatever you use. Everyone who opens a session on this server lands in that account, under their own name in the log.
SSH port22 unless you moved it.
How Portalis signs inWith its own key (recommended) or with a password you store once. See below.

Press Test the connection first. A server that Portalis cannot reach would sit in the list doing nothing, so the save button stays disabled until the test comes back green.

The gateway key

Portalis generates one SSH key pair for itself at install time and presents it to every server. That key is what makes the whole thing work without storing passwords: you install the public half once per server, and Portalis never needs a credential from you again.

Copy it from Settings → Security and add it to the target account's authorized_keys:

# on the server you are adding, as the account Portalis will use
mkdir -p ~/.ssh && chmod 700 ~/.ssh
echo 'ssh-ed25519 AAAA... ezssh-gateway' >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

If installing the key by hand on forty servers is not appealing, add the server with a password once instead. Portalis signs in, installs its own key, and from then on uses the key. The password is not kept afterwards.

The gateway key does not rotate on its own and does not change when you update Portalis. Install it once per server and forget about it. It is shown in full under Settings → Security whenever you need it again.

Host keys

The first time Portalis connects to a server it records that server's host key and pins it. If the key changes later — a rebuild, a restore, someone in the middle — the connection is refused and the audit log says why. After a legitimate rebuild, clear the pin on the server's row and let the next connection pin the new one.

Server groups

A server group is a bag of servers with a name and a colour: Production, Staging, Databases, Customer: Northwind. They exist for two reasons: the lists stay readable, and access is granted per group rather than per machine.

The Server groups tab with four groups, one expanded to show its servers.
Groups → Server groups. Every server added to a group is instantly reachable by the teams that hold that group.

That last part is the point. Grant the DevOps team the Production group today, add web-03 to that group next month, and the whole team can reach it without anyone touching permissions again.

Group by what decides access, not by what a server runs. If Support may reach staging but not production, then Staging and Production are the right groups. If a customer's servers are handled by one team, a group per customer beats a group per role.

cPanel and other panels

Setting a server's type to cPanel, WHM, DirectAdmin, Plesk or CyberPanel changes two things: the row gets that panel's badge, and you can store the panel URL so people reach the control panel from the same list. Everything else — SSH, files, recording, the command guard — behaves exactly as it does for a Linux server.

Database connections

The database browser is an Enterprise module and it is worth being precise about what it connects to, because it is the one part of Portalis that does not go through your servers.

A database connection is its own thing: a MySQL or PostgreSQL login that Portalis dials directly. It does not use your server list, and it does not tunnel through SSH. The database has to be reachable from the machine Portalis runs on.

Go to Database → New connection and fill in host, port, database name, user and password. Group it the way you group servers — Reporting, Production data — because database groups are how access is granted.

The Database page with a connection selected, its tables listed on the left.
A connection, its group, and the tables it holds. The password is stored encrypted and never shown again.

Test connection works the same way as it does for servers. Editing a connection later leaves the password field blank: fill it in only when you want to change it.

If the database only listens on 127.0.0.1 on its own machine, Portalis cannot reach it. Either bind it to an address Portalis can reach and firewall it accordingly, or keep using the SSH terminal and the database's own client on the server. Tunnelling a database connection through the gateway is on the roadmap and not built yet.

How access works

Four things decide whether a person can open a session on a server. Read them in order — it is the same order Portalis uses.

LayerWhat it grantsWhere you set it
RoleSuper admin and admin reach every server. Everything below applies to ordinary users.Users
TeamA user group is granted whole server groups. Every member inherits every server in them, including the ones added later.Groups → User groups
Person, whole groupOne person can be granted a server group directly, without being in a team.Groups → User access
Person, one serverOne person can be granted a single server on top of everything else.Groups → User access

They add up: someone in two teams reaches the union of both. One rule overrides the rest — a deny on a specific server wins, whatever granted it. That is how you hand a team the whole of Production and still keep one machine out of their reach.

The recommended order when setting up is: server groups first, then servers, then teams, then people. Do it that way and every account you create is useful the moment it exists.

Add a user

Users → Add user. Username, full name, email, role, and a one-time password you either type or generate.

The Add user dialog with username, name, email, role, a generated password and team membership.
Creating a user. Teams are picked in the same form, so the account works on the first sign-in.
RoleReaches
UserOnly what their teams and personal grants give them. This is almost everybody.
AdminEvery server, plus servers, users, groups, the guard and the audit log.
Super adminEverything an admin does, plus the licence, updates and other super admins.

The password you set is one-time: the person must replace it at their first sign-in, and they have to enrol two-factor authentication before they reach anything. You can also paste their SSH public key while creating the account, which saves them a step — and after the account exists, the same dialog lists the keys on it so you can check or remove one.

Deactivating a user is usually better than deleting them: their history stays readable in the audit log, and their sessions stop immediately either way.

Teams and their servers

A user group is a team. Give the team whole server groups and everyone in it inherits them.

The DevOps team expanded, showing members, the server groups it reaches and the database groups it reaches.
Groups → User groups. Members on top, server groups in the middle, database groups underneath.
  1. Open Groups → User groups and press New user group. Name it after the team, not the access: DevOps, Support, Northwind team.
  2. Open the group and tick the people in it. The chips fill in as you go.
  3. Under Server groups this team reaches, tick the groups they need. That is the grant — there is no save button, it applies as you click.

This is the layer you want to be using for almost everything. A new server joins a group and the right people can already reach it. A new colleague joins a team and has the right access before their first day is over. Nothing to review server by server.

One server for one person

Sometimes a team grant is too much. A support engineer needs to look at one database server, once, and putting them in Database admins would hand them all of it. Portalis has a layer for exactly that.

  1. Go to Groups → User access.
  2. Find the person and open their row. Everyone is listed with how many single servers they already hold.
  3. Open the group the server sits in — the groups here are only a way to find the machine, not a grant.
  4. Tick the one server. It applies immediately; the badge on their row goes to 1 single server.
Sam Okafor's row expanded in User access, the Databases group opened, db-01 ticked and db-02 not.
Sam gets db-01 and nothing else in that group. No team membership, no group grant.

This grant sits on top of whatever their teams already give them, and it is the same panel you use to take it away again: untick, and the access is gone at once. Any session they already have open is not killed by the change — end it from Activity if that matters.

The same row also holds Select every server in a group, which grants that whole group to this one person without creating a team for them. Use it when one person genuinely is the exception; use teams when there will ever be a second person.

Single-server grants are the layer that quietly rots. They are invisible in the team view, so someone keeps a server long after the reason expired. Check User access every few months, or use just-in-time access instead, which expires on its own.

Who can reach what

At the bottom of Groups → User access is Effective access. Pick a person and it lists every server they can reach right now, and which rule put it there.

Effective access for Sam Okafor, listing five servers, each tagged via user group or via server grant.
Every server this person reaches, with the reason next to it: via user group or via server grant.

This is the answer to "why can she reach that?" and to "did we actually remove his access?". Use it after any change to access, and when an audit asks.

Database access

Database access follows the same shape as servers, with one extra choice: read-only or read and write.

  1. Put connections in database groups when you create them.
  2. Open Groups → User groups and expand a team.
  3. Under Database groups this team reaches, set each group to No access, Read-only or Read & write.

A read-only grant is enforced by the database itself: the query runs inside a read-only transaction, so anything that would write is refused by the engine rather than by a checkbox. The row editor is hidden and the read-only switch in the SQL console is locked on. A crafted request cannot get around it.

The Database groups tab listing two groups and the connections in them.
Database groups. Connections are grouped here; teams are granted the groups.

Creating, editing and deleting connections stays with administrators, whatever grants a team holds.

Switching modules off for one person

The Add and Edit user dialog has three switches that take capability away from that one account, whatever their groups say:

  • Browser terminal — off means they use their own SSH client only. Handy for people who should not have a terminal in a browser tab on a shared laptop.
  • Files — off removes the file manager.
  • Database — off removes the database browser. Only shown on Enterprise, where the module exists.

These are subtractions, not grants. Switching Files on for someone who reaches no servers still gets them nothing.

Just-in-time access

Standing access is the thing audits complain about. Just-in-time access, a Pro and Enterprise module, lets someone ask for a server for a set number of hours; an admin approves, and the grant removes itself when the clock runs out.

The just-in-time access page with tabs for active and past requests.
Requests, approvals and history in one place. Users see Active and History; admins also see the queue.

Asking: JIT Access → Request access, pick a server, choose how long, say why. The reason is not decoration — it is what the approver reads and what stays in the log.

Approving: admins see waiting requests on the dashboard and on the JIT page, and can approve, shorten or decline with a note. An approved grant shows a bar draining towards its end time, and turns amber in the last stretch. Revoke early with one click.

When the window closes the access disappears on its own. What remains is the request, the approval, the reason and the session recording it produced.

Browser terminal

A real terminal, in the browser, on any server you may reach. Open it from the Terminal page, from the SSH button on a server's row, or with Ctrl+K from anywhere.

A live terminal session on a server, with the connection bar, a session tab and a recording indicator.
A live session. The bar at the bottom shows what you are connected as, how long you have been there, and REC.

Every server you open gets its own tab, so you can work on three machines at once without three windows. The toolbar searches the output, copies all of it, downloads the transcript, clears the screen and goes full-screen. The zoom control and the light-and-dark switch are per session.

Sessions survive a hiccup: if the connection drops, an overlay offers to reconnect rather than throwing the tab away.

Everything the screen showed is recorded, keystrokes are not. Passwords typed into sudo do not appear in a recording, because the screen never showed them.

Your own SSH client

People who live in a terminal do not have to use the browser. Point your own client at the gateway:

ssh [email protected] -p 2222

The gateway asks for up to three things, in this order:

  1. Your SSH key, which must be registered on your profile.
  2. Your password, the same one as the web interface.
  3. Your two-factor code.

Then you get a menu of the servers you may reach. Pick one and you are on it, with the session recorded exactly like a browser session. Sessions end the way SSH sessions always do — exit, or Ctrl+D.

How many of those three are asked for is a policy under Settings → Security → SSH key policy:

RequiredKey, then password, then code. Three factors. This is the default and the one to keep.
OptionalA key-holder may skip the key and sign in with password and code.
OffKeys are ignored: password and code only.

Add your key yourself under Profile → SSH keys, or have an administrator paste it when they create your account.

You have two minutes to get through the prompts. If your key has a passphrase and your authenticator is on a phone in another room, run ssh-add first so the passphrase is not part of the clock.

Files

The file manager, a Pro and Enterprise module, is SFTP over the same connection as the terminal. Browse, upload, download, rename, delete, change permissions. Open it from the Files page or the Files button on a server's row.

The file manager showing a server's directory listing.
Files on a server, reached with the gateway's own credentials. The person browsing never has the server's password.

The same access rules apply: you see the servers you may reach and nothing else. Every upload and delete is written to the audit log with your name on it.

Browsing a database

With the Enterprise module and a connection you may reach, Database gives you three tabs.

The Browse tab showing rows of a customers table with paging.
Browse pages through a table fifty rows at a time. With a primary key you can edit, insert and delete rows.

Structure lists the columns, their types, nullability and keys. SQL is a console with a read-only switch.

The SQL console with a join query and its result table underneath.
The SQL console with read-only on. Results can be exported as CSV; every query is written to the audit log.

Read-only is a real read-only transaction, not a keyword check, so a write hidden inside a common table expression is refused just as plainly as an obvious one. Turn it off deliberately when you mean to change data — and if your grant is read-only, the switch stays locked on.

A table without a primary key can be read but not edited: Portalis will not update a row it cannot point at unambiguously. Use the SQL console for those.

Command guard

The command guard, a Pro and Enterprise module, watches what is typed in a session and acts on patterns you write.

The Command Guard page listing rules with their pattern, action and note.
Rules are regular expressions with an action. Changes take effect in sessions that are already open.
BlockThe command never reaches the server. The session shows [ezssh] command blocked by policy and the attempt turns up red in Activity.
WarnThe command runs, and the attempt is flagged amber in Activity for someone to look at.

Anchor patterns so they match a command and not a mention of it. ^\s*rm\s+-rf\s+/ catches the disaster; rm -rf unanchored also catches the sentence someone pasted into a comment.

The guard is a seatbelt, not a wall. Someone determined can wrap a command in a script, encode it, or spell it differently. It stops the accident and it records the attempt. It does not turn an untrusted person into a safe one — that is what access control is for.

Activity

Everything that happened, in one list: sign-ins and failed sign-ins, sessions opened and closed, files moved, queries run, guard hits, access changed, licence applied.

The Activity page listing audit events with time, user, action and detail.
Filter by user, action, server or date. Red is a failure or a block, amber is a warning.

Click a row to expand the whole event: who, when, from which address, on which server, and the detail the event carried. Export the filtered view as CSV when someone needs it in a spreadsheet.

The Sessions tab shows what is running right now, and lets an admin watch a live session read-only or end it.

Audit events are kept for a year by default and swept nightly. Change that under Settings → Authentication if your retention rules differ.

Recordings

Every session through Portalis is recorded — the browser terminal and the SSH gateway both. Recordings is where they live.

The Recordings page with tiles for count, storage used and retention, and a table of recordings.
How many sessions are on disk, how much space they take, and how long they are kept.

Play one and it replays at its original speed, in a terminal, exactly as it looked. Download gives you the raw .cast file, which asciinema opens. Filter by user, server or date when you are looking for something specific.

Recordings are terminal casts, not video: a long session is usually well under a megabyte. Storage and retention live under Settings → Storage, covered below.

Settings, tab by tab

Settings is grouped into Instance, Access and Appearance. Every tab has a ? next to its heading with the tips for that screen.

License & Modules

Your plan, the key this instance runs on, how many servers it covers and which modules are on. Applying a new key replaces the current one straight away, without a restart. Switching a module off hides it for everyone including admins; your licence is untouched and you can switch it back on any time.

Update

The Update tab showing the running version, the available version and release notes.
The version you run, the version available, and what changed in it.

Portalis checks for updates daily. When one is available this tab shows the release notes and a button to apply it. See Updating below for what that does.

Storage

The Storage tab with disk usage and retention settings for recordings.
What the recordings cost you in disk, and how long they are kept.

Disk usage shows how many recordings are on disk, what they weigh and how much room is left on the host. Retention decides how long they live: 7, 14, 30 or 90 days, a number of your own, or forever. A nightly sweep removes what is older, and Delete expired now does it on the spot after telling you how much will go.

The default is seven days. Deleting a recording leaves the session itself in Activity — only the replay file goes.

If your industry requires keeping session records for a fixed period, set the retention to match before the first nightly sweep runs. Set it to Keep forever if the rule is open-ended, and watch the disk instead.

Security

The Security tab with the gateway key, the SSH key policy and the IP allowlist.
The gateway key to install on your servers, the key policy for port 2222, and the IP allowlist.

The gateway key is here to copy. The SSH key policy decides whether a registered key is required, optional or ignored on port 2222. The IP allowlist, when switched on, refuses everything from outside the ranges you list — the web interface and the gateway both.

Add your own address to the allowlist before you switch it on. Locking yourself out means editing the database by hand on the host.

Authentication

The Authentication tab showing the built-in methods and the password policy.
Password rules, and single sign-on for those on Enterprise.

Username and password, SSH key and TOTP are part of the gateway and cannot be switched off. Below them sits the password policy — minimum length, and whether a digit, a symbol or a capital is required — which is checked whenever anyone sets a password. Existing passwords keep working until they are changed.

Single sign-on (Enterprise) connects an OpenID Connect provider: Authentik, Keycloak, Okta, Entra ID, Google Workspace. Portalis reads the provider's discovery document at <issuer>/.well-known/openid-configuration, so a typo in the issuer fails when you save rather than when a user tries to sign in. SSO covers the web interface; the SSH gateway still uses password plus code, because SSH has nowhere to put a browser redirect.

API keys

The API keys tab listing keys with their scope and last use.
Keys for scripts and integrations. The secret is shown once, when you create it.

Create a key, choose read-only or read-write, and copy the secret — it is shown once and stored hashed. Revoking a key takes effect immediately. Calls made with a key appear in the audit log under that key's name.

Branding

The Branding tab with the app name, colour, logo and a live preview of the sign-in page.
Name, colour, logo and login background, with a preview of the sign-in page as you change it.

Change the name, the accent colour, the logo and the sign-in background. Useful when the gateway carries your own company's name in front of customers. The preview updates as you type.

Your own profile

Click your name at the bottom of the sidebar. Four things live here.

The SSH keys tab of a user profile, listing a key with its fingerprint.
Your own SSH keys: the ones you present to the gateway on port 2222.
  • Password — change it; the policy above is checked as you type.
  • SSH keys — add the public keys you use for port 2222. These are not the keys Portalis uses to reach your servers; that is the gateway key and it is separate.
  • Two-factor and sessions — reconfigure your authenticator, get fresh recovery codes, and sign out everywhere at once if you left a session on a machine you no longer trust.
  • Preferences — light or dark, and whether the tips appear.

Updating

Two ways, same result.

From the interface. Settings → Update → Update now, for super admins. Portalis pulls the new image, restarts, and shows the steps as it goes. If the new version does not come back up, the previous image is restored automatically.

From the shell. Re-run the installer. It keeps your .env, your database and your recordings, pulls the new image and restarts:

curl -fsSL https://get.portalis.sh | sudo bash

Updates are ordinary Docker image swaps. Database migrations run at start-up and are logged. Skipping versions is fine — migrations from any earlier version run in order.

Take a backup before a major update. It is one command, it takes seconds, and it turns a bad afternoon into a five-minute rollback.

Backups

Three things are worth keeping, and they live next to each other:

The databaseUsers, servers, groups, grants, audit log. Everything except the recordings themselves.
/opt/portalis/.envThe secrets. Without it a restored database cannot be read: the stored server passwords are encrypted with a key that lives here.
The recordings volumeOnly if you must keep session replays. It is the big one.
# database + secrets, the two that matter
cd /opt/portalis
docker compose exec -T db pg_dump -U ezssh ezssh | gzip > ~/portalis-$(date +%F).sql.gz
cp .env ~/portalis-env-$(date +%F).bak

Restoring is the same in reverse: put back .env, load the dump into a fresh database, start the stack.

Keep .env as safe as the database itself. It holds the key that decrypts stored credentials, and a copy of both together is a copy of everything.

API and keys

Everything the interface does goes through a REST API at /api/v1, and an API key reaches the same endpoints. Create one under Settings → API keys and send it as a bearer token:

curl -H "Authorization: Bearer <your-key>" \
     https://portalis.example.com/api/v1/servers

Read-only keys are refused on anything that writes. Both kinds are logged with the key's name, so an automation's actions are as traceable as a person's.

Uninstalling

# stop and remove the containers and units, keep the data
curl -fsSL https://get.portalis.sh | sudo bash -s -- uninstall

# the same, and delete the database and the recordings too
curl -fsSL https://get.portalis.sh | sudo bash -s -- uninstall --purge

Without --purge your data stays in place and re-running the installer picks it up again. With it, everything goes. Nothing has to be removed from the servers you connected to except the gateway key line in authorized_keys, if you want it gone.

Security model

What Portalis does with your credentials, and what it expects from you.

What is stored, and how

Server credentialsEncrypted with a key in /opt/portalis/.env. A stolen database without that file is not enough to reach your servers.
User passwordsHashed with Argon2id. They are never stored or logged in a readable form, and Portalis cannot show them to anyone, including you.
The gateway keyA private key on the Portalis host. This is the crown jewel: whoever holds it can reach every server that trusts it.
RecordingsPlain files on disk under /var/lib/ezssh/recordings. They contain what the screen showed, so treat them as sensitive.
Two-factor secretsIn the database. Recovery codes are single-use and consumed when used.

What guards the doors

  • Mandatory two-factor. No account reaches anything before it enrols, on the web and on the gateway alike.
  • Three factors on port 2222 by default: registered key, password, code.
  • Per-IP backoff. Repeated failures ban the source for a while, doubling with each failure, so a password is not worth guessing at.
  • Host key pinning. A server whose key changed is refused until an administrator clears the pin.
  • Everything audited. Sign-ins, failures, sessions, file moves, queries, access changes.

What is on you

  • Put HTTPS in front of the web interface. Portalis does not terminate TLS itself.
  • Keep the host patched and its Docker socket private. Root on the host is root on the gateway key.
  • Back up .env separately from the database, and keep both away from the people whose access you are limiting.
  • Review User access now and then. The single-server grants are the ones that outlive their reason.

Troubleshooting

The installer stops on Docker

Docker or Compose v2 is missing and there is no terminal to ask in. Re-run with PORTALIS_INSTALL_DOCKER=1, or install Docker yourself and run the installer again.

A server shows Unreachable

Reachability is a TCP check every five minutes; Check now tests it immediately. Unreachable means Portalis could not open the port: wrong address, firewall, or the machine is down. Portalis has to reach the server, never the other way round.

"Permission denied (publickey,keyboard-interactive)" on port 2222

That is the key policy doing its job. Either add your public key under Profile → SSH keys, or have an administrator set the policy to Optional or Off under Settings → Security.

The gateway closes while I am typing the code

You have two minutes from connecting to finish all the prompts. Load your key into the agent with ssh-add first, and have the authenticator open. A cut-off attempt is logged as gateway.challenge_aborted, not as a wrong code.

Adding a server fails on authentication

The gateway key is not in the target account's authorized_keys, or it is on the wrong account. Copy the key from Settings → Security again, and check you are appending it for the same user you typed in SSH username. Adding the server with a password once lets Portalis install the key for you.

Someone cannot see a server you granted

Open Groups → User access → Effective access and pick them. If the server is not listed, no rule grants it. If it is listed but they still cannot connect, check whether their account has the browser terminal switched off, and whether the server is enabled.

The database page is empty

Database access uses its own connections, not your servers. An administrator adds a connection under Database → New connection, and a team is granted its group under Groups → User groups. Without both, the page has nothing to show.

Paid features stopped working

The licence check-in has not reached the licence server for over a week. Check the host's outbound connectivity to my.portalis.sh. The banner on the dashboard says when the last successful check-in was.

Reading the logs

cd /opt/portalis
docker compose logs -f portalis     # the application
docker compose logs db              # the database, incl. migrations
cat /var/log/portalis-install.log  # what the installer did

FAQ

Does anything have to be installed on the servers?

No. Portalis connects over SSH like any client. The only change on a server is one line in authorized_keys.

What happens to open sessions when I revoke access?

They keep running. Revoking stops the next connection, not the current one. End a live session from Activity → Sessions when it needs to stop now.

Can two people work on the same server at once?

Yes, each in their own session, each recorded separately.

Are keystrokes recorded?

No. Recordings hold what the screen showed. Commands are visible because the shell echoes them; a password typed into a prompt is not, because the screen never showed it.

Can I use my own SSH key to reach the servers?

Your key gets you into the gateway. From the gateway onwards Portalis uses its own key to reach the servers — that is what keeps the servers' credentials out of everyone's hands.

Does Portalis work with a bastion I already have?

It is one. Point it at the servers behind your bastion, or replace the bastion with it. What it will not do yet is chain through another jump host to reach a target.

Is there a limit on users?

No. Plans count servers, not seats. Adding the whole team costs nothing extra.

What happens if the Portalis host dies?

Nobody can reach the servers through it until it is back, which is the trade a gateway makes. Restore from your backup onto a new host, or keep a break-glass account on the critical servers for exactly that day.

Can I run it air-gapped?

Yes. It needs an air-gapped licence key so it does not expect to reach the licence server, and you pull image updates yourself. Ask us for one.

Where do I report a problem?

Through your account at my.portalis.sh, or the contact form on portalis.sh. For anything security-related, say so in the subject and we will handle it privately.