Skip to main content

Self-host on a cloud VM

A gateway on your own cloud VM is always on, reachable from anywhere, and costs a few dollars a month. Nothing in this guide needs a tunnel or a paid plan: you bring the machine and the domain, and Permaura stays out of the path entirely.

This is the do-it-yourself route. If you would rather not run a box, Pro turns the whole of this page into one click: a managed permaura.app tunnel for a gateway on your own machine, or a fully hosted gateway that Permaura runs for you. See plans.

What you will end up with

The gateway listening on 127.0.0.1 on the VM, a reverse proxy terminating TLS on a subdomain you own, port 7376 closed to the world, and the gateway restarting on its own after a reboot.

Before you start

  • A Linux VM. Anything current works; the walkthrough was checked on Ubuntu 24.04 on a DigitalOcean droplet.
  • A subdomain you control, pointed at the VM's IP (gw.example.com in the examples). Use a subdomain rather than a path prefix: the gateway builds absolute URLs from its public URL, so a path prefix needs rewriting to work.
  • Root or sudo on the box.

1. Install the gateway

SSH to the VM and run:

curl -fsSL https://permaura.com/install | sh

That does rather more than drop a binary:

  • Installs permaura to ~/.permaura/bin, verifying the download's sha256 against a manifest that permaura.com checked against the pinned release key.
  • Registers a systemd service so the gateway starts at boot. Running as root on a VM you get a system unit, which is the usual VPS case. Where systemd is absent (some containers and minimal images) it says so plainly rather than pretending.
  • Starts it on 127.0.0.1:7376 and waits until it actually answers.
  • Writes its settings to ~/.permaura/gateway.env.

Check it:

curl -s http://127.0.0.1:7376/health
sudo systemctl status permaura
Your keys really are yours

On a headless install the gateway generates a per-install vault key at ~/.permaura/vault-kek (owner-read-only) on first run, and seals your credentials under it. There is no shared default key and no key held by Permaura. A gateway that cannot resolve a real key refuses to start rather than falling back to something weaker.

2. Put TLS in front of it

The gateway speaks plain HTTP and cannot terminate TLS. There is no inbound TLS in the binary at all, so https://your-host:7376 is not a thing that can work. Exposing port 7376 directly puts the operator token, and every agent call, on the wire in clear text.

The supported shape is a reverse proxy on your subdomain that terminates TLS and forwards to loopback. The gateway keeps its default 127.0.0.1 bind and is never exposed directly.

Caddy gets a certificate from Let's Encrypt and renews it with no further configuration. The entire config is:

/etc/caddy/Caddyfile
gw.example.com {
reverse_proxy 127.0.0.1:7376
}
sudo systemctl reload caddy

3. Tell the gateway its public URL

A gateway only answers to hosts it recognizes. Out of the box that is 127.0.0.1, localhost and [::1] on its own port, and nothing else. This is a DNS-rebinding guard and it is worth having, but it means a request arriving as gw.example.com comes back 421 Misdirected Request until you say otherwise.

Edit ~/.permaura/gateway.env:

~/.permaura/gateway.env
PERMAURA_PUBLIC_URL=https://gw.example.com
PERMAURA_OWNER_SUB=<your Permaura user id>
sudo systemctl restart permaura
Never set PERMAURA_PUBLIC_URL on its own

PERMAURA_PUBLIC_URL does two things: it admits your hostname, and it switches the gateway into an OAuth resource server so remote agents can sign in through Permaura. Without PERMAURA_OWNER_SUB alongside it, the resource server has nothing to bind tokens to and fails closed, rejecting every remote token. The symptom is a gateway that is plainly reachable and refuses every agent. Set both, or neither.

Your user id is on the Settings → Account page in the console.

Reachable, but no remote agent sign-in?

If you want the host admitted without turning on OAuth (a proxy that does its own authentication, say), use the narrower knob instead and leave PERMAURA_PUBLIC_URL unset:

PERMAURA_ALLOWED_HOSTS=gw.example.com

This admits the Host and nothing else. Do not reach for it as the default for a remote-agent setup: it leaves you with a reachable gateway that no agent can authenticate to. Note also that with no resource server configured, unauthenticated callers can still read the gateway's version and enumerate its tool list. They cannot invoke anything, which is refused without a valid session, but there is no reason to publish that surface.

Settings in gateway.env are read by both the installer and the systemd unit, so they survive a restart, a reboot and an --update, and it is the file to edit for everything on this page.

