3x-ui-setup

VPN Server Setup (3x-ui)

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "3x-ui-setup" with this command: npx skills add andyshaman/3x-ui-skill/andyshaman-3x-ui-skill-3x-ui-setup

VPN Server Setup (3x-ui)

Complete setup: fresh VPS from provider → secured server → working VPN with Hiddify client.

Workflow Overview

ЧАСТЬ 1: Настройка сервера Fresh VPS (IP + root + password) → Determine execution mode (remote or local) → Generate SSH key / setup access → Connect as root → Update system → Create non-root user + sudo → Install SSH key → TEST new user login (critical!) → Firewall (ufw) → Kernel hardening → Time sync + packages → Configure local ~/.ssh/config → ✅ Server secured

ЧАСТЬ 2: Установка VPN (3x-ui) → Install 3x-ui panel → Enable BBR (TCP optimization) → Disable ICMP (stealth) → Reality: scanner → create inbound → get link → Install Hiddify client → Verify connection → Generate guide file (credentials + instructions) → Install fail2ban + lock SSH (after key verified) → ✅ VPN working

PART 1: Server Hardening

Secure a fresh server from provider credentials to production-ready state.

Step 0: Collect Information

First, determine execution mode:

Где запущен Claude Code?

  • На локальном компьютере (Remote mode) -- настраиваем удалённый сервер через SSH

  • На самом сервере (Local mode) -- настраиваем этот же сервер напрямую

