---
title: Good practices
description: Tokens, exposing the admin UI, and what it costs.
---
> ## Documentation Index
> Fetch the complete documentation index at: https://docs.page/luizfonseca/super/llms.txt
> Use this file to discover all available pages before exploring further.

## Tokens

- **Create one token for each person or tool** with clear identifiers: removing a token removes only that access, and the UI shows who's signed in too.
- **Read-only unless needed.** Tokens can only read unless they say `access = "write"`: only provide the write token to systems you trust and never expose that publicly.
- **Use secrets from the environment** (`secret = env("PROKSI_ADMIN_TOKEN")`). Never write in the configuration file. Use long random ones via `openssl rand -hex 32` or similar.
- **Rotate keys** by changing the secret and restarting Super: all sessions are terminated when Super restarts.
- **Scrapers** that only need `/metrics` can use their own listener, `metrics_listen`, which serves nothing else ([details](/configuration/admin#a-listener-for-metrics-only)).

## Exposing it

The admin listener is plain HTTP and controls every host: don't expose it on a public address unless you have an additional security layer (tailscale, oauth etc).

**A tunnel** is the simplest: the listener stays on `127.0.0.1`, and only those who can reach the server can reach it.

**For a team**, serve it through Super itself: on a host of its own with TLS with an identity check in front, and the admin token still required behind it. For example, with Github oauth:

```hcl
host "proksi-admin.example.com" {
  location "/" {
    # Only from the office or the VPN
    ip_filter {
      allow = ["203.0.113.0/24", "10.8.0.0/16"]
    }

    # Then sign in with GitHub, letting in only these people
    oauth2 {
      provider      = "github"
      client_id     = env("GITHUB_CLIENT_ID")
      client_secret = env("GITHUB_CLIENT_SECRET")
      jwt_secret    = env("OAUTH2_JWT_SECRET")
      validations = [
        { type = "email", value = ["alice@example.com", "bob@example.com"] }
      ]
    }

    upstream "127.0.0.1:7555" {}
  }
}
```

The UI's session cookie is marked as `Secure`, and changes are refused unless they come from the UI's own pages. Its pages allow no scripts (with exception of fetching Github Releases), and can't be used via iframe by other sites.

### Behind another proxy

Changes made from the UI need to come from its own pages: their `Origin` must be the address the admin listener is reached at, as the `Host` header says. Super passes the `Host` on, so the setup above works as it is. A proxy that sends a different `Host` (nginx by default, many load balancers and tunnels, or one that drops the port) switches the UI to read-only: changes are refused with `403`, naming the origin and the address seen.

You can configure a list of addresses you open the UI at in `trusted_origins`:

```hcl
admin {
  listen          = "127.0.0.1:7555"
  trusted_origins = ["https://proksi.tailnet-name.ts.net:9200"]

  token "ops" {
    secret = env("PROKSI_ADMIN_TOKEN")
    access = "write"
  }
}
```

```bash
# Tailscale: only devices of your tailnet can reach it over HTTPS
tailscale serve --bg --https=9200 http://127.0.0.1:7555
```

Or keep the `Host` with the proxy (nginx: `proxy_set_header Host $host;`). Only these origins are trusted, and only for signed-in sessions: the token is still required, and the session cookie can't be sent by other sites.

## Distributed Mode

- Hosts added from the UI live in the [store](/configuration/redis). With the default memory store they're gone when Super restarts: use Redis, SQLite or Postgres to keep them.
- Keep the hosts you manage as code in the configuration file, and use the store for changes made at runtime. A host in the store takes priority over the file's, which is a way to change one for a while, and to undo it by removing it.
- Every instance sharing the store serves its hosts: the overview's instances table shows which revision each one serves.

## What it costs

No resources are consumed, unless specified:

- Without an `admin` block, nothing listens and nothing more is kept.
- With the block presnet, live stats are sampled only while someone reads them (the overview, a host's page, `/api/v1/stats`), and the server stops 5 minutes after the last read to diminish resource usage.
- The latest requests are only captured while the admin is open.
- The metrics behind `/metrics` and the charts cost a few atomic additions for each request. `metrics = false` turns them off, and the charts with them.
