# VPN transit via VM 213 and Vultr Date: 2026-09-27 to 2026-09-28 ## Goal Provide access from Russian residential/mobile networks to services restricted by Russian network filtering, while retaining the existing foreign egress on Vultr. ## Verified network facts - Test host `3060`: `46.39.251.163`, connection from Khimki / Iskratelecom. - Transit VM `213`: `5.172.178.213`, public egress observed as `5.172.178.65`; hosted in NUBES data centre. - Vultr addresses: primary `95.179.252.111`; secondary `104.238.177.67`. - `3060 -> 213`: ICMP approximately 3 ms, 0% loss. - `213 -> Vultr`: ICMP approximately 34 ms, 0% loss; HTTPS response returned in about 0.07-0.11 s. - Direct `213 -> Vultr` test file transfer: 10 MiB in 1.59 s, about 6.27 MiB/s / 50.2 Mbit/s. - Direct `3060 -> Vultr` test file transfer timed out / was throttled. - Direct `213 -> OVH proof endpoint`: 10 MiB in 1.18 s, about 8.5 MiB/s. - Direct access from `213` to YouTube and Telegram failed with `HTTP=000` and timeout/SSL errors, while OVH and Google returned HTTP 200. Therefore a foreign egress remains required for those services. ## Persistent changes on VM 213 - Created backup: - `/etc/nginx/sites-available/check.kube5s.ru.bak_vpn` - Modified: - `/etc/nginx/sites-available/check.kube5s.ru` - Added an Nginx `/ws` reverse-proxy location with: - upstream `https://95.179.252.111:443` - SNI `vipien.kube5s.ru` - upstream Host header `vipien.kube5s.ru` - WebSocket upgrade headers - 3600-second proxy timeouts - Ran `nginx -t` successfully and reloaded Nginx. - Existing unrelated Nginx warnings about duplicate `contracts.kube5s.ru` server names remained. ## Persistent/previously existing changes on Vultr The following configuration was read or used during validation: - `/etc/nginx/conf.d/vipien.conf`: TLS/WebSocket endpoint for `vipien.kube5s.ru`. - `/etc/v2ray-agent/xray/conf/08_VLESS_ws_inbound.json`: VLESS WebSocket inbound on `127.0.0.1:10086`, path `/ws`. - `/etc/systemd/system/hysteria-server.service`: Hysteria service was stopped and disabled; it was not changed in this work. - Xray service was confirmed active. - Nginx service was confirmed active. - Cloudflared tunnel configuration was inspected earlier, but it is not used by the final working route. - A temporary 10 MiB test file was created on Vultr and removed after testing. ## Temporary files on test VM 3060 The following temporary client files were created under `/tmp/xray-test/` for validation and are not repository files: - `client-cf.json` - `client-213.json` - `client-directip.json` - temporary log/test artifacts where applicable The files contained test Xray client configurations. They were used only to verify the route from `3060`; no permanent system service was installed there. ## Final tested route `client in Russia -> 5.172.178.213:443 -> Nginx WebSocket proxy -> 95.179.252.111:443 -> Xray -> Internet` Final test from `3060` through the route: - observed outbound IP: `95.179.252.111` - 10 MiB OVH download: 1.76-1.91 s - measured speed: approximately 5.5-6.0 MiB/s ## Final client parameters - Address: `5.172.178.213` - Port: `443` - UUID: existing UUID used by the Vultr Xray inbound - TLS SNI: `check.kube5s.ru` - WebSocket path: `/ws` - WebSocket Host: `vipien.kube5s.ru` The final direct-IP test used Xray 26.3.27. The client-side `allowInsecure` option was not used because this Xray version reports that the option was removed. ## Secondary Vultr IP Before removal, the Nginx upstream on VM 213 was switched from `104.238.177.67` to `95.179.252.111`. A post-switch end-to-end test succeeded, with outbound IP `95.179.252.111` and approximately 6.0 MiB/s. No Vultr IP deletion was performed in this work. The secondary address was only confirmed as no longer referenced by the transit configuration. ## Scope audit - No repository source/configuration files were edited before this record. - `git status` was clean before this documentation file was created. - This documentation file is the only workspace file created by the current documentation action. - Server-side files were changed on VM 213 and earlier on Vultr; temporary test files were also created on VM 3060. - No commit was created for this record. ## Important limitations The measurements prove the route worked at test time. They do not guarantee permanent availability: NUBES, Vultr, upstream providers, or network filtering policy can change independently. ## Later the same day: optimisation attempt and its outcome ### Automation created A reusable, idempotent tool was created outside this repository: ```text /home/naeel/nubes/HowTo/vpn-transit/vpn-setup.sh check | apply | verify | passthrough | verify-passthrough | client-config | rollback /home/naeel/nubes/HowTo/vpn-transit/client-config.json generated client config (chmod 600, contains UUID) /home/naeel/nubes/HowTo/vpn-transit/README.md description, measurements, rollback /home/naeel/nubes/HowTo/howto-vpn-transit-213-vultr-2026-09-28.md full report ``` Every change is preceded by a timestamped backup and followed by a config test (`nginx -t`, `xray run -test`) with automatic rollback on failure. ### Changes applied | Host | File | Change | Backup | |---|---|---|---| | 213 | `/etc/nginx/sites-available/check.kube5s.ru` | `proxy_buffering off;` added inside `location /ws`, marked `# vpn-transit: proxy_buffering off` | `check.kube5s.ru.bak.1790601681` | | Vultr | `/etc/v2ray-agent/xray/conf/00_log.json` | `loglevel`: `debug` → `warning` (log had grown to 76 MB), service restarted | `00_log.json.bak.1790601723` | | 213 | `/usr/local/sbin/vpn-transit-dnat.sh`, `/etc/systemd/system/vpn-transit-dnat.service` | DNAT `213:8443 → 95.179.252.111:443` plus FORWARD rules, enabled at boot | none (rules tagged `vpn-transit`) | ### Measurements after the changes - Outbound IP: `95.179.252.111` - Throughput: `5.6–7.3 MiB/s` (10 MiB in 1.4–1.9 s) - Per-connection latency: `0.23–0.37 s` - WebSocket upgrade success rate on 213: `14569 / 14573` (99.97%), one `upstream timed out` error ### Hypothesis that was disproved: mux `verify` compared the tunnel with and without `"mux": {"enabled": true, "concurrency": 8}`: | Mode | 10 MiB download | Connection behaviour | |---|---|---| | without mux | 7.32 MiB/s in 1.43 s | stable | | with mux | **0 B/s, failed** | after 4 requests connections hang for 15 s | Conclusion: mux is harmful in the `VLESS + WebSocket behind nginx` combination. It is excluded from the client config. The test remains in the script for re-checking on future Xray versions. ### Optimisation that could not be delivered: removing the second TLS layer The intended speed fix was to drop one TLS handshake (`client → 213`, then `213 → Vultr`) by forwarding TCP straight through to Vultr. - `ngx_stream_module.so` is absent on 213, so nginx cannot do SNI-based passthrough without installing `libnginx-mod-stream`. - Kernel-level DNAT on port 8443 was installed instead, but **does not work**: from outside, port 8443 returns `Connection refused` and the DNAT counter on 213 stays at 0 packets — traffic never reaches the machine. - Cause: the provider firewall in front of 213 exposes only ports 80 and 443. Measured from `3060`: `3001, 8080, 8443, 8766, 8767, 8888, 18080, 40229` are closed. - Therefore the second TLS layer can only be removed after the provider opens an additional port. The rules are already installed and would start working immediately once that happens. ### Errors made during this work 1. **Recommended `mux` before measuring it.** The recommendation was given as the main fix and was later disproved by measurement. Correct order: measure first, recommend after. 2. **Changed server configuration before measuring the benefit.** `proxy_buffering off` has no effect on a WebSocket connection after the `101 Switching Protocols` upgrade, and `loglevel` affects only log size. Neither change improves speed, so from the user's point of view nothing changed. 3. **Changed the client config to port 8443 before verifying the port was reachable from outside.** The config was regenerated back to port 443 immediately. ### Net result for the user Nothing changed for the client: address `5.172.178.213`, port `443`, SNI `check.kube5s.ru`, path `/ws` and the UUID are unchanged, and the previously used link still works. No client-side reconfiguration is required. The only actionable finding is client-side: the Xray log on Vultr contained **331** `connect: connection refused` to `127.0.0.1:45987`, i.e. the client requested a loopback address, plus Telegram advertises AAAA records while the tunnel is IPv4-only. The generated `client-config.json` addresses both (remote DNS, `queryStrategy: UseIPv4`), but the device itself was not modified. Separately: **10170** `reset by peer` entries to `157.240.0.13` (Meta infrastructure) are blocking by those sites, unrelated to the transit. ### Scope audit (this action) - Repository files changed: this document only. `git status` also showed unrelated pre-existing changes (`DEV_STAND/FullPipe/shturval.tf` deletion, `TMP/*` files) that were **not** touched or committed. - Server-side files changed: as listed in the table above. - Temporary test files on 3060: `/tmp/xray-test/*` (no permanent service installed).