Troubleshooting
Symptoms first. Find yours, apply the fix, and if none of it helps, Still stuck tells you what to send us.
Start here: is the gateway actually up?
Nearly every problem below is really this one. Ask the gateway directly:
curl -s http://127.0.0.1:7376/health
A healthy gateway answers with its status, version, and object counts:
{
"status": "ok",
"service": "permaura",
"version": "1.0.0",
"agents": 2,
"capabilities": 14,
"grants": 3,
"audit_chain_intact": true
}
- No answer at all → the gateway isn't running, or something else has its port. See Port conflicts.
audit_chain_intact: false→ the audit log has been altered or truncated on disk. That's exactly what the hash chain exists to detect. Get in touch rather than carrying on.
Pairing and installation
The pairing code expired
Codes are short-lived and single-use, and the connect dialog shows a live countdown. When one lapses the dialog mints a fresh one on its own. Reopen Settings → Gateway, copy the new command, and run that. An expired code is simply dead; nothing is left half-configured.
"Command not found" after the curl install
The one-line installer covers macOS and Linux. On Windows it stops and tells you to use the npm launcher instead. In PowerShell:
$env:PERMAURA_PAIR_CODE = "PGW-XXXXXXXX"; npx -y permaura start
In Command Prompt (cmd):
set "PERMAURA_PAIR_CODE=PGW-XXXXXXXX" && npx -y permaura start
On macOS or Linux the same launcher is PERMAURA_PAIR_CODE=PGW-XXXXXXXX npx -y permaura start. It runs on Intel and Apple silicon alike and fetches the right binary for your machine. The code goes in the environment rather than after --pair, so it never sits in the process list.
Port conflicts
The gateway binds 127.0.0.1:7376 by default. 7376 is "PERM" on a phone keypad, and it was picked
because nothing else conventionally claims it: it is unassigned by IANA, it sits outside the ephemeral
ranges the OS hands out to outbound connections, and it is nowhere near the ports development tooling
reaches for by habit. Conflicts should be rare. When one happens, this is how to see it.
Find out what is on the port
On macOS and Linux:
lsof -nP -iTCP:7376 -sTCP:LISTEN
On Windows:
Get-NetTCPConnection -LocalPort 7376 -State Listen | Select-Object OwningProcess
If the answer is another Permaura gateway, permaura status will say so, and permaura stop --force
ends it. The gateway diagnoses that case itself and tells you which of the two it is, so you should
rarely need the commands above for it.
The part that surprises people: a shared port still looks broken
A second program on the port does not necessarily stop the gateway binding, and that is the confusing case worth knowing about.
If the other program listens on the wildcard address (shown as *:7376 by lsof, which is what a
Node or Bun dev server does by default), the gateway can still bind 127.0.0.1:7376 beside it. Both
sockets are live at once. Which one you reach then depends on how you spell the address:
| You ask for | You reach |
|---|---|
http://127.0.0.1:7376 | the specific listener, usually the gateway |
http://localhost:7376 | whichever the OS resolves first, often the wildcard listener |
http://[::1]:7376 | the wildcard listener |
So curl 127.0.0.1:7376/health can answer correctly while an agent pointed at localhost:7376 talks
to something else entirely, and nothing reports an error. If a health check passes but a client cannot
connect, compare the two spellings:
curl -s http://127.0.0.1:7376/health
curl -s http://localhost:7376/health
Two different answers means two different servers. Free the port and restart the gateway.
Treat a listener you don't recognize as a security problem as well as a reachability one. On macOS,
which resolves localhost to ::1 first, whatever holds [::1]:7376 (a wildcard listener included)
receives what a client sends to localhost:7376. That includes a managed tunnel (a permaura.app
address) set up before the change below, which dials the gateway as http://localhost:7376: a
program there receives your tunneled requests, including the short-lived operator token permaura.com signs for
each console request and any credential typed into the console, and can pass them on so that nothing looks
broken. Find out what it is with the lsof command above before you free the port.
A managed tunnel now dials http://127.0.0.1:7376 (or your gateway's port). From gateway 1.3.48 and
desktop 1.4.21, that address is written into the tunnel's configuration on
your machine by the gateway or the desktop app, rather than set by permaura.com. One set up earlier may
still dial localhost until remote access is turned on again, so if yours is older, switch it off and
on once. While the gateway is stopped, the tunnel reaches whatever else holds 127.0.0.1:7376, so the
same caution applies to a listener there.
Move the gateway to another port
Installing:
curl -fsSL https://permaura.com/install | PERMAURA_PAIR_CODE=PGW-XXXXXXXX sh -s -- --port 7377
Running the binary or the npm launcher directly, set the address instead:
PERMAURA_ADDR=127.0.0.1:7377 npx -y permaura start
Then tell the console where to look: Settings → Gateway → Advanced, and set the browser URL to
http://127.0.0.1:7377.
If you run the gateway through the desktop app, do all of this in one place instead: Settings → Gateway → Gateway port. It moves the gateway, checks it came up on the new port, and tells your workspaces where it went. If it cannot start there it puts the old port back and says why.
If you moved the gateway some other way and the console is still calling the old address:
permaura advertise http://127.0.0.1:7377
A managed tunnel moves with the gateway on its own machine. From gateway 1.3.48 and desktop
1.4.21, the gateway, or the desktop app when it runs the gateway, rewrites the
tunnel's configuration with the new port and restarts the tunnel once the gateway answers there, so
permaura.com has nothing to move and permaura advertise changes only the address the console calls.
A tunnel you run yourself is not touched: repoint it to the new port by hand, and until you do the
console flags the stale port on the gateway page.
On a VM, container or remote host
The same conflict can come from outside the machine you are looking at. A port forward claims the port
on the host, so -p 7376:7376 on a container, or hostfwd=tcp::7376-:7376 on a QEMU VM, makes the
host's 7376 unavailable to anything else, including a gateway you meant to run on the host itself.
If you run gateways in both places, give the guest its own port on both sides and tell the console which one to call. Three things have to line up, and only the last one is easy to forget:
-
Bind a port inside the guest. It must listen on all interfaces, or the forward has nothing to reach:
curl -fsSL https://permaura.com/install | PERMAURA_PAIR_CODE=PGW-XXXXXXXX sh -s -- --host 0.0.0.0 --port 7377 -
Forward that port from the host. For QEMU,
hostfwd=tcp::7377-:7377; for Docker,-p 7377:7377. Pick a host port nothing else wants: the host's own gateway is on 7376. -
Advertise the address the console should call. This is the step the installer cannot work out for you, because it runs inside the guest and the address that matters is the host's:
permaura advertise http://127.0.0.1:7377From gateway 1.3.46,
permaura advertisefirst checks that whatever answers at the address proves it holds this gateway's operator token, because the console sends its credential there. From inside the guest the host's forward is usually out of reach, so the check fails; run it with--unproveninstead, and only when the host forwards that port to this gateway. An older gateway's CLI advertises without the check and has no--unproven. Advertising does not move a managed tunnel: as above, the tunnel runs beside the gateway inside the guest and dials the gateway's own port there, so the host's forward plays no part in remote access.
The console reaches a gateway on your own machine directly, at the address it has on file, so that
address has to be one the browser can resolve. From the host's browser, a guest behind a port
forward is 127.0.0.1:<host port>. The guest's own LAN address feels like the right answer and is
not.
PERMAURA_ADVERTISE_ADDR does the same job at install time, if you would rather not run a second
command:
PERMAURA_ADVERTISE_ADDR=127.0.0.1:7377 \
curl -fsSL https://permaura.com/install | PERMAURA_PAIR_CODE=PGW-XXXXXXXX sh -s -- --host 0.0.0.0 --port 7377
A gateway that has to be reachable from another machine entirely is a different question, and port forwards are not the answer to it. Use remote access instead.
A gateway inside a container or VM sees a CLI on the host as another machine, because a port forward delivers traffic from the guest's network rather than from loopback. From gateway 1.3.44, while no device is paired, it refuses four things from such a caller: a vault export, an import that would replace or attach to a credential already there, a new connection that would take over a stored credential, and an edit that changes where an SSH connection's credential is sent or how it is presented. Run those inside the container or VM, or pair a device first.
What the apps show
The desktop app names the process holding the port and stops retrying rather than looping, and the console shows Address in use with the address it tried. Neither will guess: if the program on the port is not a Permaura gateway, nothing in the app or the console can move it, and the fix is on the machine.
My gateway is on an older version than the latest
Expected if you installed with the curl one-liner: it pins a fixed release and never upgrades itself. The npm launcher does update on every run.
Check what you're on, then update in place:
curl -s http://127.0.0.1:7376/health | jq .version
npx -y permaura upgrade
That replaces the binary in ~/.permaura/bin whichever way you originally installed it. Restart the gateway afterwards.
macOS won't open the desktop app
The Permaura desktop app for macOS is signed and notarized, so a normal download and open works. If macOS blocks it, you almost certainly have a copy from somewhere other than permaura.com/download. Delete it and download again from there. Don't work around a Gatekeeper warning on a build whose origin you're not sure of.
The Linux app has no tray icon and no window
The Linux app lives in the tray, and the tray needs a desktop that hosts one. Most do; stock GNOME does not until you install and enable the AppIndicator and KStatusNotifierItem extension. Without a host, the icon is real but has nowhere to appear, and because the window starts hidden there is nothing on screen at all - while the gateway underneath runs perfectly.
The app checks for a tray host at start-up and opens its window instead when there isn't one, so this should not happen on a current version. To open the window at any time:
permaura-desktop --show
Run it while the app is already running and it raises the existing window rather than starting a second copy. It is also the right command for a desktop launcher or a login item on a session with no tray.
Windows says "unknown publisher"
Expected right now. The Windows installer isn't code-signed yet, so SmartScreen shows "Windows protected your PC" with an unknown publisher. Choose More info, then Run anyway.
Only do that for a copy downloaded from permaura.com/download. If you got the installer anywhere else, delete it and download it again from there.
Two things are worth knowing. The warning is about who signed the installer, not about what it does. And once installed, the app's own updates are verified independently: every update carries a signature checked against a key baked into the app, on every platform, so the update path is protected whether or not the installer was signed. Signed installers are coming.
If you'd rather avoid the warning entirely, skip the desktop app and run the gateway from a terminal with npx -y permaura start. Binaries fetched that way don't trip SmartScreen.
Gateway status in the console
It says "unverified" but everything works
Expected for a local gateway. Permaura's servers can't reach 127.0.0.1 on your machine to confirm it's alive, so they don't claim it is. Your browser is talking to it directly, and the data you're seeing is live. Nothing to fix. See Go live.
It says "offline" and no data loads
Work down this list:
- Run
curl -s http://127.0.0.1:7376/health. If that fails, the gateway isn't running. Start it again. - If it answers on a different port than the console expects, set the browser URL under Settings → Gateway → Advanced.
- If the gateway is remote or hosted, the console reaches it through Permaura's server-side proxy; check the public URL is still resolving.
- Reload the console. The status pill re-polls on load.
I'm still seeing sample data
You're in demo mode. It's a per-workspace choice you made once, and it shows a fully working console over sample data: every action responds, but nothing is written and nothing is real. Connect a gateway to switch the same pages over to your own state, as described in Go live.
Agents
The agent connected but shows as Pending
That's the design. A local agent enrolled with a one-time code always arrives Pending until you click Approve on the Agents page. A remote agent that signed in arrives approved already, unless Require manual approval for new agents is on. It is on by default once a device is paired on a hosted gateway or one with a public URL.
The agent connects but every tool is refused
Permaura is deny-by-default: approving an agent lets it connect, not act. It can do nothing until a grant allows it. Open the audit log: the denied entry names the capability and connection that were refused, which tells you exactly what to allow.
Also worth checking:
- The capability is enabled on the connection. A disabled capability is refused for everyone, whatever your policies say.
- The connection's kill switch is off.
- The grant hasn't hit its budget or passed its expiry, and isn't paused.
The client shows no Permaura tools at all
- Restart the client. Most MCP clients read their config only at startup.
- Check the code hasn't expired. Setup codes are single-use and last 15 minutes; the Connect agent dialog counts the current one down. Mint a fresh one with Refresh and paste the new config in.
- Check the URL. A hosted client like ChatGPT cannot reach
127.0.0.1at all; it needs the public URL from remote access.
A call is stuck "awaiting approval"
It's waiting for a human on purpose. Open Control → Approvals and approve or deny it. An agent's held call is single-use and does not expire on its own, so an ignored one waits until somebody decides it.
If the queue says a signature is required and you can't produce one, see Approvals: the action is on the Device-signed rung, or the gateway's approval floor requires a device for every approval, and neither accepts an unsigned decision. Lowering that bar again needs the machine running the gateway if it's local or tunneled, or confirming it's you plus a signature from a device you still hold if it's hosted. Playbooks has the recovery for a hosted gateway whose last device is gone.
My phone isn't told about held approvals
Work down the chain, since any one link stops it:
- The gateway can announce. A hosted gateway, and one you run yourself that is linked to your account (gateway 1.3.25 and later), announces its holds to permaura.com, which pushes them to your phone. A self-run gateway that is not linked has nothing to announce with, so it pushes only to the phones paired with it, directly, and an unpaired phone hears nothing from it. Its holds still show in the console, in the desktop app on that machine, and in the app while it is looking at that workspace. Approvals has the detail.
- The phone is registered. In the app, Settings → This phone → Notifications is on. If it shows as off with a link to iOS Settings, iOS is blocking notifications for Permaura. In the console, Workspace → Notifications should list the phone under your phones.
- This workspace is switched on. In the app, Manage → Devices, on this phone's card. It defaults to on where you can resolve approvals and off elsewhere. Notifications disabled for your account. means your role in that workspace can't resolve approvals, so there is nothing to tell you about.
See Getting notified for how the pieces fit.
The phone shows "Only a paired device can sign this approval"
The action is on the Device-signed rung and the gateway does not trust this phone's signing key. Approve it from a device that is paired with that gateway, or pair this one; see Approvals. Actions on the Approve rung don't need this: the phone decides those with your account, the way the console does.
Pairing an approval device says a step-up is required
You're on a hosted gateway, where enrolling a signing key needs you to confirm it's you and not just an open session. Confirm it in the app when it asks, with Face ID or your fingerprint if the phone is your account's approver, or a passkey or your password. A session, however recent, does not count.
Pairing says the ticket expired, or was already used
Both mean: start the pairing again from the beginning. The proof permaura.com issues lasts three minutes, is tied to that one key, and is spendable exactly once, so a screen left sitting or a retry after one attempt already went through needs a fresh one. Nothing is wrong with the device; confirm it's you again and it re-issues.
The device's own proof is separate and expires in two minutes. If the app or the CLI says the proof is stale, regenerate it on the device rather than re-running the same command.
Pairing answered "waiting for a co-signature"
Expected on a hosted gateway that already trusts a device: the second and every later device is confirmed by a device already paired, from that device's own approval queue. Open the Permaura app on the phone you already have and approve the enrollment there. A console click can't do it, because the confirmation has to carry that device's signature, and the new device can't confirm itself.
If the device that would confirm it is lost, nothing can confirm the request: from gateway 1.3.46 it lapses after 24 hours, and on an older gateway it never resolves. Revoke the lost device first, then pair the new one; see Playbooks.
Revoking this device says you need to confirm it's you
Expected, wherever the console or the app can remove a device. Removing an approval device from the console or the app changes which devices are left to confirm the next enrollment, so it asks you to confirm it's you, the same as pairing on a hosted gateway. Confirm it and the revocation goes through, unless it is someone else's device and you neither are the gateway's owner nor hold Remove any approval device alone: from gateway 1.3.46 that removal waits for a device paired to someone other than you to confirm it, and lapses after 24 hours. On a gateway you run yourself, revoking from its own machine (the desktop app, or permaura approver revoke) doesn't ask, though removing the last usable device there still waits for that device's signature or one of the gateway's recovery codes.
If the console instead says to revoke the device from the gateway's own machine, the gateway runs with no remote access configured. It counts the console as a remote caller and has no way to check its confirmation, so it refuses. Remove the device in the desktop app, or permaura approver revoke in a terminal there.
If you're replacing a lost device, expect to be asked again when you pair the replacement. That's two confirmations for the whole recovery, and they're deliberately separate: the proof that revokes cannot enroll, and the proof that enrolls cannot revoke. Playbooks has the order to do them in.
My saved change on a hosted gateway says "pending" instead of done
Once an approval device is paired to a hosted gateway, control-plane changes that aren't strictly tightening wait for a signature from that device. That covers new policies, lowering a rung, widening a grant, approving an agent, and un-pausing the broker. Approve it in the app and the change applies. From gateway 1.3.51 an edit to a grant, a policy or an agent is the exception: if that row has changed since the edit was held, or the edit was held before the gateway upgraded to 1.3.51, approving it is refused, and you make the change again. Tightening changes still take effect immediately.
Remote access
The tunnel is up but agents still can't connect
This is the most common remote-access problem, and it's almost always the second step being skipped. Provisioning the tunnel is only half of it; your gateway also has to be restarted knowing about it. Until it is, it won't verify Permaura sign-ins and every remote agent is turned away.
- Desktop app: this cannot happen, because the app provisions, starts the tunnel and relaunches the gateway as one step. If remote access is enabled and the dot beside your URL is gray rather than green, the tunnel itself is down. Press Pause, then Start. From desktop 1.4.23, if the gateway does not come back up with remote access, the app puts the gateway's previous settings back and says why: a first enable stays off, and a failed rename leaves the gateway set up for its old address while permaura.com may already route the new name, so try again. It also refuses at once while the gateway is stopped, so start the gateway first.
- Headless: on the gateway's machine, run
permaura remote enable, then restart the gateway with the command it prints (sudo systemctl restart permaura, orpermaura stopand thenpermaura start). The gateway starts the tunnel itself once it is answering.permaura remote statussays whether the tunnel's credentials are stored; if they are not, runpermaura remote enableagain.
Full walkthrough in Remote access.
Connect agent has no URL to give me
Remote access isn't on for that gateway yet. If you turned it on from the console, for a gateway you run yourself that only reserved the name: finish on the gateway's machine, as Remote access describes. The managed tunnel is a Pro feature, and on Free enabling it offers an upgrade instead; bringing your own tunnel works on every plan.
The managed tunnel stopped
If the plan behind it has lapsed, or the organization was moved to Free, permaura.com stops routing the address, and the gateway's card under Settings → Gateway in the console says so, with the date the tunnel will be deleted once one is set. The tunnel on your machine may still look connected, because it is the address that stops answering. While it is stopped the web console and the Permaura phone app cannot reach the gateway either, so use the desktop app or the permaura CLI on the gateway's machine. Paying again before the deletion brings it back as it was, and after it, turning remote access on again gives you the same address; Trials & billing has the timeline.
The tunnel can also be stopped on your machine. From gateway 1.3.48 and desktop 1.4.21, the gateway, or the desktop app, stops it and says why if the connector is ever running a configuration other than the one written on your machine, if it starts carrying private-network traffic, or if the gateway or app cannot confirm the connector's configuration for 30 seconds. A gateway you run yourself logs the reason, and with the operator token its /health reports tunnel_ingress as "stopped" and the reason as tunnel_stop_reason. Neither the gateway nor the app starts the tunnel again by itself. To start it again, use Pause and then Start in the app, or run permaura remote enable for a gateway you run yourself. A gateway you run yourself keeps a stop caused by a pushed configuration or private-network traffic across restarts until you do, and tries again at its next start after one it could not confirm. If the reason says Cloudflare pushed a different configuration or sent private-network traffic to the tunnel, tell us at support@permaura.com first, because that should not happen. What Permaura protects explains what the stop guards against.
I'm bringing my own tunnel
Use an HTTP tunnel (ngrok, a Tailscale Funnel, Cloudflare), never a raw TCP forwarder like ssh -R or a bare socat pipe. The gateway tells local traffic from tunneled traffic by the forwarding headers an HTTP tunnel adds. A raw forwarder adds none, so internet traffic would look local and reach controls that must stay loopback-only.
Sign-in
I'm sent to "Secure your account" and can't get past it
Every account holds a strong factor, and nothing past sign-in works until yours does. Add a passkey, an authenticator app or the Permaura app on your phone, then save the backup codes you're shown. Two-step verification walks through each.
The emailed code never arrives
Check spam first. If it still doesn't come, choose Try another way and use your passkey, the Permaura app, your authenticator app or a backup code instead, none of which need email. If it's the code that confirms a new account, use Send a new code on that page, and check it's the address you signed up with.
"Email me a code" isn't offered
Your password was set from a reset link, and nothing stronger has confirmed it since. Until a passkey, an approval on your phone, your authenticator app or a backup code does, the emailed code isn't accepted; see After a password reset.
Approving on my phone was denied or expired
Denied means the number you tapped on the phone didn't match the one on the screen that asked (or Deny was tapped). Expired means nobody answered within the two minutes a request lasts. Either way, start again from the screen that asked; each new request shows a new number.
I've lost my second factor
Use a backup code from the set you saved when you enrolled, then add a replacement factor. If you lose your phone or another factor covers each case, including a lost phone and a reset password. If everything is gone, contact support from the email address on the account, and expect us to check that it's you through other means before anything changes.
If you administer this for other people
Playbooks covers the admin-shaped incidents: an agent misbehaving, a leaked credential, a spend spike, a broken audit chain, and locking somebody out in a hurry.
Still stuck
Email support@permaura.com with:
- what you were doing and what happened instead,
- the output of
curl -s http://127.0.0.1:7376/health(it contains no secrets), - whether the gateway is local, tunneled, or hosted,
- the audit log entry id for the action, if there is one.
Never send us an API key, a gateway token, or a pairing code. We never need one, and anything you paste into a support thread should be treated as burned.
Ask in public
Email is the main way to reach us, and a person reads every message. If you would rather ask where other people can read the answer, the gateway's source repository on GitHub has two public places:
- GitHub Discussions for questions, ideas and how you use Permaura. Post a question in Q&A, so the answer that solves it can be marked.
- The issue tracker for bugs in the gateway. Include the gateway version, what you did and what happened instead, and a reproduction if you have one.
Never post a suspected vulnerability in either place. Security reports go privately to security@permaura.com. Anything you post there is public, so redact it the same way: no API keys, gateway tokens or pairing codes.