TLS and Reverse Proxy
Status: Complete
How to set up HTTPS and reverse proxy for a production Chatalot instance.
Why TLS Matters
TLS (HTTPS) is essential for any Chatalot instance accessible beyond localhost:
- WebSocket security. Browsers require
wss://(secure WebSocket) on HTTPS pages. Without TLS, real-time messaging will fail. - WebRTC voice/video. Browsers block microphone and camera access on insecure origins. Voice and video calls will not work without TLS.
- End-to-end encryption integrity. While messages are E2E encrypted, TLS protects authentication tokens, metadata, and the key exchange from network-level attacks.
- Browser warnings. Modern browsers display prominent security warnings on HTTP sites.
Note:
localhostand127.0.0.1are exempt from these restrictions and work fine without TLS during development.
Architecture
In a typical production setup:
The reverse proxy handles TLS termination and forwards requests to the Chatalot container. Both HTTP API requests and WebSocket connections at /ws must be proxied.
Nginx
Installation
# Debian/Ubuntu
sudo apt install nginx
# Fedora/RHEL
sudo dnf install nginx
# Arch Linux
sudo pacman -S nginx
Configuration
Create /etc/nginx/sites-available/chatalot:
server {
listen 80;
server_name chat.example.com;
# Redirect HTTP to HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name chat.example.com;
ssl_certificate /etc/letsencrypt/live/chat.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/chat.example.com/privkey.pem;
# TLS settings
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# Max upload size (match MAX_FILE_SIZE_MB + overhead)
client_max_body_size 110M;
# API and static files
location / {
proxy_pass http://127.0.0.1:8080;
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;
}
# WebSocket endpoint
location /ws {
proxy_pass http://127.0.0.1:8080/ws;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Keep WebSocket connections alive
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}
}
Enable the site:
sudo ln -s /etc/nginx/sites-available/chatalot /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Important: The
proxy_read_timeout 86400son the/wslocation is critical. Without it, nginx will close idle WebSocket connections after 60 seconds, causing constant reconnections.
Caddy
Caddy is an excellent choice because it handles TLS certificates automatically via Let's Encrypt.
Installation
# Debian/Ubuntu
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy
Configuration
Edit /etc/caddy/Caddyfile:
chat.example.com {
reverse_proxy localhost:8080
# Request body limit for file uploads
request_body {
max_size 110MB
}
}
Caddy automatically: - Obtains and renews Let's Encrypt certificates - Redirects HTTP to HTTPS - Handles WebSocket upgrades (no special configuration needed)
Start Caddy:
That is all. Caddy's WebSocket handling works out of the box with no additional configuration.
Traefik
If you are using Traefik as a Docker-based reverse proxy, create docker-compose.override.yml:
services:
chatalot:
labels:
- "traefik.enable=true"
- "traefik.docker.network=web"
- "traefik.http.routers.chatalot.rule=Host(`chat.example.com`)"
- "traefik.http.routers.chatalot.entrypoints=websecure"
- "traefik.http.routers.chatalot.tls=true"
- "traefik.http.services.chatalot.loadbalancer.server.port=8080"
networks:
- web
networks:
web:
external: true
The deploy script (scripts/deploy.sh) can generate this automatically if you set DEPLOY_DOMAIN:
Let's Encrypt
With Certbot (for nginx)
# Install certbot
sudo apt install certbot python3-certbot-nginx
# Obtain certificate
sudo certbot --nginx -d chat.example.com
# Auto-renewal is configured automatically
# Verify with:
sudo certbot renew --dry-run
With Caddy
Caddy handles Let's Encrypt automatically. No additional setup is needed.
With acme.sh
# Install acme.sh
curl https://get.acme.sh | sh
# Issue certificate
acme.sh --issue -d chat.example.com --webroot /var/www/html
# Install certificate
acme.sh --install-cert -d chat.example.com \
--key-file /etc/ssl/private/chatalot.key \
--fullchain-file /etc/ssl/certs/chatalot.pem \
--reloadcmd "systemctl reload nginx"
Self-Signed Certificates (Testing Only)
For local testing or development, you can generate a self-signed certificate:
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/ssl/private/chatalot-selfsigned.key \
-out /etc/ssl/certs/chatalot-selfsigned.crt \
-subj "/CN=chat.local"
Warning: Self-signed certificates will trigger browser security warnings. They should never be used in production. For testing, consider using mkcert which installs a local CA that browsers trust.
Verifying Your Setup
After configuring TLS, verify everything works:
# Check TLS certificate
curl -I https://chat.example.com
# Check health endpoint
curl https://chat.example.com/api/health
# Test WebSocket (requires wscat: npm install -g wscat)
wscat -c wss://chat.example.com/ws
Next Step
For the full Docker Compose reference, see Docker Deployment.