Skip to main content

Panel Installation

Source code

Panel — xray-panel/backend · node — xray-panel/node · subscription page — xray-panel/subscription-page

The XLADA panel is the main component: users, nodes, subscriptions, templates, statistics, and API. It is installed on a single server with docker compose: the panel, the database, and the cache come up as three containers and require no build.

What you need:

  • a host with Docker and Docker Compose — at least 2 cores, 4 GB RAM, and 20 GB of disk, see Requirements for details;
  • a domain whose A record already points to this host — panel.example.com in the examples;
  • a reverse proxy with TLS: without it the panel does not work.

Compose creates the xpanel-network network and the remnawave-db-data volume:

ContainerImageWhat it does
xpanelghcr.io/xray-panel/backend:latestinterface and API on 127.0.0.1:3000
xpanel-dbpostgres:18.4database
xpanel-redisvalkey/valkey:9-alpinecache and queues

Installing Docker​

sudo curl -fsSL https://get.docker.com | sh

Step 1 — Download the required files​

Create a directory and download the compose file and the environment sample from the panel repository:

mkdir /opt/xlada && cd /opt/xlada

curl -o docker-compose.yml https://raw.githubusercontent.com/xray-panel/backend/main/docker-compose-prod.yml
curl -o .env https://raw.githubusercontent.com/xray-panel/backend/main/.env.sample

docker-compose.yml does not need editing: the ports, volumes, network, and limits are already described in it. All settings live in .env.

Step 2 — Configure .env​

Insert the secrets and domains with ready-made commands:

cd /opt/xlada

# Application secret: password hashes, JWT, TOTP encryption.
sed -i "s/^APP_SECRET=.*/APP_SECRET=$(openssl rand -hex 64)/" .env

# PostgreSQL password and the same password inside DATABASE_URL.
pw=$(openssl rand -hex 24) \
&& sed -i "s/^POSTGRES_PASSWORD=.*/POSTGRES_PASSWORD=$pw/" .env \
&& sed -i "s|^\(DATABASE_URL=\"postgresql://postgres:\)[^@]*\(@.*\)|\1$pw\2|" .env

# Panel and subscription domains.
sed -i "s|^PANEL_DOMAIN=.*|PANEL_DOMAIN=https://panel.example.com|" .env
sed -i "s|^FRONT_END_DOMAIN=.*|FRONT_END_DOMAIN=https://panel.example.com|" .env
sed -i "s|^SUB_PUBLIC_DOMAIN=.*|SUB_PUBLIC_DOMAIN=panel.example.com/api/sub|" .env

# Access to Prometheus metrics.
sed -i "s/^METRICS_USER=.*/METRICS_USER=xpanel/" .env
sed -i "s/^METRICS_PASS=.*/METRICS_PASS=$(openssl rand -hex 24)/" .env

chmod 600 .env
VariableWhat to set
APP_SECRETthe result of openssl rand -hex 64
POSTGRES_PASSWORDa random password; the same one is inserted into DATABASE_URL
DATABASE_URLhost remnawave-db — the compose service name, no need to change it
PANEL_DOMAINhttps://panel.example.com
FRONT_END_DOMAINhttps://panel.example.com — the origin allowed to make CORS requests
SUB_PUBLIC_DOMAINpanel.example.com/api/sub — without https:// and without a trailing slash
METRICS_USER, METRICS_PASSthe basic auth username and password for http://127.0.0.1:3001/metrics
APP_SECRET must not be changed

Password hashes, JWT, and TOTP secret encryption depend on APP_SECRET. After the first start it must not be changed: login stops working and TOTP secrets become unreadable. Generate it once and save it.

Without METRICS_USER and METRICS_PASS the panel container does not start. For a full breakdown of the variables, see Environment Variables.

Step 3 — Start the containers​

docker compose up -d && docker compose logs -f -t

On the first start the panel applies the migrations and seeds the database itself. A successful start looks like this:

xpanel | XLADA Backend v1.1.7
xpanel | Nest application successfully started

Exit the logs with Ctrl+C — the containers keep running. Check the state:

docker compose ps

All three containers — xpanel, xpanel-db, xpanel-redis — must be in the running (healthy) state. If xpanel restarts, look at the reason in its logs:

docker compose logs --tail=100 xpanel

Reverse proxy and HTTPS​

The panel listens on localhost only: 127.0.0.1:3000 — interface and API, 127.0.0.1:3001 — Prometheus metrics. nginx or another web server exposes it to the outside; ports 3000 and 3001 do not need to be opened. By this point the domain's A record must already point to the host, and port 80 must be free: the web server uses it to answer the Let's Encrypt challenge.

HTTPS is required

The panel code has middleware that drops the connection if a request arrives without the X-Forwarded-For and X-Forwarded-Proto: https headers. Without a reverse proxy with TLS the panel does not work.

Full configs with all headers and WebSocket are in Reverse Proxy: nginx, Caddy, Angie, Traefik, and Cloudflare Tunnel. The shortest path is nginx: a ready-made config, a Let's Encrypt certificate, and verification. After setup, make sure the proxy serves the panel:

curl -sI https://panel.example.com | head -3

First login​

Open https://panel.example.com — the panel will prompt you to create an administrator. Enter a username and a password.

Password requirements: at least 24 characters, with lowercase letters, uppercase letters, and digits required. Example: XladaProd2026SecurePass!.

Right after logging in, create an API token: Settings → API Tokens → Create token. The value is shown only once. The token is needed by the subscription page and external integrations.

What's next​