From getting a bare metal machine to "hardening complete, proxy usable, information archived". All steps are executed in order, with a gate before every destructive action.
Usage
/ray-vpsinit root@1.2.3.4 # 裸机全流程
/ray-vpsinit myhost --harden-only # 只做加固,不装代理栈
/ray-vpsinit myhost --proxy-only # 只装代理栈(机器已加固过)
Before starting, clarify three things (there are defaults; if the user doesn't answer, use the defaults):
- What is this machine's purpose? (proxy node / application server / relay) — determines whether to install the proxy stack
- Which SSH public key to use? (default
~/.ssh/id_ed25519.pub) - Where to record the panel credentials and port? (default: append to the user-specified credential file)
Step 0: Connectivity and inventory
ssh -o ConnectTimeout=10 <host> 'uname -m && cat /etc/os-release | head -2 && free -m | head -2 && df -h / | tail -1'
ssh <host> "ss -tlnp | awk 'NR>1{print \$4}'" # 已占端口清单
Record: architecture (amd64/arm64), distribution, memory, disk,set of occupied ports。
Port conflicts are the number one cause of deployment failure. Before choosing a port for any service, first check against this list.
Step 1: System hardening
Fixed order, each step idempotent (rerunning is harmless):
1a. SSH key login
ssh-copy-id -i <pubkey> <host> # 或手动追加 authorized_keys
ssh -i <key> <host> 'echo ok' # 必须先验证密钥能登
1b. SSH tightening (lockout protection gate)
Modify /etc/ssh/sshd_config(or drop-in /etc/ssh/sshd_config.d/99-harden.conf):
PasswordAuthentication no
PermitRootLogin prohibit-password
Iron rule:
- After modifying, first run
sshd -tsyntax check, reload only after it passes - Do not exit the current session, open a new connection to verify you can log in; only count it as complete after verification passes
- Roll back immediately if key verification fails; never disable password login when only the password is usable
1c. Kernel and network: BBR
cat >> /etc/sysctl.d/99-bbr.conf <<'EOF'
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
EOF
sysctl --system
sysctl net.ipv4.tcp_congestion_control # 确认输出 bbr
1d. Basic hygiene
timedatectl set-timezone UTC # 或用户指定时区
apt-get update && apt-get install -y unattended-upgrades && dpkg-reconfigure -f noninteractive unattended-upgrades
Add swap for small instances with memory ≤1G:
fallocate -l 1G /swapfile && chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
1e. Time synchronization: confirm timedatectl shows NTP active (the Reality handshake is sensitive to the clock).
Step 2: Proxy stack (3x-ui + VLESS+Reality)
If you have your own batch deployment script (the kind that deploys one machine with one command), you can use it directly for automation; the following manual sequence is only used when there is no ready-made script — butthe pitfall list must be checked for both paths。
2a. Choose port: from the preference sequence 443 → 2083 → 8443 → 2053 → 2087 → 2096 pick the first one not in the occupied list. Choose another high port for the panel port.
2b. Install 3x-ui (version pin)
echo 'y' | bash <(curl -Ls https://raw.githubusercontent.com/MHSanaei/3x-ui/v3.4.1/install.sh) v3.4.1
- The installation script is interactive, piping is unreliable — the strategy isinstall defaults, then force-reset credentials via CLI:
/usr/local/x-ui/x-ui setting -username <u> -password <p> -port <panel_port> -webBasePath /
systemctl restart x-ui
2c. Generate Reality keys (version pitfall)
xray x25519 output field names drift by version; when parsing, be compatible according to the table below:
| Xray version | private key field | public key field |
|---|---|---|
| old version | Private key: |
Public key: |
| v26.x | PrivateKey: |
Password (PublicKey): or Password: |
xray binary location:/usr/local/x-ui/bin/xray-linux-*(glob match, compatible with amd64/arm64).
2d. Inbound configuration (three field-tested pitfalls)
- SNI selection: the TLS certificate of the dest site must be ≤8192 bytes (hardcoded limit in xray-core).
www.microsoft.com's certificate already exceeds the limit, and the handshake fails on xray-core 26.x — usewww.apple.com/www.cloudflare.com/www.bing.com - client email must be non-empty and unique: 3x-ui 3.4.x silently discards clients with empty email, causing
clients: null, all handshakes fail with no error. Naming suggestion<remark>-<随机3字节hex> - Key fields:
flow: xtls-rprx-vision、fingerprint: chrome、shortIduse 4-byte hex, sniffingdestOverride: [http, tls, quic, fakedns]
Step 3: Firewall
Probe-based handling, don't assume what's on the machine:
ufw status 含 active → ufw allow <每个端口>/tcp && ufw reload
否则 iptables INPUT 默认策略是 DROP/REJECT → iptables -A INPUT -p tcp --dport <p> -j ACCEPT
两者都不是 → 跳过(云厂商安全组在外层,提醒用户去控制台开)
Ports to open = proxy port + panel port + SSH port.After opening, verify immediately from local, don't trust the green light in the panel.
Step 4: Verification and Handover
4a. End-to-end verification
curl -sk -o /dev/null -w '%{http_code}' --connect-timeout 10 https://<server>:<port>
Returning 400 or 200 both count as alive—Reality returns 400 to non-VLESS clients, this is expected behavior not a failure.
4b. Handover checklist(output to user, for sensitive items only give storage location, do not print plaintext):
主机: <ip> (<架构>/<发行版>/<内存>)
SSH: 密钥登录,密码已关,root 仅密钥
BBR: ✅
面板: https://<ip>:<panel_port> 凭证 → <凭证档位置>
节点: vless+reality :<port> SNI=<sni>
防火墙: <ufw/iptables/云安全组> 已放行 <端口列表>
待办: [ ] 订阅同步 [ ] 加入巡检清单(/ray-nodecheck)
Failure handling principles
- If any step fails, first lay out thelist of completed stepsand theraw error at the failure point, don't blindly retry
- For SSH configuration failures, prioritize restoring loginability, everything else comes after
- Installation script timeout (>180s) is most likely a network issue: switch to a GitHub mirror or proxy to pull, do not
--insecure