Since gateway 1.2.9 the binary also reads ~/.permaura/config.toml directly, which it did not when this guide was written. The two overlap, and where they do the environment wins: gateway.env is loaded by the unit that starts the gateway, and the file only fills in settings the environment has not already set. So editing gateway.env still does what you expect, and a value you put in config.toml will look ignored if the same key is in gateway.env too.

config.toml is strict on purpose

An unrecognised key is a startup error, not a warning. That is deliberate — a typo like public_utl would otherwise leave the gateway on loopback with no resource server and nothing said about why — but it does mean a key from a newer gateway will stop an older one from starting. Check permaura --version against the setting you are adding.

4. Close the port

A stock cloud VM often has no firewall running at all. On a fresh DigitalOcean droplet ufw is inactive and the iptables policy is ACCEPT, so anything you bind is immediately world-reachable.

Since the gateway is on loopback and the proxy is the only thing that talks to it, allow only SSH and HTTPS:

sudo ufw allow OpenSSH
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status

Port 7376 should never appear. If your provider has its own cloud firewall, set the same rules there.

5. Verify

From your laptop, not the VM:

curl -s https://gw.example.com/health
curl -s -o /dev/null -w '%{http_code}\n' https://gw.example.com/v1/agents

You want {"status":"ok",...} from the first and 401 from the second. A 401 is the right answer: it means the gateway is reachable and is refusing unauthenticated callers.

curl -s -o /dev/null -w '%{http_code}\n' http://<vm-ip>:7376/health

That should fail to connect. If it answers, port 7376 is still open.

Managing it

You have a shell on the box, and for a self-hosted gateway that is the primary way to work with it. The permaura CLI on the VM reads the operator token from ~/.permaura/operator.token and talks to the gateway over loopback:

permaura status
permaura agent add my-agent
permaura connection list
permaura audit --limit 20

Some things can only be done on the box, by design. Three primitives mint or move authority with no credential, so they are refused to any caller that is not a loopback client on the gateway's own host:

  • opening a session (POST /v1/sessions)
  • graceful shutdown
  • pairing an approver device, and lowering the approval floor

A reachable gateway that accepted those would let anyone who can open a socket mint operator access, so the restriction is the point rather than a gap. Everything else is ordinary authenticated management and works from anywhere.

If you also enroll the gateway to a Permaura account, the console becomes a convenience layer over the same gateway: it reaches it through a server-side broker that holds the sealed operator token, so the token never reaches your browser. Enroll it with a pairing code from Settings → Gateway:

curl -fsSL https://permaura.com/install | sh -s -- --pair PGW-XXXXXXXXXX

The console is optional. A gateway you never enroll still works, and still enforces everything.

Reboots

The systemd unit the installer registers brings the gateway back after a reboot. Confirm it rather than assuming:

sudo systemctl is-enabled permaura
sudo reboot
# then, once it is back
curl -s http://127.0.0.1:7376/health

If the installer told you it could not register a service (no systemd), the gateway runs under nohup and will not come back. Install it on a machine with systemd, or supervise it yourself.

Updating

permaura upgrade

That verifies the new binary against the signed release manifest, swaps it in, and restarts the systemd service, on the same address and settings. Replacing the file is not enough on its own: the running process holds the old binary and keeps serving it until something restarts it, which is why the upgrade does the restart rather than telling you to.

If you installed under a dedicated account with --service-user, that binary belongs to a nologin user and cannot upgrade itself. Re-run the one-liner instead:

sudo sh -c "curl -fsSL https://permaura.com/install | sh -s -- --update"

Use that form too after any change to how the service itself is installed, since it rewrites the unit as well as the binary.

If you would rather not run a box

Everything above is the price of owning the whole path. Permaura's paid plans exist to remove it:

  • A managed tunnel. Flip one switch and your gateway, wherever it runs, gets a stable permaura.app URL over a secure tunnel. No VM, no proxy, no certificate, no firewall, no DNS. See Remote access.
  • A hosted gateway. Permaura runs the gateway itself, so there is no machine in your life at all. See execution modes.

Both are one click from the console. This guide is here because self-hosting should be a real option rather than a lock-in point, not because it is the easy one.

A note on binding without a proxy

You can bind a reachable interface directly:

curl -fsSL https://permaura.com/install | sh -s -- --host 0.0.0.0

The installer allows the machine's detected address in PERMAURA_ALLOWED_HOSTS for you, and warns you about what you just did.

Do not do this on the public internet. The gateway cannot terminate TLS, so the operator token and every agent call cross the network in clear text, readable and modifiable by anything on the path. It is a reasonable shape only on a network you already trust end to end, such as a private VPC, a Tailscale tailnet or a WireGuard network, where something else is providing the encryption. For anything reachable from the internet, use the reverse proxy above.