3x-ui-setup

Complete VPN server setup from scratch. Takes a fresh VPS (IP + root + password from hosting provider) through full server hardening and 3x-ui (Xray proxy panel) installation with VLESS Reality or VLESS TLS. Guides user through connecting via Hiddify client. Use when user mentions v2ray, xray, vless, 3x-ui, proxy server, vpn server, or wants to set up encrypted proxy access on a VPS. Designed for beginners — hand-holds through every step.

Safety Notice

This listing is from the official public ClawHub registry. Review SKILL.md and referenced 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 DavydenkovM/3x-ui-vpn-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:

  1. Server IP -- from provider email
  2. Root password -- from provider email
  3. Desired username -- for the new non-root account
  4. Server nickname -- for SSH config (e.g., myserver, vpn1)
  5. Has domain? -- if unsure, recommend "no" (Reality path, simpler)
  6. Domain name (if yes to #5) -- must already point to server IP

Local Mode -- ASK the user for:

  1. Desired username -- for the new non-root account
  2. Server nickname -- for future SSH access from user's computer (e.g., myserver, vpn1)
  3. Has domain? -- if unsure, recommend "no" (Reality path, simpler)
  4. 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:

StepRemote Mode (default)Local Mode
Step 1Generate SSH key on LOCAL machineSKIP -- user creates key on laptop later (Step 22)
Step 2ssh root@{SERVER_IP}Already on server. If not root: sudo su -
Steps 3-4Run on server via root SSHRun directly (already on server)
Step 5Install local public key on serverSKIP -- user sends .pub via SCP later (Step 22)
Step 6SSH test from LOCAL: ssh -i ... user@IPSwitch user: su - {username}, then sudo whoami
Step 7SKIP -- lockdown deferred to Step 22SKIP -- lockdown deferred to Step 22
Steps 8-11sudo on server via SSHsudo directly (no SSH prefix)
Step 12Write ~/.ssh/config on LOCALSKIP -- user does this from guide file (Step 22)
Step 13Verify via ssh {nickname}Run audit directly, skip SSH lockdown checks
Part 2ssh {nickname} "sudo ..."sudo ... directly (no SSH prefix)
Step 17AScanner via ssh {nickname} '...'Scanner runs directly (no SSH wrapper) -- see Step 17A for both commands
Panel accessVia SSH tunnelDirect: https://127.0.0.1:{panel_port}/{web_base_path}
Step 22Generate guide + fail2ban + lock SSHGenerate 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:

  1. Enter the current (provider) password
  2. Enter a new strong temporary password (this is temporary -- SSH keys will replace it)
  3. 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:

  1. Small unknown sites on neighboring IPs (e.g., shop.finn-auto.fi, portal.company.de) -- ideal, not filtered by DPI
  2. Regional/niche services (e.g., local hosting panels, small business sites) -- low profile
  3. 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:

  1. Login with generated credentials
  2. Inbounds -> Add Inbound
  3. Protocol: VLESS
  4. Port: 443
  5. Security: Reality
  6. Client Flow: xtls-rprx-vision
  7. Target & SNI: paste the best SNI from scanner
  8. Click "Get New Cert" for keys
  9. 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:

  1. The raw link (for reference)
  2. 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

| Параметр | Значение |
|----------|----------|
| URL | `https://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

```bash
# Создать ключ
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 << 'SSHEOF'

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

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

### Windows (PowerShell)

```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. Частые команды

```bash
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 refused | `ssh {nickname} "sudo x-ui status"` — перезапусти если остановлен |
| Permission denied (publickey) | Проверь путь и права ключа: `ls -la ~/.ssh/{nickname}_key` |
| Host key verification failed | `ssh-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}
   ```
4. Проверь вход по ключу (должен работать БЕЗ пароля):
   ```
   ssh -i ~/.ssh/{nickname}_key {username}@{SERVER_IP} "echo 'SSH key OK'"
   ```
5. Добавь SSH-конфиг:
   ```
   cat >> ~/.ssh/config << 'EOF'

   Host {nickname}
       HostName {SERVER_IP}
       User {username}
       IdentityFile ~/.ssh/{nickname}_key
       IdentitiesOnly yes
   EOF
   ```
6. Проверь подключение через конфиг: `ssh {nickname} "echo 'Config OK'"`

**Часть В: Финальная защита сервера (fail2ban + блокировка пароля)**
7. Установи fail2ban:
   ```
   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'
   ```
8. Заблокируй парольный вход и root:
   ```
   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'
   ```
9. Проверь что SSH-ключ всё ещё работает после блокировки:
   ```
   ssh {nickname} "grep -E 'PermitRootLogin|PasswordAuthentication' /etc/ssh/sshd_config && sudo systemctl status fail2ban --no-pager && echo 'Сервер полностью защищён!'"
   ```
10. Скажи пользователю: "Готово! 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)

  1. NEVER skip Step 6 (test login) -- user can be locked out permanently
  2. NEVER disable root before confirming new user works
  3. NEVER store passwords in files -- only display once to user
  4. If connection drops after password change -- reconnect, this is normal
  5. If Step 6 fails -- fix it before proceeding, keep root session open
  6. Generate SSH key BEFORE first connection -- more efficient workflow
  7. All operations after Step 6 use sudo -- not root
  8. Steps 7 and 9 are DEFERRED -- SSH lockdown and fail2ban are installed at the very end (Step 22)

Part 2 (VPN)

  1. NEVER expose panel to internet -- access only via SSH tunnel
  2. NEVER skip firewall configuration -- only open needed ports
  3. ALWAYS save panel credentials -- show them once, clearly
  4. ALWAYS verify connection works before declaring success
  5. Ask before every destructive or irreversible action
  6. ALWAYS generate guide file (Step 22) -- the user's single source of truth
  7. Lock SSH + install fail2ban LAST (Step 22) -- only after SSH key access is verified in BOTH modes
  8. NEVER leave password auth enabled after setup is complete

Troubleshooting

ProblemSolution
Connection drops after password changeNormal -- reconnect with new password
Permission denied (publickey)Check key path and permissions (700/600)
Host key verification failedssh-keygen -R {SERVER_IP} then reconnect
x-ui install failssudo apt update && sudo apt install -y curl tar
Panel not accessibleUse SSH tunnel: ssh -L {panel_port}:127.0.0.1:{panel_port} {nickname}
Reality not connectingWrong SNI -- re-run scanner, try different domain
Hiddify shows errorUpdate Hiddify to latest version, re-add link
"connection refused"Check x-ui is running: sudo x-ui status
Forgot panel passwordsudo 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 rebootRe-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

Session-Memory Enhanced

Session-Memory Enhanced v4.0 - 统一增强版。融合 session-memory + memu-engine 核心功能。特性:结构化提取 + 向量检索 + 不可变分片 + 三位一体自动化 + 多代理隔离 + AI 摘要 + 零配置启动。

Registry SourceRecently Updated
General

PRISM-GEN-DEMO

English: Retrieve, filter, sort, merge, and visualize multiple CSV result files from PRISM-Gen molecular generation/screening. Provides portable query-based...

Registry SourceRecently Updated
General

Video Pro by cza999

专业AI视频生成器,支持文本转高质量短视频,批量处理、多模板和高级自定义语音功能,适合创作者和企业。

Registry SourceRecently Updated
0133
cza999