SSL & Deployment
TLS Termination
Sockudo can terminate TLS directly using PEM-encoded certificates, powered by Rustls.
Configuration
{
"ssl": {
"enabled": true,
"cert_path": "/etc/ssl/certs/sockudo.pem",
"key_path": "/etc/ssl/private/sockudo-key.pem",
"redirect_http": true,
"http_port": 80
}
}
| Setting | Env Var | Default | Description |
|---|---|---|---|
enabled | SSL_ENABLED | false | Enable TLS |
cert_path | SSL_CERT_PATH | "" | Path to PEM certificate file |
key_path | SSL_KEY_PATH | "" | Path to PEM private key file |
passphrase | — | null | Key passphrase (reserved, not yet supported) |
ca_path | — | null | CA certificate (reserved, not yet supported) |
redirect_http | SSL_REDIRECT_HTTP | false | Start an HTTP redirect server |
http_port | SSL_HTTP_PORT | 80 | HTTP port for the redirect server |
HTTP to HTTPS Redirect
When redirect_http is enabled, Sockudo starts a secondary HTTP server that responds with 301 Permanent Redirect to the HTTPS equivalent:
http://example.com/app/my-key → https://example.com:6001/app/my-key
The redirect preserves the original host and URI path.
Certificate Requirements
- Certificates must be PEM-encoded (not DER or PKCS#12).
- Certificate and key must be in separate files.
- For Let's Encrypt, use
fullchain.pemascert_pathandprivkey.pemaskey_path.
Example: Let's Encrypt
SSL_ENABLED=true
SSL_CERT_PATH=/etc/letsencrypt/live/ws.example.com/fullchain.pem
SSL_KEY_PATH=/etc/letsencrypt/live/ws.example.com/privkey.pem
SSL_REDIRECT_HTTP=true
Unix Socket
Sockudo can listen on a Unix domain socket instead of (or in addition to) a TCP port. This is useful when running behind a reverse proxy on the same machine - Unix sockets avoid TCP overhead and provide file-based access control.
{
"unix_socket": {
"enabled": true,
"path": "/var/run/sockudo/sockudo.sock",
"permission_mode": "660"
}
}
| Setting | Env Var | Default | Description |
|---|---|---|---|
enabled | UNIX_SOCKET_ENABLED | false | Enable Unix socket listener |
path | UNIX_SOCKET_PATH | /var/run/sockudo/sockudo.sock | Socket file path (must be absolute) |
permission_mode | UNIX_SOCKET_PERMISSION_MODE | 660 | Octal file permissions |
Security
- The socket path must be absolute (starts with
/). - Directory traversal (
../) is rejected. - World-writable permissions (
xx7) generate a warning. - Using
/tmp/in production generates a warning - use/var/run/sockudo/instead.
Permission Mode Examples
| Mode | Meaning |
|---|---|
660 | Owner and group can read/write (recommended) |
750 | Owner full access, group read/execute |
700 | Owner only |
Reverse Proxy Setup
For production, running Sockudo behind a reverse proxy is recommended. The reverse proxy handles TLS, load balancing, and static assets while Sockudo focuses on WebSocket connections.
Nginx
upstream sockudo {
# TCP
server 127.0.0.1:6001;
# Or Unix socket
# server unix:/var/run/sockudo/sockudo.sock;
}
server {
listen 443 ssl http2;
server_name ws.example.com;
ssl_certificate /etc/letsencrypt/live/ws.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ws.example.com/privkey.pem;
location / {
proxy_pass http://sockudo;
proxy_http_version 1.1;
# WebSocket upgrade
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Forward client IP
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Timeouts
proxy_read_timeout 120s;
proxy_send_timeout 120s;
}
}
proxy_read_timeout higher than Sockudo's activity_timeout (default 120s) to prevent the proxy from closing idle connections before Sockudo does.Caddy
ws.example.com {
reverse_proxy 127.0.0.1:6001
}
Caddy handles WebSocket upgrades, TLS (automatic via Let's Encrypt), and HTTP/2 out of the box.
HAProxy
frontend ws_frontend
bind *:443 ssl crt /etc/haproxy/certs/ws.example.com.pem
default_backend sockudo_nodes
backend sockudo_nodes
balance leastconn
option http-server-close
# Sticky sessions for WebSocket
stick-table type ip size 200k expire 30m
stick on src
server node1 10.0.0.1:6001 check
server node2 10.0.0.2:6001 check
server node3 10.0.0.3:6001 check
Docker Deployment
Basic Docker Run
docker run -d \
--name sockudo \
-p 6001:6001 \
-p 9601:9601 \
-v ./config:/app/config:ro \
-e ADAPTER_DRIVER=redis \
-e DATABASE_REDIS_HOST=redis \
sockudo/sockudo:latest
Docker Compose
services:
sockudo:
image: sockudo/sockudo:latest
ports:
- "6001:6001"
- "9601:9601"
environment:
HOST: "0.0.0.0"
PORT: "6001"
ADAPTER_DRIVER: "redis"
DATABASE_REDIS_HOST: "redis"
SOCKUDO_DEFAULT_APP_ID: "my-app"
SOCKUDO_DEFAULT_APP_KEY: "my-key"
SOCKUDO_DEFAULT_APP_SECRET: "my-secret"
depends_on:
- redis
redis:
image: redis:7-alpine
ports:
- "6379:6379"
Docker Compose with Multiple Nodes
services:
sockudo-1:
image: sockudo/sockudo:latest
ports:
- "6001:6001"
environment:
ADAPTER_DRIVER: "redis"
DATABASE_REDIS_HOST: "redis"
SOCKUDO_DEFAULT_APP_ID: "my-app"
SOCKUDO_DEFAULT_APP_KEY: "my-key"
SOCKUDO_DEFAULT_APP_SECRET: "my-secret"
depends_on:
- redis
sockudo-2:
image: sockudo/sockudo:latest
ports:
- "6002:6001"
environment:
ADAPTER_DRIVER: "redis"
DATABASE_REDIS_HOST: "redis"
SOCKUDO_DEFAULT_APP_ID: "my-app"
SOCKUDO_DEFAULT_APP_KEY: "my-key"
SOCKUDO_DEFAULT_APP_SECRET: "my-secret"
depends_on:
- redis
redis:
image: redis:7-alpine
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- sockudo-1
- sockudo-2
Systemd Service
For bare-metal or VM deployments:
[Unit]
Description=Sockudo WebSocket Server
After=network.target redis.service
Wants=redis.service
[Service]
Type=simple
User=sockudo
Group=sockudo
WorkingDirectory=/opt/sockudo
ExecStart=/opt/sockudo/sockudo --config /opt/sockudo/config/config.json
Restart=always
RestartSec=5
# Environment
Environment=HOST=0.0.0.0
Environment=PORT=6001
Environment=ADAPTER_DRIVER=redis
# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/run/sockudo
# Resource limits
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
sudo systemctl enable sockudo
sudo systemctl start sockudo
LimitNOFILE high enough for your expected connection count. Each WebSocket connection uses one file descriptor.Build Features
Compile only the backends you need for smaller binaries:
# Minimal (local adapter only)
cargo build --release
# Redis adapter + PostgreSQL app manager
cargo build --release --features "redis,postgres"
# Full build with all backends
cargo build --release --features full
Available features:
| Feature | What It Enables |
|---|---|
redis | Redis adapter, cache, queue, rate limiter |
redis-cluster | Redis Cluster support for all backends |
nats | NATS adapter |
mysql | MySQL app manager |
postgres | PostgreSQL app manager |
dynamodb | DynamoDB app manager |
scylladb | ScyllaDB app manager |
sqs | SQS queue |
lambda | AWS Lambda webhook delivery |
full | All features |
Health Checks
Sockudo exposes health and metrics endpoints for monitoring:
| Endpoint | Port | Description |
|---|---|---|
/ | 6001 | Returns OK if the server is running |
/metrics | 9601 | Prometheus metrics |
Use these for load balancer health checks, Kubernetes liveness probes, or Docker healthchecks:
# Docker Compose
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:6001/"]
interval: 10s
timeout: 5s
retries: 3
# Kubernetes
livenessProbe:
httpGet:
path: /
port: 6001
initialDelaySeconds: 5
periodSeconds: 10
Production Checklist
- Use a horizontal adapter (
redis,redis-cluster, ornats) if running multiple nodes - Set
SOCKUDO_DEFAULT_APP_SECRETto a strong random value - Enable rate limiting with
redisbackend for cluster-wide enforcement - Set
RATE_LIMITER_API_TRUST_HOPSto match your proxy chain depth - Configure
allowed_originsper app to restrict WebSocket connections - Set
LimitNOFILE/ulimithigh enough for your connection target - Enable metrics and set up Prometheus scraping
- Use TLS - either via Sockudo directly or via a reverse proxy
- Configure webhook endpoints if you need server-side event notifications
- Set
ENVIRONMENT=productionfor production-appropriate defaults