Reverse Proxy Setup¶
Validibot does not include a reverse proxy in the default Docker Compose configuration. You'll need to set up your own reverse proxy to handle TLS termination and route traffic to the application.
This follows the same approach as other Docker Compose projects like Sentry, PostHog, Paperless-ngx, and Immich.
Why no built-in proxy?¶
Most self-hosters already have a reverse proxy in their infrastructure (Traefik for other services, nginx on the host, Cloudflare Tunnel, etc.). Including one by default would conflict with existing setups and add complexity for users who don't need it.
Before you start¶
Make sure Validibot is running and accessible on its default port:
You should see a successful health check response before configuring the proxy.
Configuration requirements¶
Whichever proxy you choose, ensure:
- TLS termination — The proxy handles HTTPS; Validibot receives plain HTTP
- Proper headers — Forward
X-Forwarded-For,X-Forwarded-Proto, andHost - WebSocket support — Required for real-time updates (if using HTMx WebSocket extensions)
- Timeout settings — Increase timeouts for long-running validation uploads
Update your Validibot environment variables:
# .envs/.production/.Docker Compose/.django
DJANGO_ALLOWED_HOSTS=validibot.example.com
SITE_URL=https://validibot.example.com
DJANGO_SECURE_SSL_REDIRECT=False # Proxy handles TLS
Caddy¶
Caddy automatically obtains and renews TLS certificates via Let's Encrypt. It's the simplest option for most deployments.
Standalone (on host)¶
Install Caddy on your host system:
# Debian/Ubuntu
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
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
Create /etc/caddy/Caddyfile:
Start Caddy:
That's it. Caddy will automatically obtain a certificate and start serving HTTPS.
Docker container¶
Add Caddy as a service alongside Validibot. Create compose/production/caddy/Caddyfile:
Create docker-compose.caddy.yml:
services:
caddy:
image: caddy:2-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp" # HTTP/3
volumes:
- ./compose/production/caddy/Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
environment:
- DOMAIN=${VALIDIBOT_DOMAIN:-localhost}
networks:
- validibot
volumes:
caddy_data:
caddy_config:
networks:
validibot:
external: true
name: validibot_validibot
Run both compose files:
# Start Validibot
docker compose -f docker-compose.Docker Compose.yml up -d
# Start Caddy (after the validibot network exists)
VALIDIBOT_DOMAIN=validibot.example.com docker compose -f docker-compose.caddy.yml up -d
Important: The
caddy_datavolume stores certificates. Without persistence, Caddy requests new certificates on every restart, which can hit Let's Encrypt rate limits.
Traefik¶
Traefik integrates natively with Docker and discovers services via labels. It's a good choice if you're already using Traefik for other services.
Docker labels approach¶
The Docker Compose compose file includes a commented Traefik example. To enable it:
- Uncomment the Traefik service in
docker-compose.Docker Compose.yml - Add labels to the Django service
Or create a separate docker-compose.traefik.yml:
services:
traefik:
image: traefik:v3.0
restart: unless-stopped
command:
- "--api.dashboard=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
- "--certificatesresolvers.letsencrypt.acme.email=${ACME_EMAIL}"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- traefik_letsencrypt:/letsencrypt
networks:
- validibot
volumes:
traefik_letsencrypt:
networks:
validibot:
external: true
name: validibot_validibot
Add labels to the Django service in docker-compose.Docker Compose.yml:
services:
django:
labels:
- "traefik.enable=true"
- "traefik.http.routers.validibot.rule=Host(`validibot.example.com`)"
- "traefik.http.routers.validibot.entrypoints=websecure"
- "traefik.http.routers.validibot.tls.certresolver=letsencrypt"
- "traefik.http.services.validibot.loadbalancer.server.port=5000"
# Remove or comment out the ports mapping
# ports:
# - "8000:5000"
nginx¶
nginx is the most widely deployed reverse proxy. Use it if you're already familiar with nginx or need advanced configuration options.
Standalone (on host)¶
Install nginx:
Create /etc/nginx/sites-available/validibot:
server {
listen 80;
server_name validibot.example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name validibot.example.com;
# Certificates (use certbot or your own)
ssl_certificate /etc/letsencrypt/live/validibot.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/validibot.example.com/privkey.pem;
# SSL settings
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
# Proxy settings
location / {
proxy_pass http://127.0.0.1:8000;
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;
# Increase timeouts for large file uploads
proxy_read_timeout 300s;
proxy_send_timeout 300s;
client_max_body_size 100M;
}
# WebSocket support (if needed)
location /ws/ {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
}
Enable the site and obtain certificates:
sudo ln -s /etc/nginx/sites-available/validibot /etc/nginx/sites-enabled/
sudo certbot --nginx -d validibot.example.com
sudo systemctl reload nginx
Docker container¶
Create docker-compose.nginx.yml:
services:
nginx:
image: nginx:alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./compose/production/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./compose/production/nginx/certs:/etc/nginx/certs:ro
networks:
- validibot
networks:
validibot:
external: true
name: validibot_validibot
You'll need to manage certificates separately (e.g., with certbot on the host, or a sidecar container).
Cloudflare Tunnel¶
Cloudflare Tunnel is ideal for home servers or environments where you can't open ports. Traffic routes through Cloudflare's network, so you get DDoS protection and don't need to expose your server directly.
- Create a tunnel in the Cloudflare Zero Trust dashboard
- Install
cloudflaredon your host or run it as a container - Configure the tunnel to route to
http://localhost:8000
# docker-compose.cloudflared.yml
services:
cloudflared:
image: cloudflare/cloudflared:latest
restart: unless-stopped
command: tunnel run
environment:
- TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN}
networks:
- validibot
networks:
validibot:
external: true
name: validibot_validibot
Verifying the setup¶
After configuring your proxy:
-
Check HTTPS access:
-
Verify headers are forwarded:
-
Test file uploads: Submit a test validation to ensure large files work with your timeout settings.
Troubleshooting¶
CSRF verification failed¶
If you see "CSRF verification failed" errors after adding a proxy:
- Ensure
SITE_URLmatches your public domain exactly - Check that
X-Forwarded-Proto: httpsis being sent - Verify
DJANGO_ALLOWED_HOSTSincludes your domain
502 Bad Gateway¶
The proxy can't reach Validibot:
- Check Validibot is running:
docker compose ps - Verify the network connection:
docker network inspect validibot_validibot - Ensure the proxy is targeting the correct service name and port (
django:5000)
Certificate errors¶
- Caddy: Check the
caddy_datavolume persists between restarts - Traefik: Verify your ACME email is set and DNS points to your server
- nginx: Run
certbot renew --dry-runto test renewal