From 3x-ui-vpn-skills
Deploys and manages 3X-UI on Ubuntu/Debian VPS with Docker Compose, nginx proxy, ACME certs, SSH tunneling, UFW hardening, and Xray VLESS over XHTTP on port 443. For fresh installs, repairs, client adds, or safe updates.
npx claudepluginhub olegtsvetkov/3x-ui-vpn-skill --plugin 3x-ui-vpn-skillsThis skill uses the workspace's default tool permissions.
Deploy 3X-UI on a VPS with the panel and subscription server bound to loopback, `ufw` allowing only SSH/HTTP/HTTPS, nginx on public `80/443`, and one `VLESS + XHTTP` transport routed through nginx.
Provisions, hardens, and deploys apps to Hetzner Cloud VPS with Docker, Nginx/Caddy reverse proxy, SSL certs, database setup, monitoring, and backups.
Manages VPS for autonomous dev environments: checks status via Supabase queries and health endpoints, connects projects via SSH, provisions new VPS.
Sets up, hardens, or reviews Linux cloud servers for secure web hosting: SSH, firewalls, Nginx for static sites or app reverse proxies, DNS, HTTPS via Let's Encrypt/ACME, HTTP redirects, BBR tuning.
Share bugs, ideas, or general feedback.
Deploy 3X-UI on a VPS with the panel and subscription server bound to loopback, ufw allowing only SSH/HTTP/HTTPS, nginx on public 80/443, and one VLESS + XHTTP transport routed through nginx.
This skill is manual-first because it mutates remote infrastructure. Invoke it only when the user explicitly asks to deploy, repair, harden, or update a VPS.
Collect these before doing any work:
ssh target for the VPS, preferably root@hostAssume Ubuntu or Debian with apt. Do not use this skill on other distributions without adapting the Docker repository setup first.
The operator workstation and the target VPS also need outbound internet access for Docker downloads, ACME issuance, and panel API calls.
Before changing the host, confirm these assumptions:
acme.sh "Domains not changed" as a hard failureUse scripts/bootstrap-host.sh.
Example:
./scripts/bootstrap-host.sh \
--host root@example-vps \
--ssh-password 'host-password' \
--domain vpn.example.com \
--panel-username admin \
--panel-password 'panel-secret'
Default shape:
/opt/3x-uiget.docker.comnetwork_mode: host3x-ui-data/db/3x-ui-data/cert/127.0.0.1:<panel_port>127.0.0.1:2096dig before ACME issuance443401 for unmatched trafficgrpc_passufw must allow only SSH, HTTP, and HTTPS from the internetAfter deploy, verify:
ssh <target> 'ss -ltnp | egrep ":2053 |:2096 |:1234 "'
ssh <target> 'docker compose -f /opt/3x-ui/docker-compose.yml ps'
ssh <target> 'curl -I http://127.0.0.1:2053/'
ssh <target> 'ufw status numbered'
Read references/architecture.md if you need the full topology or nginx routing rationale.
Use scripts/open-panel-tunnel.sh and keep the panel SSH-only.
Default tunnel:
./scripts/open-panel-tunnel.sh --host root@example-vps --ssh-password 'host-password' --local-port 12053 --panel-port 2053
Then open http://127.0.0.1:12053.
If the tunnel fails with an immediate SSH disconnect, avoid parallel SSH sessions to the same host for a short period and retry the tunnel as a single connection after a brief pause.
Do not publish the panel in nginx. If the operator later wants a public panel, treat that as a separate hardening decision.
Use scripts/bootstrap-inbound.py against the tunneled panel URL.
Important detail:
TLS because nginx terminates TLS on 443XHTTP on loopbackPreferred flow:
python3 scripts/bootstrap-inbound.py \
--panel-url http://127.0.0.1:12053 \
--username admin \
--password 'secret' \
--public-domain vpn.example.com \
--backend-port 1234 \
--path /xhttp-keep-this-secret
The script prefers API automation but always prints a manual fallback checklist. Use references/manual-bootstrap.md if API endpoints drift or the panel UI has changed.
That fallback is UI-only. If server-side behavior needs to change, update the bundled scripts first and rerun them.
Use scripts/add-inbound-client.py against the tunneled panel URL.
This workflow is for adding one more client to an already working inbound without changing nginx, ports, or the existing secret path.
Preferred flow:
python3 scripts/add-inbound-client.py \
--panel-url http://127.0.0.1:12053 \
--username admin \
--password 'secret' \
--inbound-id 1
Behavior:
settings.clientsvless:// client URLIf --inbound-id is omitted, the script may auto-select the inbound only when the panel has exactly one inbound. Otherwise require the operator to pass the inbound ID explicitly.
The update workflow must stay conservative:
./scripts/update-stack.sh --host root@example-vps --ssh-password 'host-password'
This runs:
apt updateapt upgradedocker compose pulldocker compose up -d2096ufw rules for SSH, HTTP, and HTTPS onlyDo not switch this skill to apt full-upgrade unless the user explicitly asks for it.
Use these checks before assuming the deploy is broken:
ssh <target> 'ss -ltnp | egrep ":2053 |:2096 |:1234 |:443 |:80 "'ssh <target> 'docker compose -f /opt/3x-ui/docker-compose.yml ps'ssh <target> 'curl -I http://127.0.0.1:2053/'ssh <target> 'cat /opt/3x-ui/bootstrap.env'lsof -nP -iTCP:12053 -sTCP:LISTENInterpretation:
127.0.0.1:2053 and 127.0.0.1:2096 mean panel and sub server are correctly isolated127.0.0.1:1234 means the Xray backend inbound exists0.0.0.0:80 and 0.0.0.0:443 should belong to nginxcurl -I http://127.0.0.1:2053/ returning 404 is acceptable and proves the panel is respondinghttps://<domain>/ returning 401 is the expected nginx default for unmatched traffic--ssh-password instead of wrapping SSH manually.curl -fsSL https://get.docker.com -o get-docker.sh followed by sh ./get-docker.sh.dig, not from the operator workstation, before relying on DNS results.127.0.0.1; verify with ss -ltnp.127.0.0.1:2096; verify with ss -ltnp.80/443.127.0.0.1:1234.nginx default responses normal HTTP 401, not 444, so browsers receive a valid error page.ufw active and restricted to SSH, HTTP, and HTTPS ingress only./app/x-ui setting ...; the x-ui wrapper may not apply panel settings correctly inside the container.subListen=127.0.0.1 and verify that 2096 is not public.scripts/bootstrap-host.sh: install host packages, Docker, nginx, ACME, Compose stack, and nginx configscripts/ssh-with-password.sh: wrapper for ssh with optional plain-text password supportscripts/open-panel-tunnel.sh: open an SSH local port forward to the loopback-bound panelscripts/bootstrap-inbound.py: log in to 3X-UI and create one VLESS client plus inboundscripts/add-inbound-client.py: log in to 3X-UI, load an existing inbound, append one more client, and print the new vless:// URLscripts/update-stack.sh: run safe package and container updates remotelyreferences/architecture.md: deploy topology and nginx behaviorreferences/manual-bootstrap.md: panel UI fallback steps and field mapping