Remote Mode -- ASK the user for:

  • Server IP -- from provider email

  • Root password -- from provider email

  • Desired username -- for the new non-root account

  • Server nickname -- for SSH config (e.g., myserver , vpn1 )

  • Has domain? -- if unsure, recommend "no" (Reality path, simpler)

  • Domain name (if yes to #5) -- must already point to server IP

Local Mode -- ASK the user for:

  • Desired username -- for the new non-root account

  • Server nickname -- for future SSH access from user's computer (e.g., myserver , vpn1 )

  • Has domain? -- if unsure, recommend "no" (Reality path, simpler)

  • Domain name (if yes to #3) -- must already point to server IP

In Local mode, get server IP automatically:

curl -4 -s ifconfig.me

If user pastes the full provider email, extract the data from it.

Recommend Reality (no domain) for beginners. Explain:

  • Reality: works without domain, free, simpler setup, great performance

  • TLS: needs domain purchase (~$10/year), more traditional, allows fallback site

Execution Modes

All commands in this skill are written for Remote mode (via SSH). For Local mode, adapt as follows:

Step Remote Mode (default) Local Mode

Step 1 Generate SSH key on LOCAL machine SKIP -- user creates key on laptop later (Step 22)

Step 2 ssh root@{SERVER_IP}

Already on server. If not root: sudo su -

Steps 3-4 Run on server via root SSH Run directly (already on server)

Step 5 Install local public key on server SKIP -- user sends .pub via SCP later (Step 22)

Step 6 SSH test from LOCAL: ssh -i ... user@IP

Switch user: su - {username} , then sudo whoami

Step 7 SKIP -- lockdown deferred to Step 22 SKIP -- lockdown deferred to Step 22

Steps 8-11 sudo on server via SSH sudo directly (no SSH prefix)

Step 12 Write ~/.ssh/config on LOCAL SKIP -- user does this from guide file (Step 22)

Step 13 Verify via ssh {nickname}

Run audit directly, skip SSH lockdown checks

Part 2 ssh {nickname} "sudo ..."

sudo ... directly (no SSH prefix)

Step 17A Scanner via ssh {nickname} '...'

Scanner runs directly (no SSH wrapper) -- see Step 17A for both commands

Panel access Via SSH tunnel Direct: https://127.0.0.1:{panel_port}/{web_base_path}

Step 22 Generate guide + fail2ban + lock SSH Generate guide → SCP download → SSH key setup → fail2ban + lock SSH

IMPORTANT: In both modes, the end result is the same -- user has SSH key access to the server from their local computer via ssh {nickname} , password auth disabled, root login disabled.

Step 1: Generate SSH Key (LOCAL)

Run on the user's LOCAL machine BEFORE connecting to the server:

ssh-keygen -t ed25519 -C "{username}@{nickname}" -f ~/.ssh/{nickname}_key -N ""

Save the public key content for later:

cat ~/.ssh/{nickname}_key.pub

Step 2: First Connection as Root

ssh root@{SERVER_IP}

Handling forced password change

Many providers force a password change on first login. Signs:

  • Prompt: "You are required to change your password immediately"

  • Prompt: "Current password:" followed by "New password:"

  • Prompt: "WARNING: Your password has expired"

If this happens:

  • Enter the current (provider) password

  • Enter a new strong temporary password (this is temporary -- SSH keys will replace it)

  • You may be disconnected -- reconnect with the new password

If connection drops after password change -- this is normal. Reconnect:

ssh root@{SERVER_IP}

Step 3: System Update (as root on server)

apt update && DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt upgrade -y

Step 4: Create Non-Root User

useradd -m -s /bin/bash {username} echo "{username}:{GENERATE_STRONG_PASSWORD}" | chpasswd usermod -aG sudo {username}

Generate a strong random password. Tell the user to save it (needed for sudo). Then:

Verify

groups {username}

Step 5: Install SSH Key for New User

mkdir -p /home/{username}/.ssh echo "{PUBLIC_KEY_CONTENT}" > /home/{username}/.ssh/authorized_keys chmod 700 /home/{username}/.ssh chmod 600 /home/{username}/.ssh/authorized_keys chown -R {username}:{username} /home/{username}/.ssh

Step 6: TEST New User Login -- CRITICAL CHECKPOINT

DO NOT proceed without successful test!

Open a NEW connection (keep root session alive):

ssh -i ~/.ssh/{nickname}_key {username}@{SERVER_IP}

Verify sudo works:

sudo whoami

Must output: root

If this fails -- debug permissions, do NOT disable root login:

Check on server as root:

ls -la /home/{username}/.ssh/ cat /home/{username}/.ssh/authorized_keys

Fix ownership:

chown -R {username}:{username} /home/{username}/.ssh

Step 7: Lock Down SSH — DEFERRED

Оба режима: ПРОПУСКАЕМ. Блокировка SSH и установка fail2ban выполняются в самом конце (Step 22), после того как SSH-ключ проверен. Это предотвращает случайную блокировку доступа во время настройки.

Step 8: Firewall

sudo apt install -y ufw sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow ssh sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw --force enable sudo ufw status

Step 9: fail2ban — DEFERRED

Пропущен. fail2ban устанавливается в конце настройки (Step 22) вместе с блокировкой SSH, чтобы не заблокировать пользователя во время настройки.

Step 10: Kernel Hardening

sudo tee /etc/sysctl.d/99-security.conf << 'EOF' net.ipv4.conf.all.rp_filter = 1 net.ipv4.conf.default.rp_filter = 1 net.ipv4.conf.all.accept_redirects = 0 net.ipv4.conf.default.accept_redirects = 0 net.ipv4.conf.all.send_redirects = 0 net.ipv4.conf.default.send_redirects = 0 net.ipv4.tcp_syncookies = 1 net.ipv4.conf.all.log_martians = 1 net.ipv4.conf.default.log_martians = 1 net.ipv4.icmp_echo_ignore_broadcasts = 1 net.ipv4.conf.all.accept_source_route = 0 net.ipv4.conf.default.accept_source_route = 0 EOF sudo sysctl -p /etc/sysctl.d/99-security.conf

Step 11: Time Sync + Base Packages

sudo apt install -y chrony curl wget unzip net-tools sudo systemctl enable chrony

Step 12: Configure Local SSH Config

On the user's LOCAL machine:

cat >> ~/.ssh/config << 'EOF'

Host {nickname} HostName {SERVER_IP} User {username} IdentityFile ~/.ssh/{nickname}_key IdentitiesOnly yes EOF

Tell user: Теперь подключайся командой ssh {nickname} -- без пароля и IP.

Step 13: Final Verification

Connect as new user and run quick audit:

ssh {nickname}

Then on server:

sudo ufw status sudo sysctl net.ipv4.conf.all.rp_filter

Expected: ufw active, rp_filter = 1.

Note: SSH lockdown и fail2ban проверяются в конце (Step 22) после подтверждения работы SSH-ключа.

Часть 1 завершена. Базовая настройка сервера готова. Переходим к установке VPN.

PART 2: VPN Installation (3x-ui)

All commands from here use ssh {nickname} -- the shortcut configured in Part 1.

Step 14: Install 3x-ui

3x-ui install script requires root. Run with sudo:

ssh {nickname} "curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh -o /tmp/3x-ui-install.sh && echo 'n' | sudo bash /tmp/3x-ui-install.sh"

The echo 'n' answers "no" to port customization prompt -- a random port and credentials will be generated.

Note: Do NOT use sudo bash <(curl ...) -- process substitution does not work with sudo (file descriptors are not inherited).

IMPORTANT: Capture the output! It contains:

  • Generated username

  • Generated password

  • Panel port

  • Panel web base path

Extract and save these values. Show them to the user:

Данные панели 3x-ui (СОХРАНИ!): Username: {panel_username} Password: {panel_password} Port: {panel_port} Path: {web_base_path} URL: https://127.0.0.1:{panel_port}/{web_base_path} (через SSH-туннель)

Verify 3x-ui is running:

ssh {nickname} "sudo x-ui status"

If not running: ssh {nickname} "sudo x-ui start"

Panel port is NOT opened in firewall intentionally -- access panel only via SSH tunnel for security.

Step 14b: Enable BBR

BBR (Bottleneck Bandwidth and RTT) dramatically improves TCP throughput, especially on lossy links -- critical for VPN performance.

ssh {nickname} 'current=$(sysctl -n net.ipv4.tcp_congestion_control); echo "Current: $current"; if [ "$current" != "bbr" ]; then echo "net.core.default_qdisc=fq" | sudo tee -a /etc/sysctl.conf && echo "net.ipv4.tcp_congestion_control=bbr" | sudo tee -a /etc/sysctl.conf && sudo sysctl -p && echo "BBR enabled"; else echo "BBR already active"; fi'

Verify:

ssh {nickname} "sysctl net.ipv4.tcp_congestion_control net.core.default_qdisc"

Expected: net.ipv4.tcp_congestion_control = bbr , net.core.default_qdisc = fq .

Step 15: Disable ICMP (Stealth)

Makes server invisible to ping scans:

ssh {nickname} "sudo sed -i 's/-A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT/-A ufw-before-input -p icmp --icmp-type echo-request -j DROP/' /etc/ufw/before.rules && sudo sed -i 's/-A ufw-before-forward -p icmp --icmp-type echo-request -j ACCEPT/-A ufw-before-forward -p icmp --icmp-type echo-request -j DROP/' /etc/ufw/before.rules && sudo ufw reload"

Verify:

ping -c 2 -W 2 {SERVER_IP}

Expected: no response (timeout).

Step 16: Branch -- Reality or TLS

Path A: VLESS Reality (NO domain needed) -- RECOMMENDED

Go to Step 17A.

Path B: VLESS TLS (domain required)

Go to references/vless-tls.md .

Step 17A: Find Best SNI with Reality Scanner

Scan the server's /24 subnet to find real websites on neighboring IPs that support TLS 1.3, H2 (HTTP/2), and X25519 -- the exact stack Reality needs to mimic a genuine TLS handshake. The found domain becomes the masquerade target (SNI/dest), making VPN traffic indistinguishable from regular HTTPS to a neighboring site on the same hosting.

Why subnet scanning matters:

  • Reality reproduces a real TLS 1.3 handshake with the dest server -- the dest must support TLS 1.3 + H2 + X25519, or Reality won't work

  • RealiTLScanner (from the XTLS project) checks exactly this -- it only outputs servers compatible with Reality

  • DPI sees the SNI in TLS ClientHello and can probe the IP to verify the domain actually lives there

  • Popular domains (microsoft.com, google.com) are often on CDN IPs far from the VPS -- active probing catches this

  • A small unknown site on a neighboring IP (e.g., shop.finn-auto.fi ) is ideal -- nobody filters it, and it's in the same subnet

  • Do NOT manually pick an SNI without the scanner -- a random domain may not support TLS 1.3 or may be on a different IP range

Download and run Reality Scanner against the /24 subnet:

Remote mode (Claude Code on user's laptop):

ssh {nickname} 'ARCH=$(dpkg --print-architecture); case "$ARCH" in amd64) SA="64";; arm64|aarch64) SA="arm64-v8a";; ) SA="$ARCH";; esac && curl -sL "https://github.com/XTLS/RealiTLScanner/releases/latest/download/RealiTLScanner-linux-${SA}" -o /tmp/scanner && chmod +x /tmp/scanner && file /tmp/scanner | grep -q ELF || { echo "ERROR: scanner binary not valid for this architecture"; exit 1; }; MY_IP=$(curl -4 -s ifconfig.me); SUBNET=$(echo $MY_IP | sed "s/.[0-9]$/.0/24/"); echo "Scanning subnet: $SUBNET"; timeout 120 /tmp/scanner --addr "$SUBNET" 2>&1 | head -80'

Local mode (Claude Code on the VPS itself):

ARCH=$(dpkg --print-architecture); case "$ARCH" in amd64) SA="64";; arm64|aarch64) SA="arm64-v8a";; ) SA="$ARCH";; esac && curl -sL "https://github.com/XTLS/RealiTLScanner/releases/latest/download/RealiTLScanner-linux-${SA}" -o /tmp/scanner && chmod +x /tmp/scanner && file /tmp/scanner | grep -q ELF || { echo "ERROR: scanner binary not valid for this architecture"; exit 1; }; MY_IP=$(curl -4 -s ifconfig.me); SUBNET=$(echo $MY_IP | sed "s/.[0-9]$/.0/24/"); echo "Scanning subnet: $SUBNET"; timeout 120 /tmp/scanner --addr "$SUBNET" 2>&1 | head -80

