My Own Private Tunnel

Why I self-host my VPN on a small VPS — and how Xray, nginx, and Let's Encrypt became my personal, private tunnel to the internet.

Updated October 2026

VPNXraynginxLet's EncryptUbuntu
← Back to home

Why Self-Host?

Every commercial VPN is the same deal: you pay someone else to see all your traffic instead of your ISP. The privacy pitch sounds good until you realize you've just moved the trust problem one hop downstream. Their logging policy is a promise; your own server is a fact.

I had previously run my tunnel on a serverless edge platform. It worked — until the platform decided proxying wasn't allowed on their free tier and shut it down. That was the push I needed: rent a small VPS, own the whole stack, and never depend on someone else's terms of service again.

The goal was never to run a service for anyone else. This tunnel exists for exactly one household: mine. I built it for myself, I maintain it for myself, and the only user it will ever have is me.

Architecture

The design centers on one idea: the VPN traffic should look exactly like ordinary HTTPS. If a network observer sees my tunnel, they should see a browser visiting a website — nothing more.

                    ┌─────────────────────────┐
                    │      Small VPS (Ubuntu)   │
                    │                           │
  Internet ──443──► │  nginx (TLS termination)  │
                    │     ├── /  → this website  │
                    │     └── /<path> → proxy │
                    │                           │
                    │  Xray                     │
                    │     ├── VMess+WS ──► 443   │
                    │     └── VMess TCP ──► 8443 │
                    └─────────────────────────┘
                              ▲
                              │  Tailscale (admin)
                    ┌─────────┴─────────┐
                    │  Home (OpenWrt +   │
                    │  Mihomo client)    │
                    └───────────────────┘

Two inbound paths serve different needs. Port 443 carries VMess over WebSocket behind nginx, sharing the port with the website itself — TLS camouflage. Port 8443 carries VMess over raw TCP for clients and networks where the WebSocket path is unreliable. Both terminate in the same Xray instance.

Key Components

Xray-core

Xray is the heart of the tunnel. Each inbound is a small declaration of intent: which protocol, which transport, which credentials. VMess was chosen over newer protocols deliberately — my client (Mihomo on OpenWrt) supports it natively, and compatibility with the tools I already run beats chasing the latest protocol. The config shape looks like this (placeholders, not my real values):

{
  "inbounds": [
    {
      "port": 10000,
      "protocol": "vmess",
      "settings": { "clients": [{ "id": "<uuid>" }] },
      "streamSettings": {
        "network": "ws",
        "wsSettings": { "path": "/<secret-path>" }
      }
    }
  ]
}

nginx as the Front Door

nginx owns port 443 and terminates TLS. Normal web requests get the website; requests to the secret WebSocket path get reverse-proxied to Xray listening on localhost. From the outside, the whole server is just a boring little website — which it also is.

Let's Encrypt, Automated

Certificates come from Let's Encrypt and renew automatically — but Xray needs the certificate files too, not just nginx. A certbot deploy hook copies the renewed certs into place and reloads both services, so renewal never silently breaks the tunnel. The one time I forgot the hook, I learned exactly how quiet a dead VPN can be.

DNS Without the Orange Cloud

DNS is handled with plain A records pointing straight at the VPS — no proxying in front. A CDN in front of a VPN endpoint adds a middleman I don't need and latency I don't want. The domain resolves directly; TLS does the rest.

The Client: Mihomo on OpenWrt

On the home side, my OpenWrt router runs Mihomo (the engine behind the Nikki app) as a transparent proxy. The VPN lives in a selector group alongside direct and fallback options, so switching the whole household between "through the tunnel" and "direct" is one tap. Per-app and per-domain rules decide what actually uses the tunnel — streaming and browsing go through, local devices and latency-sensitive traffic stay direct.

Challenges & Solutions

The Platform Shutdown

The previous serverless setup died overnight when the provider enforced their ToS against proxying. Rebuilding on a VPS took an afternoon: install Xray, configure nginx, issue certs, point DNS. The lesson wasn't technical — it was architectural: never build on a platform whose rules can delete your infrastructure.

Port 443 Was Already Taken

The website already lived on 443. Rather than choosing between the site and the tunnel, nginx routes by path — the website and the VPN coexist on one port, one certificate, one public face. Path-based routing on a single TLS port is the simplest form of camouflage there is.

Client Capability Gaps

My Mihomo build doesn't support uTLS fingerprinting or VLESS+REALITY, so those options were off the table no matter how fashionable they are. VMess+WS+TLS works everywhere, including through the DPI boxes of local ISPs, and that mattered more than protocol novelty. Design for the client you have, not the client you wish you had.

Certificate Renewal Broke the Tunnel

Certbot renewed the certificate for nginx just fine — and Xray kept serving the expired one until I noticed. The deploy hook that syncs certs to Xray and reloads it closed that gap permanently. Automate the boring parts or they will bite you at 2 AM.

ISP Interference

Some local mobile networks throttle or block unusual traffic patterns. VMess over WebSocket+TLS survives because it genuinely is HTTPS-shaped — standard port, standard handshake, standard framing. When in doubt, look boring.

Lessons

What's Next

The tunnel is stable and does exactly what I need. Possible future work: WireGuard for a dedicated site-to-site link between the VPS and home (lower overhead than VMess for bulk transfer), and tighter integration with the home proxy rules so more traffic types get sensible defaults. But there's no urgency — the current setup hums along, and I check on it the way you check on a good appliance: rarely, and with satisfaction.


I built this for myself — one VPS, one tunnel, zero trust required.

← Back to home