Shadowsocks proxy in Kubernetes
Published: 2026-06-02
Tor is slow. SOCKS5 has no encryption. Shadowsocks sits between the two: it's a
lightweight encrypted proxy protocol designed to be indistinguishable from random
HTTPS noise. The setup on this server runs shadowsocks-libev inside a
Kubernetes pod and exposes it as a NodePort.
Why Shadowsocks
Shadowsocks was originally built to bypass DPI (deep packet inspection) in China. The protocol:
- wraps all traffic in AES-256-GCM (or ChaCha20) encrypted frames
- looks like random TCP data — no recognizable handshake
- supports both TCP and UDP
- has clients for every platform (Android, iOS, Windows, macOS, Linux)
For a personal VPS, this means traffic between your device and the server is encrypted and not fingerprinted as "this is a proxy."
Kubernetes setup
Everything lives in a dedicated shadowsocks namespace. The password is stored
in a Secret and injected as an env variable:
yamlapiVersion: v1
kind: Secret
metadata:
name: ss-auth
namespace: shadowsocks
stringData:
password: "your-password-here"
The Deployment uses shadowsocks/shadowsocks-libev and passes flags as
args. TCP and UDP share the same port, which requires both containerPort
entries:
yamlapiVersion: apps/v1
kind: Deployment
metadata:
name: shadowsocks
namespace: shadowsocks
spec:
replicas: 1
selector:
matchLabels:
app: shadowsocks
template:
metadata:
labels:
app: shadowsocks
spec:
containers:
- name: ss-server
image: shadowsocks/shadowsocks-libev:latest
args:
- ss-server
- -s
- "0.0.0.0"
- -p
- "8388"
- -m
- aes-256-gcm
- -k
- $(SS_PASSWORD)
- -u # enable UDP relay
- --no-delay
- --reuse-port
- -v
ports:
- containerPort: 8388
hostPort: 8388
protocol: TCP
name: ss-tcp
- containerPort: 8388
hostPort: 8388
protocol: UDP
name: ss-udp
env:
- name: SS_PASSWORD
valueFrom:
secretKeyRef:
name: ss-auth
key: password
resources:
requests:
cpu: "10m"
memory: "32Mi"
limits:
cpu: "200m"
memory: "64Mi"
The Service exposes both protocols on the same port:
yamlapiVersion: v1
kind: Service
metadata:
name: shadowsocks
namespace: shadowsocks
spec:
type: NodePort
selector:
app: shadowsocks
ports:
- name: ss-tcp
port: 8388
targetPort: 8388
nodePort: 30388
protocol: TCP
- name: ss-udp
port: 8388
targetPort: 8388
nodePort: 30388
protocol: UDP
The -u flag and UDP relay
The -u flag enables UDP relay. Without it, Shadowsocks only proxies TCP.
DNS queries in SOCKS5 mode typically go over UDP, so without -u you'd need to
handle DNS separately. With -u, the client can send UDP packets through the
tunnel and DNS just works.
Choosing the cipher
aes-256-gcm is the default and works well on most hardware. On low-end VMs
without AES-NI acceleration, chacha20-ietf-poly1305 is faster:
bash# check AES-NI support
grep -m1 aes /proc/cpuinfo
If the flag is present, stick with aes-256-gcm. If not, switch to chacha20-ietf-poly1305.
The performance difference on a 1-core VPS can be 3–5x for bulk transfers.
Client setup
After deployment, connect with any Shadowsocks client:
Host: <your-vps-ip>
Port: 30388
Password: <value from secret>
Method: aes-256-gcm
On macOS/iOS Shadowrocket
works well. On Android v2rayNG supports
Shadowsocks natively. On Linux ss-local from the shadowsocks-libev package
exposes a local SOCKS5 port:
bashss-local -s <vps-ip> -p 30388 -m aes-256-gcm -k <password> -l 1080
# now set SOCKS5 proxy to 127.0.0.1:1080
curl --socks5 127.0.0.1:1080 https://ifconfig.me
Setup script
k0s-setup/20-setup-shadowsocks.sh generates a random password if none is set, creates the namespace, Secret, and Deployment, then prints connection details:
bashSS_PORT=30388 SS_METHOD=aes-256-gcm ssh root@server 'bash -s' < k0s-setup/20-setup-shadowsocks.sh
The script auto-detects the public IP and prints a ready-to-use config at the end.
Firewall
NodePort services open the port in kube-proxy's iptables rules, but the host
firewall also needs to allow it. On a k0s VM with ufw:
bashufw allow 30388/tcp
ufw allow 30388/udp
Without the UDP rule, ss-local -u (UDP relay) will silently fail and DNS
lookups will time out.
Resource usage
At idle, shadowsocks-libev uses ~2MB RAM and essentially no CPU. Under active
use (1 connected client), CPU stays under 10m on a shared vCPU. The limits set
in the Deployment (200m / 64Mi) are comfortable headroom.
Summary
| Setting | Value |
|---|---|
| Image | shadowsocks/shadowsocks-libev:latest |
| Cipher | aes-256-gcm |
| Port (NodePort) | 30388 |
| UDP relay | enabled (-u) |
| Password storage | Kubernetes Secret |
| Idle RAM | ~2 MB |