Note: The commands are identical — Local mode simply runs without the ssh {nickname} wrapper since Claude Code is already on the VPS. GitHub releases use non-standard arch names (64 instead of amd64 , arm64-v8a instead of arm64 ). The case block maps them. The file | grep ELF check ensures the download is a real binary, not a 404 HTML page. Timeout is 120s because scanning 254 IPs takes longer than a single IP.

Choosing the best SNI from scan results

Every domain in the scanner output already supports TLS 1.3 + H2 + X25519 (the scanner filters for this). From those results, prefer domains in this order:

  • Small unknown sites on neighboring IPs (e.g., shop.finn-auto.fi , portal.company.de ) -- ideal, not filtered by DPI

  • Regional/niche services (e.g., local hosting panels, small business sites) -- low profile

  • Well-known tech sites (e.g., github.com , twitch.tv ) -- acceptable but less ideal

AVOID these as SNI:

  • www.google.com , www.microsoft.com , googletagmanager.com -- commonly blacklisted by DPI, people in Amnezia chats report these stop working

  • Any domain behind a CDN (Cloudflare, Akamai, Fastly) -- the IP won't match the CDN edge, active probing detects this

  • Domains that resolve to a completely different IP range than the VPS

How to verify a candidate SNI: The scanner output shows which IP responded with which domain. Pick a domain where the responding IP is in the same /24 as the VPS.

