Remnawave install: panel and nodes
Remnawave install for the panel and nodes: Docker behind a TLS reverse proxy, VLESS Reality Vision nodes, subscriptions, backups, updates and common failures.
Current as of September 2026: Remnawave panel 3.4.4, node 3.4.1 with Xray-core 26.7.28 built in, subscription page 8.0.0. We skip Docker and Xray basics and focus on a multi-node setup and on what breaks in production.
What you'll need
- A VPS for the panel: at least 2 vCPU and 2 GB RAM, 4 and 4 recommended, 20 GB of disk, Ubuntu or Debian.
- A VPS per location: a node runs on 1 vCPU and 1 GB, but Xray gets hungry under load, so size commercial nodes with headroom. At ForestsNet Cloud these are servers in the Netherlands (Eygelshoven) and Germany (Frankfurt) on AMD EPYC 7713 with NVMe and 1–10 Gbit/s ports; IP addresses are checked daily for blocks by Russia's DPI (TSPU).
- A domain: A records
panel.example.comandsub.example.compointing to the panel server,nl1.example.comandde1.example.compointing to the nodes, see Pointing a domain at your server. - Docker with the Compose plugin on every server:
curl -fsSL https://get.docker.com | sh, details in Docker and Docker Compose. - Basic hardening: SSH hardening, the ufw firewall, VPS security checklist.
Step 1. Plan which component runs where
Almost every Remnawave failure is one service that can't reach another, so start with the map.
- Panel (
remnawave/backend): API and web UI on port 3000, Prometheus metrics on 3001. Next to it run PostgreSQL 18 with all the data and Valkey, a Redis-compatible queue and cache on a unix socket. Everything is published on127.0.0.1only; the reverse proxy faces the internet. - Node (
remnawave/node): a container with Xray-core. The panel pushes its configuration, users and keys. - Subscription page (
remnawave/subscription-page): port 3010. Serves subscriptions from its own domain and calls the panel API with a token.
Connections:
- Panel → node: the panel connects to the node on
NODE_PORT(2222 by default) over HTTPS with mutual certificate authentication, TLS 1.3 only. The panel is the certificate authority; the node's certificate and key are packed into itsSECRET_KEY. The node never connects to the panel. - Client → node: 443/TCP, the VLESS Reality inbound.
- Client → subscription page: 443 on
sub.example.com; the page then talks to the panel over the internal Docker network.
If the panel goes down, nodes keep serving existing users with the last configuration they received, but new users, renewals and suspensions wait until it's back. Keep the panel on its own VPS rather than on a node: node addresses are the ones you'll end up changing.
Step 2. Install the panel
mkdir -p /opt/remnawave && cd /opt/remnawave
curl -o docker-compose.yml https://raw.githubusercontent.com/remnawave/backend/refs/heads/main/docker-compose-prod.yml
curl -o .env https://raw.githubusercontent.com/remnawave/backend/refs/heads/main/.env.sampleThe compose file uses remnawave/backend:3 (updates within major version 3), PostgreSQL 18.4 and Valkey 9. Generate the secrets and the database password:
sed -i "s/^APP_SECRET=.*/APP_SECRET=$(openssl rand -hex 64)/" .env
sed -i "s/^METRICS_PASS=.*/METRICS_PASS=$(openssl rand -hex 64)/" .env
sed -i "s/^WEBHOOK_SECRET_HEADER=.*/WEBHOOK_SECRET_HEADER=$(openssl rand -hex 64)/" .env
pw=$(openssl rand -hex 24)
sed -i "s/^POSTGRES_PASSWORD=.*/POSTGRES_PASSWORD=$pw/" .env
sed -i "s|^\(DATABASE_URL=\"postgresql://postgres:\)[^@]*\(@.*\)|\1$pw\2|" .envThe JWT secrets from older guides are gone: in 3.x a single APP_SECRET signs sessions and API tokens, and the panel refuses to start with change_me. When upgrading from 2.x, rename JWT_AUTH_SECRET to APP_SECRET and keep the value.
Set the domains in .env:
FRONT_END_DOMAIN=panel.example.com
PANEL_DOMAIN=panel.example.com
SUB_PUBLIC_DOMAIN=sub.example.comFRONT_END_DOMAIN drives CORS, PANEL_DOMAIN is used for links in notifications, and SUB_PUBLIC_DOMAIN is what the panel builds subscription links from: no https:// and no trailing slash. Without a separate subscription page, use panel.example.com/api/sub.
docker compose up -d && docker compose psStep 3. Set up the reverse proxy and lock down the panel
The backend accepts only requests that came through a proxy over HTTPS: without X-Forwarded-For and X-Forwarded-Proto: https it silently drops the connection and logs "Reverse proxy and HTTPS are required". It also can't run under a sub-path like /panel — only at the root of a domain or subdomain.
We'll use Caddy: it gets Let's Encrypt certificates on its own and sets the headers; basics are in Caddy: automatic HTTPS. The panel gets a knock-and-cookie gate right away: without a secret cookie Caddy closes the connection without any response. The sign-up form never faces the internet, which matters because the first user to register becomes the super-admin.
mkdir -p /opt/remnawave/caddy && cd /opt/remnawave/caddy
echo "RW_KNOCK=$(openssl rand -hex 16)" > .envCaddyfile:
panel.example.com {
@knock query rw={$RW_KNOCK}
@allowed header Cookie *rw_gate={$RW_KNOCK}*
route {
header @knock Set-Cookie "rw_gate={$RW_KNOCK}; Path=/; Max-Age=2592000; HttpOnly; Secure; SameSite=Lax"
reverse_proxy @knock remnawave:3000
reverse_proxy @allowed remnawave:3000
abort
}
}
sub.example.com {
reverse_proxy remnawave-subscription-page:3010
}docker-compose.yml:
services:
caddy:
image: caddy:2.11
container_name: caddy
restart: always
env_file: .env
ports:
- "80:80"
- "443:443"
networks:
- remnawave-network
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data
networks:
remnawave-network:
external: true
volumes:
caddy-data:Run docker compose up -d. To get in, open https://panel.example.com/?rw=<RW_KNOCK value>: Caddy sets a 30-day cookie, and after that the panel opens at its normal address. The panel's compose file created remnawave-network, so Caddy reaches the containers by name.
Panel 3.x has no built-in cookie gate. The docs offer remnawave/caddy-with-auth with TOTP (the image hasn't been updated since March 2025), TinyAuth in front of nginx, and Cloudflare Zero Trust; in nginx the same knock is done with map $http_cookie, as in the eGames script. Telegram OAuth login may break behind such gates.
Step 4. First login and the subscription page
Open the knock link and register — this account becomes the super-admin. In Remnawave Settings, add a passkey and turn off password login; if you lock yourself out, the Rescue CLI brings it back: docker exec -it remnawave cli.
Issue an API token for the subscription page in Remnawave Settings → API Tokens. In 3.x tokens must have an expiry — set it generously and add a reminder: an expired token breaks subscriptions for everyone at once.
/opt/remnawave/subscription/docker-compose.yml:
services:
remnawave-subscription-page:
image: remnawave/subscription-page:8.0.0
container_name: remnawave-subscription-page
hostname: remnawave-subscription-page
restart: always
env_file: .env
ports:
- "127.0.0.1:3010:3010"
networks:
- remnawave-network
networks:
remnawave-network:
external: true.env next to it:
APP_PORT=3010
REMNAWAVE_PANEL_URL=http://remnawave:3000
REMNAWAVE_API_TOKEN=token_from_the_panel
TRUST_PROXY=1The scheme in REMNAWAVE_PANEL_URL must be http://. The repository's .env.sample has https://remnawave:3000, but inside the Docker network the backend speaks plain HTTP, and the page adds the proxy-check headers itself only for an http:// address. TRUST_PROXY=1 means one trusted proxy, Caddy. Start it with docker compose up -d.
A subscription link looks like https://sub.example.com/<shortUuid>: a browser gets a page with setup instructions, an app gets its configuration.
Step 5. Build the VLESS Reality Vision profile
A config profile (Config Profiles) is a complete Xray config for a node. The panel validates it with a lightweight core on every save and fills in users itself, so leave clients empty. For VLESS with Reality over RAW (formerly TCP), clients get flow: xtls-rprx-vision automatically.
Reality target: your own site on the node
Reality borrows the TLS handshake of a target site (target, formerly dest) and forwards everything that fails authentication to it. The setup here is self-steal: each node has its own subdomain with an A record pointing to its IP, Caddy with a Let's Encrypt certificate listens on 127.0.0.1:9443 on the node, target points there, and serverNames lists your domains. The SNI honestly resolves to the node's address, a visitor without the key sees an ordinary website, and Caddy serves TLS 1.3 and HTTP/2 — what Reality requires of a target — out of the box. One profile serves all nodes, and a burned subdomain is replaced in the panel: clients pick up the new SNI with their next subscription update.
On ForestsNet Cloud servers this is the only option: the Terms of Service prohibit making a server impersonate someone else's site, that is, putting a domain you don't own into SNI, target or a certificate. Such setups get IP addresses blocked, and that hurts every customer on the network.
The profile editor also has a ready-made self-steal template from the remnawave/templates repository (menu → Download from Github): it uses nginx on the unix socket /dev/shm/nginx.sock with PROXY protocol (xver: 1). The Caddy variant on 127.0.0.1:9443 below is simpler: no socket and no /dev/shm mount in the node container. A ready-made self-steal installer for Caddy or nginx is in DigneZzZ's scripts from the community catalog on docs.rw.
Keys, shortIds and minClientVer
Generate x25519 keys in the profile editor: bottom menu → Generate Keys. The privateKey goes into the profile; the panel derives the public key and hands it to clients. shortIds are hex strings of even length up to 16 characters, for example from openssl rand -hex 8; clients get the first element of the array, so put a non-empty ID first.
The Xray in node 3.4.1 accepts Reality clients only from Xray 26.3.27 and newer by default. Mihomo and sing-box — the cores behind Clash Verge, FlClashX, Hiddify and Karing — identify themselves as version 1.8 and get refused, which is why Remnawave's templates set "minClientVer": "0.0.0". XTLS warns that old and third-party clients stand out by their TLS fingerprint and that lowering the threshold raises the risk of blocking. If all your users run recent Xray-based apps such as Happ, v2rayNG or v2rayN, you can drop the line.
A self-steal profile shared by all nodes:
{
"log": { "loglevel": "warning" },
"inbounds": [
{
"tag": "VLESS_REALITY",
"listen": "0.0.0.0",
"port": 443,
"protocol": "vless",
"settings": { "clients": [], "decryption": "none" },
"sniffing": { "enabled": true, "destOverride": ["http", "tls", "quic"] },
"streamSettings": {
"network": "raw",
"security": "reality",
"realitySettings": {
"target": "127.0.0.1:9443",
"xver": 0,
"serverNames": ["nl1.example.com", "de1.example.com"],
"privateKey": "GENERATE_IN_THE_PANEL",
"shortIds": ["9f3c1a7e5b2d4086"],
"minClientVer": "0.0.0"
}
}
}
],
"outbounds": [
{ "protocol": "freedom", "tag": "DIRECT" },
{ "protocol": "blackhole", "tag": "BLOCK" }
],
"routing": {
"rules": [
{ "ip": ["geoip:private"], "outboundTag": "BLOCK" },
{ "domain": ["geosite:private"], "outboundTag": "BLOCK" },
{ "protocol": ["bittorrent"], "outboundTag": "BLOCK" }
]
}
}The private rules keep clients out of the node's local services, including Caddy on 127.0.0.1. The bittorrent rule catches only part of torrent traffic — for the rest see How to block torrents on your VPN or proxy and A torrent detector for Remnawave.
Step 6. Install the nodes
The self-steal site
On each node, run Caddy: it gets a Let's Encrypt certificate for the node's subdomain via HTTP-01 on port 80 and serves the site on 127.0.0.1:9443. Don't proxy the node's DNS record through Cloudflare — clients must reach the IP directly. Put some plausible static content into /opt/selfsteal/site, at least an index.html.
/opt/selfsteal/Caddyfile:
{
https_port 9443
default_bind 127.0.0.1
auto_https disable_redirects
}
http://nl1.example.com {
bind 0.0.0.0
redir https://nl1.example.com{uri} permanent
}
https://nl1.example.com {
root * /srv/site
file_server
}/opt/selfsteal/docker-compose.yml:
services:
caddy:
image: caddy:2.11
container_name: selfsteal
restart: always
network_mode: host
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./site:/srv/site:ro
- caddy-data:/data
volumes:
caddy-data:The node
In the panel, open Nodes → Management → +: country, name, address and port 2222. The Copy docker-compose.yml button copies a file with the SECRET_KEY — base64 of a JSON holding the panel's CA certificate, the node's certificate and key, and the public key for JWT checks. Treat it like a password. On the node, save the file as /opt/remnanode/docker-compose.yml:
services:
remnanode:
container_name: remnanode
hostname: remnanode
image: remnawave/node:3.4.1
network_mode: host
restart: always
cap_add:
- NET_ADMIN
ulimits:
nofile:
soft: 1048576
hard: 1048576
environment:
- NODE_PORT=2222
- SECRET_KEY="key_from_the_panel"The panel puts in the latest tag — replace it with an explicit version: latest means whatever was newest at each server's last pull, and your fleet quietly drifts across versions. NET_ADMIN is needed by the node's nftables plugins, including Torrent Blocker, and for dropping connections of removed users.
cd /opt/remnanode && docker compose up -d && docker compose logs -f -tBack in the panel, click Next, pick the profile and enable the VLESS_REALITY inbound on the node. Within seconds the node card shows the Xray and node versions.
The node firewall
Open NODE_PORT to the panel's IP only. The node uses host networking, so ufw rules apply to it directly:
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw allow from 203.0.113.10 to any port 2222 proto tcp
ufw enable203.0.113.10 is the panel server; port 80 lets Caddy issue and renew its certificate.
Step 7. Hosts and squads for several locations
A host is the client side of an inbound: the address, port, SNI and label the user sees in the app. Create a host per node in Hosts: inbound VLESS_REALITY, address nl1.example.com, port 443, and in the advanced settings enable Override SNI from Address — otherwise the client gets the first element of serverNames. Selecting nodes in the host form is only a label for the admin.
Access is controlled by internal squads (Internal Squads) — sets of inbounds from any profiles. A user gets the hosts of the inbounds in their squads and is added to Xray on every node where those inbounds are enabled. The unit of access is the inbound, not the node: if some customers should see only the Netherlands and others Germany as well, create a profile with its own inbound per location and build squads such as Base and Premium from them. External squads (External Squads) override subscription templates and settings for a group of users.
Step 8. Users and subscriptions
Each user has a traffic limit and a reset strategy: NO_RESET, DAY, WEEK (Mondays), MONTH (the 1st) or MONTH_ROLLING — monthly on the day the user was created, handy for subscriptions bought on arbitrary dates. The same form sets the expiry date and squads. Statuses are ACTIVE, DISABLED, LIMITED (traffic used up) and EXPIRED; an inactive user gets placeholder entries such as "Subscription expired" instead of servers, with texts editable in Subscription → Settings. The traffic multiplier on a node lets you count an expensive location's traffic double, for example.
The HWID device limit (Subscription → Settings → HWID Device Limit) is off by default. Not every app sends an HWID — the docs list Happ, v2RayTun, Koala Clash and FlClashX — and once it's on, a client without an HWID gets an empty subscription or an "App not supported" placeholder unless the limit is disabled for that user.
Response Rules pick the response format from request headers. By default a browser gets the subscription page, Mihomo, Stash, sing-box and Clash clients get a config from their template in Templates, and everything else gets a base64 list of links. A separate rule can serve a custom template to a specific app.
Apps by platform:
- Android: v2rayNG and Happ (Xray), Hiddify (sing-box), FlClashX (Mihomo).
- iOS and macOS: Happ, Streisand and V2Box (Xray), Shadowrocket, Stash, sing-box.
- Windows, Linux and macOS: v2rayN (Xray), Clash Verge and Koala Clash (Mihomo), Hiddify and Throne (sing-box).
Step 9. Operations
PostgreSQL backups
The database holds everything: users with their UUIDs, profiles, hosts, settings and the keys of the certificate authority the nodes trust. Lose it without a copy and you're issuing a new SECRET_KEY to every node and new credentials to every user. Keep .env next to the dump: with a different APP_SECRET, issued API tokens stop working.
/usr/local/bin/rw-backup.sh:
#!/bin/sh
set -e
D=/opt/remnawave/backup
mkdir -p "$D"
docker exec remnawave-db pg_dump -U postgres -d postgres -Fc > "$D/rw-$(date +%F-%H%M).dump"
cp /opt/remnawave/.env "$D/env-$(date +%F)"
find "$D" -type f -mtime +14 -deletechmod +x /usr/local/bin/rw-backup.sh
echo "20 3 * * * root /usr/local/bin/rw-backup.sh" > /etc/cron.d/rw-backupShip copies off the server, see Database backups. Restore onto the same panel version: start only the database, load the dump, then start the rest.
cd /opt/remnawave && docker compose up -d remnawave-db
docker exec -i remnawave-db pg_restore -U postgres -d postgres --clean --if-exists < backup/rw-2026-09-30-0320.dump
docker compose up -dUpdates
Panel first, then nodes:
cd /opt/remnawave && docker compose pull && docker compose down && docker compose up -dThe remnawave/backend:3 tag won't jump to the next major version on its own, and that's a good thing: in 3.0 users in the REST API switched from UUIDs to numeric ids, and bots built on the old API broke. Before a major upgrade, read the release notes and take a dump. Update nodes one at a time: change the tag, run docker compose pull && docker compose up -d, check the version on the node card, move on.
Logs and monitoring
- Panel:
docker compose logs -f remnawave, processes viadocker exec -it remnawave pm2 monit, liveness viacurl -fsS http://127.0.0.1:3001/health. - Node:
docker logs -f remnanodefor the node itself,docker exec remnanode xlogsfor Xray. If you write Xray logs to the host under/var/log/remnanode, set up logrotate, or the access log will eat the disk. - Metrics:
http://127.0.0.1:3001/metricswith basic auth fromMETRICS_USERandMETRICS_PASS. For alerts, useremnawave_node_status(1 means connected) andremnawave_node_online_users; there's a ready Grafana dashboard, 25064. - Notifications: node outages can go to a Telegram bot (
IS_TELEGRAM_NOTIFICATIONS_ENABLED,TELEGRAM_BOT_TOKEN,TELEGRAM_NOTIFY_NODES) or a webhook (WEBHOOK_ENABLED,WEBHOOK_URL).
Migrating from Marzban
The official route is the remnawave/migrate tool, v2.1.0 (Marzban, Marzneshin, 3X-UI). It moves users with their VLESS UUIDs, Trojan and Shadowsocks passwords, limits and expiry dates. Bring the nodes up first. If your old setup already used your own domain, keep the addresses, ports, Reality key, SNI and shortId — clients keep working without a subscription update; configs that used someone else's target have to be reissued. Remnawave has no VMess. For the source panel, see Marzban install: panel and nodes.
./remnawave-migrate --panel-type=marzban \
--panel-url=https://marzban.example.com --panel-username=admin --panel-password='password' \
--remnawave-url=https://panel.example.com --remnawave-token='API-token' \
--dest-headers="Cookie:rw_gate=RW_KNOCK_value" \
--internal-squad=SQUAD_UUID --preserve-status --preserve-subhash --last-users=5--dest-headers gets the tool through the gate from step 3. The tool predates panel 3.0, so start with a trial run of --last-users=5. Old /sub/<token> links can be served by the subscription page: add MARZBAN_LEGACY_LINK_ENABLED=true, CUSTOM_SUB_PREFIX=sub and MARZBAN_LEGACY_SECRET_KEY — the result of SELECT secret_key FROM jwt LIMIT 1; in the Marzban database — to its .env, and set SUB_PUBLIC_DOMAIN=sub.example.com/sub in the panel's .env.
Troubleshooting
The node won't connect
- Port closed. From the panel server:
curl -vk --max-time 5 https://<node IP>:2222. A TLS error means the port is open and the node answers; a timeout means ufw, the provider's firewall or a wrong port on the node card. - Broken SECRET_KEY. The key got truncated or picked up a line break, and the node exits with
SECRET_KEY contains invalid JSONorInvalid SECRET_KEY payload. Copy the compose file from the panel again. - Panel reinstalled without the database. The certificate authority is new — issue a new
SECRET_KEYto every node. - Xray won't start. The node is connected but inbounds are dead; the node log shows "Xray Core … failed to start" with the tail of the Xray log (full log:
docker exec remnanode xlogs). It's almost always a profile error or port 443 already in use. - Clock skew. mTLS and JWT depend on time: check
timedatectlon both sides.
The subscription is empty or shows placeholders
- "No hosts found", "Check Hosts tab", "Check Internal Squads tab". The user's squads have no inbound with an enabled host, or the host is disabled, hidden or restricted to other squads. If the host is delivered but connections fail, the inbound isn't enabled on the node.
- "Subscription expired", "limited", "disabled". That's the user's status, not a fault.
- Empty response or "App not supported". The HWID limit is on and the app sends no HWID; "Limit of devices reached" means the device limit is used up.
- 404. No user with that
shortUuid: deleted, or the link was reissued. - Errors for everyone at once.
docker logs remnawave-subscription-page: the API token expired, orREMNAWAVE_PANEL_URLsayshttps://. - Links in the panel point to the wrong place.
SUB_PUBLIC_DOMAINincludes a scheme or trailing slash, or the container wasn't recreated after editing.env.
Reality fails on some networks
First connect with the same client from a network without censorship: if it fails there too, the problem is the node or the profile. If it fails only inside a censored country:
- Russia: the first 14–25 KB arrive, then silence. Since June 2025, TSPU at many providers freezes TCP connections to the subnets of large hosting providers and CDNs. XHTTP and a new subdomain don't help; a relay server inside Russia does. The TCP 16-20 checker from hyperion-cs/dpi-checkers shows whether a given provider applies it.
- Russia: heavy pages hang for a couple of minutes. On some mobile networks a burst of TLS handshakes to one server freezes new connections, and Vision opens one handshake per app connection. A second inbound on XHTTP and the
firefoxfingerprint in the host's advanced settings help. - Nothing works on mobile only. Whitelisting: the operator lets through only approved IP ranges, and no protocol reaches a foreign VPS. Russia's mobile networks do this during restrictions, and Iran has cut international traffic almost entirely during shutdowns. That takes an entry server inside the country — a separate setup.
- One node dropped while the others work. Its address or SNI is blocked — in China and Iran this is the usual failure mode. Change the self-steal subdomain first, then the address; if a ForestsNet address is blocked, contact support.
Commands for testing from a customer's network are covered in 3x-ui install and VLESS Reality setup, protocol choice in Self-hosted VPN protocols.
If the handshake fails for every client of a node, it isn't DPI: the host's SNI isn't in serverNames, the target doesn't answer (on the node: curl -vk --resolve nl1.example.com:9443:127.0.0.1 https://nl1.example.com:9443), or the host is bound to an inbound of another profile with a different key.
502 from the reverse proxy
- The panel isn't running or keeps restarting.
docker compose psin/opt/remnawaveandcurl -fsS http://127.0.0.1:3001/health; a common cause of the loop is a database password changed after the first start. - The proxy is on the wrong network. Caddy or nginx must be attached to
remnawave-network, or the nameremnawavewon't resolve. - Missing proxy headers. Without
X-Forwarded-ForandX-Forwarded-Proto: httpsthe backend drops connections and logs "Reverse proxy and HTTPS are required". nginx needsproxy_set_header X-Forwarded-Proto $schemeand TLS on nginx itself: behind Cloudflare in Flexible mode the scheme ishttp. - Panel under a sub-path.
location /panelisn't supported — use a separate domain or subdomain.
Sources
- Remnawave documentation: installation, reverse proxies, panel security, environment variables, migrating from Marzban.
- remnawave/backend, remnawave/node, remnawave/subscription-page, remnawave/migrate: compose files,
.env.sample, theSECRET_KEYformat, releases. - REALITY in the Xray docs and XTLS/REALITY: target requirements, shortIds format.
- remnawave/templates, the Xray commit with the default minClientVer and Xray-core #6477: the self-steal template and Mihomo's incompatibility with Xray 26.7.
- net4people/bbs #490, hyperion-cs/dpi-checkers and ntc.party: Cloudflare, OVH, Hetzner, DigitalOcean blocking since 2025-06-09: the TCP freeze after 15–20 KB.
- Habr: the era of whitelists (in Russian): mobile network restrictions, December 2025.
- eGamesAPI/remnawave-reverse-proxy and DigneZzZ/remnawave-scripts: the cookie gate and self-steal in community scripts.