Homelab: Shadowsocks + VLESS (TCP+TLS)
  • Home
  • Docker
  • Homelab: Shadowsocks + VLESS (TCP+TLS)
By Xin Lu profile image Xin Lu
2 min read

Homelab: Shadowsocks + VLESS (TCP+TLS)

This post documents a simple, stable setup I’m currently using on a VPS. No protocol comparison, no theory — just a configuration that works reliably on mobile networks (iOS) using a single public port.

Client (VLESS TCP+TLS)
        ↓
     V2Ray
        ↓
  Shadowsocks (internal)

Only port 443/tcp is exposed to the internet.

Prerequisites

  • A domain pointing to the VPS public IPv4 (DNS only, no CDN proxy)
  • Port 443/tcp open on the VPS

Generate VLESS UUID

cat /proc/sys/kernel/random/uuid

Use this UUID in:

  • V2Ray config
  • Client import URL

TLS Certificate (Certbot)

sudo apt update
sudo apt install -y certbot
sudo certbot certonly --standalone -d your.domain.com

Certificates are stored under:

/opt/ssl/live/your.domain.com/
  ├─ fullchain.pem
  └─ privkey.pem

If certbot used /etc/letsencrypt, copy once and keep paths consistent:

sudo mkdir -p /opt/ssl
sudo cp -a /etc/letsencrypt /opt/ssl/

Permissions:

sudo chmod 755 /opt/ssl
sudo chmod -R 755 /opt/ssl/live /opt/ssl/archive
sudo chmod 644 /opt/ssl/live/your.domain.com/fullchain.pem
sudo chmod 600 /opt/ssl/live/your.domain.com/privkey.pem

Docker Compose

~/shadowsocks/docker-compose.yml

services:
  shadowsocks:
    image: shadowsocks/shadowsocks-libev
    restart: unless-stopped
    expose:
      - "8388"
    command: >
      ss-server
      -s 0.0.0.0
      -p 8388
      -m chacha20-ietf-poly1305
      -k 'PASSWORD'

Notes:

  • Shadowsocks is internal only
  • V2Ray connects via Docker DNS name shadowsocks
  • Public surface = 443/tcp only

V2Ray Configuration

/opt/v2ray/config.json

{
  "log": { "loglevel": "warning" },
  "inbounds": [{
    "port": 443,
    "protocol": "vless",
    "settings": {
      "clients": [{ "id": "UUID-HERE" }],
      "decryption": "none"
    },
    "streamSettings": {
      "network": "tcp",
      "security": "tls",
      "tlsSettings": {
        "alpn": ["http/1.1", "h2"],
        "certificates": [{
          "certificateFile": "/opt/ssl/live/your.domain.com/fullchain.pem",
          "keyFile": "/opt/ssl/live/your.domain.com/privkey.pem"
        }]
      }
    }
  }],
  "outbounds": [{
    "protocol": "shadowsocks",
    "settings": {
      "servers": [{
        "address": "shadowsocks",
        "port": 8388,
        "method": "chacha20-ietf-poly1305",
        "password": "PASSWORD"
      }]
    }
  }]
}
sudo chmod 755 /opt/v2ray
sudo chmod 644 /opt/v2ray/config.json

Start Containers

cd ~/shadowsocks
docker compose up -d

Verify:

docker ps
docker logs v2ray --tail=30

Client Import (iOS / v2Box)

vless://[email protected]:443?encryption=none&security=tls&type=tcp&sni=your.domain.com&alpn=http/1.1

Import → Connect.

Certificate Renewal

Certbot enables automatic renewal via a systemd timer. After renewal, Docker containers must be restarted to load new certificates.

Deploy hook:

sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploy
sudo nano /etc/letsencrypt/renewal-hooks/deploy/restart-v2ray.sh
#!/bin/sh
docker restart v2ray >/dev/null 2>&1 || true
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/restart-v2ray.sh

Notes

  • VLESS over TCP+TLS behaves closer to normal HTTPS
  • More reliable than WS on mobile networks
  • Simple structure, easy to maintain

This setup is stable enough that I no longer touch it — this post is just a record.

By Xin Lu profile image Xin Lu
Updated on
Docker HomeLab