If scanner finds nothing or times out -- some providers (e.g., OVH) have sparse subnets. Try scanning a wider range /23 (512 IPs):

Remote mode:

ssh {nickname} 'MY_IP=$(curl -4 -s ifconfig.me); SUBNET=$(echo $MY_IP | sed "s/.[0-9]*$/.0/23/"); timeout 180 /tmp/scanner --addr "$SUBNET" 2>&1 | head -80'

Local mode:

MY_IP=$(curl -4 -s ifconfig.me); SUBNET=$(echo $MY_IP | sed "s/.[0-9]*$/.0/23/"); timeout 180 /tmp/scanner --addr "$SUBNET" 2>&1 | head -80

If still nothing, use www.yahoo.com as a last-resort fallback -- it supports TLS 1.3 and resolves to many IPs globally, and is less commonly filtered than google/microsoft. But always prefer a real neighbor from the scan -- a neighbor is guaranteed to be in the same subnet and verified by the scanner for TLS 1.3 + H2 + X25519 compatibility.

Save the best SNI for the next step.

Step 18A: Create VLESS Reality Inbound via API

Pre-check: Verify port 443 is not occupied by another service (some providers pre-install apache2/nginx):

ssh {nickname} "ss -tlnp | grep ':443 '"

If something is listening on 443, stop and disable it first (e.g., sudo systemctl stop apache2 && sudo systemctl disable apache2 ). Otherwise the VLESS inbound will silently fail to bind.

3x-ui has an API. Since v2.8+, the installer auto-configures SSL, so the panel runs on HTTPS. Use -k to skip certificate verification (self-signed cert on localhost).

First, get session cookie:

ssh {nickname} 'PANEL_PORT={panel_port}; curl -sk -c /tmp/3x-cookie -b /tmp/3x-cookie -X POST "https://127.0.0.1:${PANEL_PORT}/{web_base_path}/login" -H "Content-Type: application/x-www-form-urlencoded" -d "username={panel_username}&password={panel_password}"'

Generate keys for Reality:

ssh {nickname} "sudo /usr/local/x-ui/bin/xray-linux-* x25519"

This outputs two lines: PrivateKey = private key, Password = public key (confusing naming by xray). Save both.

Generate UUID for the client:

ssh {nickname} "sudo /usr/local/x-ui/bin/xray-linux-* uuid"

Generate random Short ID:

ssh {nickname} "openssl rand -hex 8"

Create the inbound:

ssh {nickname} 'PANEL_PORT={panel_port}; curl -sk -c /tmp/3x-cookie -b /tmp/3x-cookie -X POST "https://127.0.0.1:${PANEL_PORT}/{web_base_path}/panel/api/inbounds/add" -H "Content-Type: application/json" -d '"'"'{ "up": 0, "down": 0, "total": 0, "remark": "vless-reality", "enable": true, "expiryTime": 0, "listen": "", "port": 443, "protocol": "vless", "settings": "{"clients":[{"id":"{CLIENT_UUID}","flow":"xtls-rprx-vision","email":"user1","limitIp":0,"totalGB":0,"expiryTime":0,"enable":true}],"decryption":"none","fallbacks":[]}", "streamSettings": "{"network":"tcp","security":"reality","externalProxy":[],"realitySettings":{"show":false,"xver":0,"dest":"{BEST_SNI}:443","serverNames":["{BEST_SNI}"],"privateKey":"{PRIVATE_KEY}","minClient":"","maxClient":"","maxTimediff":0,"shortIds":["{SHORT_ID}"],"settings":{"publicKey":"{PUBLIC_KEY}","fingerprint":"chrome","serverName":"","spiderX":"/"}},"tcpSettings":{"acceptProxyProtocol":false,"header":{"type":"none"}}}", "sniffing": "{"enabled":true,"destOverride":["http","tls","quic","fakedns"],"metadataOnly":false,"routeOnly":false}", "allocate": "{"strategy":"always","refresh":5,"concurrency":3}" }'"'"''

If API approach fails -- tell user to access panel via SSH tunnel (Step 18A-alt).

Step 18A-alt: SSH Tunnel to Panel (manual fallback)

If API fails, user can access panel in browser:

ssh -L {panel_port}:127.0.0.1:{panel_port} {nickname}

Then open in browser: https://127.0.0.1:{panel_port}/{web_base_path} (browser will warn about self-signed cert -- accept it)

Guide user through the UI:

  • Login with generated credentials

  • Inbounds -> Add Inbound

  • Protocol: VLESS

  • Port: 443

  • Security: Reality

  • Client Flow: xtls-rprx-vision

  • Target & SNI: paste the best SNI from scanner

  • Click "Get New Cert" for keys

  • Create

