Panel Installation
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.comin 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:
| Container | Image | What it does |
|---|---|---|
xpanel | ghcr.io/xray-panel/backend:latest | interface and API on 127.0.0.1:3000 |
xpanel-db | postgres:18.4 | database |
xpanel-redis | valkey/valkey:9-alpine | cache 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
| Variable | What to set |
|---|---|
APP_SECRET | the result of openssl rand -hex 64 |
POSTGRES_PASSWORD | a random password; the same one is inserted into DATABASE_URL |
DATABASE_URL | host remnawave-db â the compose service name, no need to change it |
PANEL_DOMAIN | https://panel.example.com |
FRONT_END_DOMAIN | https://panel.example.com â the origin allowed to make CORS requests |
SUB_PUBLIC_DOMAIN | panel.example.com/api/sub â without https:// and without a trailing slash |
METRICS_USER, METRICS_PASS | the basic auth username and password for http://127.0.0.1:3001/metrics |
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.
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â
- Node installation â connect a proxy server to the panel.
- Reverse Proxy â a ready-made config for your web server.
- Subscription page â a styled subscription delivery.
- Panel security â what is already protected and what to keep in mind.