Step 19: Get Connection Link

Get the client connection link from 3x-ui API:

ssh {nickname} 'PANEL_PORT={panel_port}; curl -sk -b /tmp/3x-cookie "https://127.0.0.1:${PANEL_PORT}/{web_base_path}/panel/api/inbounds/list" | python3 -c " import json,sys data = json.load(sys.stdin) for inb in data.get("obj", []): if inb.get("protocol") == "vless": settings = json.loads(inb["settings"]) stream = json.loads(inb["streamSettings"]) client = settings["clients"][0] uuid = client["id"] port = inb["port"] security = stream.get("security", "none") if security == "reality": rs = stream["realitySettings"] sni = rs["serverNames"][0] pbk = rs["settings"]["publicKey"] sid = rs["shortIds"][0] fp = rs["settings"].get("fingerprint", "chrome") flow = client.get("flow", "") link = f"vless://{uuid}@$(curl -4 -s ifconfig.me):{port}?type=tcp&security=reality&pbk={pbk}&fp={fp}&sni={sni}&sid={sid}&spx=%2F&flow={flow}#vless-reality" print(link) break "'

Show the link to the user. This is what they'll paste into Hiddify.

IMPORTANT: Terminal line-wrap fix. Long VLESS links break when copied from terminal. ALWAYS provide the link in TWO formats:

  • The raw link (for reference)

  • A ready-to-copy block with LLM cleanup prompt:

Скопируй всё ниже и вставь в любой LLM (ChatGPT, Claude) чтобы получить чистую ссылку:

Убери все переносы строк и лишние пробелы из этой ссылки, выдай одной строкой:

{VLESS_LINK}

Also save the link to a file for easy access:

ssh {nickname} "echo '{VLESS_LINK}' > ~/vpn-link.txt"

Tell the user: Ссылка также сохранена в файл ~/vpn-link.txt

Cleanup session cookie:

ssh {nickname} "rm -f /tmp/3x-cookie"

Step 20: Guide User -- Install Hiddify Client

Tell the user:

Теперь установи клиент Hiddify на своё устройство:

Android: Google Play -> "Hiddify" или https://github.com/hiddify/hiddify-app/releases iOS: App Store -> "Hiddify" Windows: https://github.com/hiddify/hiddify-app/releases (скачай .exe) macOS: https://github.com/hiddify/hiddify-app/releases (скачай .dmg) Linux: https://github.com/hiddify/hiddify-app/releases (.deb или .AppImage)

После установки:

  1. Открой Hiddify
  2. Нажми "+" или "Add Profile"
  3. Выбери "Add from clipboard" (ссылка уже скопирована)
  4. Или отсканируй QR-код (я могу его показать)
  5. Нажми кнопку подключения (большая кнопка в центре)
  6. Готово! Проверь IP на сайте: https://2ip.ru

Step 21: Verify Connection Works

After user connects via Hiddify, verify:

ssh {nickname} "sudo x-ui status && ss -tlnp | grep -E '443|{panel_port}'"

Step 22: Generate Guide File & Finalize SSH Access

This step generates a comprehensive guide file with all credentials and instructions, then finalizes SSH key-based access.

Remote Mode

22R-1: Generate guide file locally

Use the Write tool to create ~/vpn-{nickname}-guide.md on the user's local machine. Use the Guide File Template below, substituting all {variables} with actual values.

Tell user: Методичка сохранена в ~/vpn-{nickname}-guide.md — там все пароли, доступы и инструкции.

22R-2: Final lockdown — fail2ban + SSH

Verify SSH key access works:

ssh {nickname} "echo 'SSH key access OK'"

If successful, install fail2ban and lock SSH:

ssh {nickname} 'sudo apt install -y fail2ban && sudo tee /etc/fail2ban/jail.local << JAILEOF [DEFAULT] bantime = 1h findtime = 10m maxretry = 5

[sshd] enabled = true port = ssh filter = sshd logpath = /var/log/auth.log maxretry = 3 bantime = 24h JAILEOF sudo systemctl enable fail2ban && sudo systemctl restart fail2ban'

ssh {nickname} 'sudo sed -i "s/^#?PermitRootLogin./PermitRootLogin no/" /etc/ssh/sshd_config && sudo sed -i "s/^#?PasswordAuthentication./PasswordAuthentication no/" /etc/ssh/sshd_config && sudo systemctl restart sshd'

Verify lockdown + SSH still works:

ssh {nickname} "grep -E 'PermitRootLogin|PasswordAuthentication' /etc/ssh/sshd_config && sudo systemctl status fail2ban --no-pager -l && echo 'Lockdown OK'"

Local Mode

In Local mode, Claude Code runs on the server. SSH lockdown was skipped (Step 7), so password auth still works. The flow:

22L-1: Generate guide file on server

Use the Write tool to create /home/{username}/vpn-guide.md on the server. Use the Guide File Template below, substituting all {variables} with actual values.

22L-2: User downloads guide via SCP

Tell the user:

Методичка готова! Скачай её на свой компьютер. Открой НОВЫЙ терминал на своём ноутбуке и выполни:

scp {username}@{SERVER_IP}:~/vpn-guide.md ./

Пароль: {sudo_password}

Файл сохранится в текущую папку. Открой его -- там все пароли и инструкции.

Fallback: If SCP doesn't work (Windows without OpenSSH, network issues), show the full guide content directly in chat.

22L-3: User creates SSH key on their laptop

Tell the user:

Теперь создай SSH-ключ на своём компьютере. Есть два варианта:

Вариант А: Следуй инструкциям из раздела "SSH Key Setup" в методичке.

Вариант Б (автоматический): Установи Claude Code на ноутбуке (https://claude.ai/download) и скинь ему файл vpn-guide.md -- он сам всё настроит по инструкциям из раздела "Instructions for Claude Code".

После создания ключа отправь публичный ключ на сервер (следующий шаг).

22L-4: User sends public key to server via SCP

Tell the user:

Отправь публичный ключ на сервер (из терминала на ноутбуке):

scp /.ssh/{nickname}_key.pub {username}@{SERVER_IP}:/

Пароль: {sudo_password}

Wait for user confirmation before proceeding.

22L-5: Install key + verify

mkdir -p /home/{username}/.ssh cat /home/{username}/{nickname}_key.pub >> /home/{username}/.ssh/authorized_keys chmod 700 /home/{username}/.ssh chmod 600 /home/{username}/.ssh/authorized_keys chown -R {username}:{username} /home/{username}/.ssh rm -f /home/{username}/{nickname}_key.pub

Tell user to test from their laptop:

Проверь подключение с ноутбука: ssh -i ~/.ssh/{nickname}_key {username}@{SERVER_IP}

Должно подключиться без пароля.

Wait for user confirmation that SSH key works before proceeding!

22L-6: Final lockdown — fail2ban + SSH

Only after user confirms key-based login works!

Install fail2ban:

sudo apt install -y fail2ban sudo tee /etc/fail2ban/jail.local << 'EOF' [DEFAULT] bantime = 1h findtime = 10m maxretry = 5

[sshd] enabled = true port = ssh filter = sshd logpath = /var/log/auth.log maxretry = 3 bantime = 24h EOF sudo systemctl enable fail2ban sudo systemctl restart fail2ban

Lock SSH:

sudo sed -i 's/^#?PermitRootLogin./PermitRootLogin no/' /etc/ssh/sshd_config sudo sed -i 's/^#?PasswordAuthentication./PasswordAuthentication no/' /etc/ssh/sshd_config sudo systemctl restart sshd

Verify:

grep -E "PermitRootLogin|PasswordAuthentication" /etc/ssh/sshd_config sudo systemctl status fail2ban --no-pager

Expected: PermitRootLogin no , PasswordAuthentication no , fail2ban active.

Tell user to verify SSH still works from laptop:

Проверь, что SSH-ключ всё ещё работает: ssh {nickname} Если подключился — всё настроено!

22L-7: User configures SSH config

Tell the user:

Последний шаг! Добавь на ноутбуке в файл ~/.ssh/config:

Host {nickname} HostName {SERVER_IP} User {username} IdentityFile ~/.ssh/{nickname}_key IdentitiesOnly yes

Теперь подключайся просто: ssh {nickname}

22L-8: Delete guide file from server

rm -f /home/{username}/vpn-guide.md

Tell user: Методичка удалена с сервера. Убедись, что она сохранена на твоём компьютере.

Guide File Template

Generate this file using the Write tool, substituting all {variables} with actual values collected during setup.

Методичка VPN-сервера — {nickname}

Дата создания: {current_date}

1. Подключение к серверу

ПараметрЗначение
IP{SERVER_IP}
Пользователь{username}
Пароль sudo{sudo_password}
SSH-ключ~/.ssh/{nickname}_key
Быстрое подключениеssh {nickname}

2. Панель 3x-ui

ПараметрЗначение
URLhttps://127.0.0.1:{panel_port}/{web_base_path}
Логин{panel_username}
Пароль{panel_password}

Доступ через SSH-туннель:

ssh -L {panel_port}:127.0.0.1:{panel_port} {nickname}

Затем открой: https://127.0.0.1:{panel_port}/{web_base_path}

3. VPN-подключение

ПараметрЗначение
ПротоколVLESS Reality
Порт443
SNI{best_sni}
КлиентHiddify

Ссылка VLESS:

{VLESS_LINK}

4. Настройка SSH-ключа

Если у тебя ещё нет SSH-ключа, следуй инструкциям для своей ОС:

macOS / Linux

# Создать ключ
ssh-keygen -t ed25519 -C "{username}@{nickname}" -f ~/.ssh/{nickname}_key -N ""

# Отправить публичный ключ на сервер
scp ~/.ssh/{nickname}_key.pub {username}@{SERVER_IP}:~/

# Установить права
chmod 600 ~/.ssh/{nickname}_key

# Добавить в SSH-конфиг
cat >> ~/.ssh/config &#x3C;&#x3C; 'SSHEOF'

Host {nickname}
    HostName {SERVER_IP}
    User {username}
    IdentityFile ~/.ssh/{nickname}_key
    IdentitiesOnly yes
SSHEOF

# Проверить подключение
ssh {nickname}

Windows (PowerShell)

# Создать ключ
ssh-keygen -t ed25519 -C "{username}@{nickname}" -f $HOME\.ssh\{nickname}_key -N '""'

# Отправить публичный ключ на сервер
scp $HOME\.ssh\{nickname}_key.pub {username}@{SERVER_IP}:~/

# Добавить в SSH-конфиг
Add-Content $HOME\.ssh\config @"

Host {nickname}
    HostName {SERVER_IP}
    User {username}
    IdentityFile ~/.ssh/{nickname}_key
    IdentitiesOnly yes
"@

# Проверить подключение
ssh {nickname}

Примечание: chmod не нужен на Windows. SSH использует ACL автоматически.

5. Частые команды

ssh {nickname}                           # подключиться к серверу
ssh {nickname} "sudo x-ui status"        # статус панели
ssh {nickname} "sudo x-ui restart"       # перезапустить панель
ssh {nickname} "sudo x-ui log"           # логи
ssh {nickname} "sudo x-ui setting -reset" # сбросить пароль панели

Добавить нового VPN-клиента: Открой панель через SSH-туннель → Inbounds → ... → Add Client → отправь ссылку/QR.

6. Статус безопасности

ПараметрСтатус
Вход под rootОтключён
Вход по паролюОтключён
Файрвол UFWВключён (SSH, 80, 443)
fail2banВключён (3 попытки → бан 24ч)
Усиление ядраВключено (sysctl)
BBRВключён
ICMP (ping)Отключён

7. Решение проблем

ПроблемаРешение
Connection refusedssh {nickname} "sudo x-ui status" — перезапусти если остановлен
Permission denied (publickey)Проверь путь и права ключа: ls -la ~/.ssh/{nickname}_key
Host key verification failedssh-keygen -R {SERVER_IP} и переподключись
Панель недоступнаИспользуй SSH-туннель (см. раздел 2)
VPN не подключаетсяНеверный SNI или сервер лежит — проверь sudo x-ui log
Забыл пароль панелиssh {nickname} "sudo x-ui setting -reset"

8. Инструкции для Claude Code

Этот раздел для автоматической настройки через Claude Code на твоём ноутбуке. Скопируй всё ниже и вставь в Claude Code на ноутбуке.


ЗАДАЧА: Настроить SSH-ключ и завершить настройку безопасности VPN-сервера

Данные сервера:

  • SERVER_IP: {SERVER_IP}
  • USERNAME: {username}
  • PASSWORD: {sudo_password}
  • NICKNAME: {nickname}

Шаги:

Часть А: Установка sshpass (нужен для автоматической передачи пароля)

  1. Определи ОС и установи sshpass:
    • macOS: brew install hudochenkov/sshpass/sshpass
    • Linux (Debian/Ubuntu): sudo apt install -y sshpass

Часть Б: Создание и установка SSH-ключа 2. Создай SSH-ключ: ssh-keygen -t ed25519 -C "{username}@{nickname}" -f ~/.ssh/{nickname}_key -N "" 3. Установи ключ на сервере одной командой:

sshpass -p '{sudo_password}' ssh-copy-id -i ~/.ssh/{nickname}_key.pub -o StrictHostKeyChecking=accept-new {username}@{SERVER_IP}
  1. Проверь вход по ключу (должен работать БЕЗ пароля):
    ssh -i ~/.ssh/{nickname}_key {username}@{SERVER_IP} "echo 'SSH key OK'"
    
  2. Добавь SSH-конфиг:
    cat >> ~/.ssh/config &#x3C;&#x3C; 'EOF'
    
    Host {nickname}
        HostName {SERVER_IP}
        User {username}
        IdentityFile ~/.ssh/{nickname}_key
        IdentitiesOnly yes
    EOF
    
  3. Проверь подключение через конфиг: ssh {nickname} "echo 'Config OK'"

Часть В: Финальная защита сервера (fail2ban + блокировка пароля) 7. Установи fail2ban:

ssh {nickname} 'sudo apt install -y fail2ban &#x26;&#x26; sudo tee /etc/fail2ban/jail.local &#x3C;&#x3C; JAILEOF
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5

[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 24h
JAILEOF
sudo systemctl enable fail2ban &#x26;&#x26; sudo systemctl restart fail2ban'
  1. Заблокируй парольный вход и root:
    ssh {nickname} 'sudo sed -i "s/^#\?PermitRootLogin.*/PermitRootLogin no/" /etc/ssh/sshd_config &#x26;&#x26; sudo sed -i "s/^#\?PasswordAuthentication.*/PasswordAuthentication no/" /etc/ssh/sshd_config &#x26;&#x26; sudo systemctl restart sshd'
    
  2. Проверь что SSH-ключ всё ещё работает после блокировки:
    ssh {nickname} "grep -E 'PermitRootLogin|PasswordAuthentication' /etc/ssh/sshd_config &#x26;&#x26; sudo systemctl status fail2ban --no-pager &#x26;&#x26; echo 'Сервер полностью защищён!'"
    
  3. Скажи пользователю: "Готово! SSH-ключ настроен, fail2ban установлен, парольный вход отключён. Подключайся: ssh {nickname}"

Completion Summary

Print this summary for the user:

VPN-сервер полностью настроен и работает!

Подключение к серверу: Команда: ssh {nickname} IP: {SERVER_IP} Пользователь: {username} SSH-ключ: ~/.ssh/{nickname}_key Пароль sudo: {sudo_password}

Безопасность сервера: Root-вход отключён Парольный вход отключён Файрвол включён (порты: SSH, 80, 443) fail2ban защищает от брутфорса Ядро усилено (sysctl) BBR включён (TCP-оптимизация) ICMP отключён (сервер не пингуется)

Панель 3x-ui: URL: https://127.0.0.1:{panel_port}/{web_base_path} (через SSH-туннель) Login: {panel_username} Password: {panel_password}

VPN-подключение: Протокол: VLESS Reality Порт: 443 SNI: {best_sni}

Клиент: Hiddify -- ссылка добавлена

Управление (через SSH): ssh {nickname} # подключиться к серверу ssh {nickname} "sudo x-ui status" # статус панели ssh {nickname} "sudo x-ui restart" # перезапустить панель ssh {nickname} "sudo x-ui log" # логи

SSH-туннель к админке: ssh -L {panel_port}:127.0.0.1:{panel_port} {nickname} Затем открыть: https://127.0.0.1:{panel_port}/{web_base_path}

Добавить нового клиента: Открой админку -> Inbounds -> ... -> Add Client Скинь ссылку или QR-код другому человеку

Методичка: ~/vpn-{nickname}-guide.md Все пароли, инструкции и команды в одном файле

Critical Rules

Part 1 (Server)

  • NEVER skip Step 6 (test login) -- user can be locked out permanently

  • NEVER disable root before confirming new user works

  • NEVER store passwords in files -- only display once to user

  • If connection drops after password change -- reconnect, this is normal

  • If Step 6 fails -- fix it before proceeding, keep root session open

  • Generate SSH key BEFORE first connection -- more efficient workflow

  • All operations after Step 6 use sudo -- not root

  • Steps 7 and 9 are DEFERRED -- SSH lockdown and fail2ban are installed at the very end (Step 22)

Part 2 (VPN)

  • NEVER expose panel to internet -- access only via SSH tunnel

  • NEVER skip firewall configuration -- only open needed ports

  • ALWAYS save panel credentials -- show them once, clearly

  • ALWAYS verify connection works before declaring success

  • Ask before every destructive or irreversible action

  • ALWAYS generate guide file (Step 22) -- the user's single source of truth

  • Lock SSH + install fail2ban LAST (Step 22) -- only after SSH key access is verified in BOTH modes

  • NEVER leave password auth enabled after setup is complete

Troubleshooting

Problem Solution

Connection drops after password change Normal -- reconnect with new password

Permission denied (publickey) Check key path and permissions (700/600)

Host key verification failed ssh-keygen -R {SERVER_IP} then reconnect

x-ui install fails sudo apt update && sudo apt install -y curl tar

Panel not accessible Use SSH tunnel: ssh -L {panel_port}:127.0.0.1:{panel_port} {nickname}

Reality not connecting Wrong SNI -- re-run scanner, try different domain

Hiddify shows error Update Hiddify to latest version, re-add link

"connection refused" Check x-ui is running: sudo x-ui status

Forgot panel password sudo x-ui setting -reset

SCP fails (Windows) Install OpenSSH: Settings → Apps → Optional Features → OpenSSH Client

SCP fails (connection refused) Check UFW allows SSH: sudo ufw status , verify sshd running

BBR not active after reboot Re-check: sysctl net.ipv4.tcp_congestion_control -- re-apply if needed

x-ui CLI Reference

x-ui start # start panel x-ui stop # stop panel x-ui restart # restart panel x-ui status # check status x-ui setting -reset # reset username/password x-ui log # view logs x-ui cert # manage SSL certificates x-ui update # update to latest version

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

ll-feishu-audio

飞书语音交互技能。支持语音消息自动识别、AI 处理、语音回复全流程。需要配置 FEISHU_APP_ID 和 FEISHU_APP_SECRET 环境变量。使用 faster-whisper 进行语音识别,Edge TTS 进行语音合成,自动转换 OPUS 格式并通过飞书发送。适用于飞书平台的语音对话场景。

Archived SourceRecently Updated
General

test_skill

import json import tkinter as tk from tkinter import messagebox, simpledialog

Archived SourceRecently Updated
General

51mee-resume-profile

简历画像。触发场景:用户要求生成候选人画像;用户想了解候选人的多维度标签和能力评估。

Archived SourceRecently Updated
General

51mee-resume-parse

简历解析。触发场景:用户上传简历文件要求解析、提取结构化信息。

Archived SourceRecently Updated