Reverse Proxy Setup Guide
Complete guide for running Borg Web UI behind a reverse proxy with Nginx, Traefik, Caddy, or Apache.
Borg Web UI must be served from a dedicated (sub)domain (e.g., backups.example.com). Subfolder deployments (e.g., example.com/borg/) are not supported.
Quick Start (Nginx)
The simplest reverse proxy configuration:
server {
listen 80;
server_name backups.example.com;
location / {
proxy_pass http://localhost:8081;
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/SSE support (required for real-time updates)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400;
}
}
Nginx Configurations
Basic (Root Domain)
server {
listen 80;
server_name backups.example.com;
location / {
proxy_pass http://localhost:8081;
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/SSE support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400;
}
}
With SSL/HTTPS (Let’s Encrypt)
server {
listen 443 ssl http2;
server_name backups.example.com;
ssl_certificate /etc/letsencrypt/live/backups.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/backups.example.com/privkey.pem;
# Strong SSL configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://localhost:8081;
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/SSE support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400;
}
}
# HTTP to HTTPS redirect
server {
listen 80;
server_name backups.example.com;
return 301 https://$host$request_uri;
}
With Authelia
server {
listen 443 ssl http2;
server_name backups.example.com;
# SSL configuration...
# Authelia authentication
include /path/to/authelia-authrequest.conf;
location / {
# Forward authenticated username to Borg UI
proxy_set_header X-Remote-User $remote_user;
proxy_set_header X-Forwarded-User $remote_user;
proxy_pass http://127.0.0.1:8081;
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/SSE support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400;
}
}
See Security Guide - Authelia for Authelia access_control configuration.
With Basic Auth (htpasswd)
server {
listen 443 ssl http2;
server_name backups.example.com;
# SSL configuration...
auth_basic "Borg Backups";
auth_basic_user_file /etc/nginx/.htpasswd;
location / {
# Forward authenticated username
proxy_set_header X-Remote-User $remote_user;
proxy_set_header X-Forwarded-User $remote_user;
proxy_pass http://127.0.0.1:8081;
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/SSE support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400;
}
}
Create users:
htpasswd -c /etc/nginx/.htpasswd username
Traefik Configuration
Use Docker labels with automatic Let’s Encrypt certificates:
services:
borg-ui:
image: ainullcode/borg-ui:latest
container_name: borg-web-ui
restart: unless-stopped
volumes:
- borg_data:/data
- borg_cache:/home/borg/.cache/borg
- /home/yourusername:/local:rw
environment:
- PUID=1000
- PGID=1000
labels:
- "traefik.enable=true"
- "traefik.http.routers.borg-ui.rule=Host(`backups.example.com`)"
- "traefik.http.routers.borg-ui.entrypoints=websecure"
- "traefik.http.routers.borg-ui.tls.certresolver=letsencrypt"
- "traefik.http.services.borg-ui.loadbalancer.server.port=8081"
networks:
- traefik
networks:
traefik:
external: true
volumes:
borg_data:
borg_cache:
Notes:
- Replace
backups.example.comwith your domain - The
traefiknetwork must be created and configured in your Traefik instance certresolver=letsencryptassumes you have a Let’s Encrypt resolver configured in Traefik
Caddy Configuration
Caddy provides automatic HTTPS with zero configuration:
backups.example.com {
reverse_proxy localhost:8081
}
Caddy automatically obtains and renews SSL certificates from Let’s Encrypt.
Proxy Authentication
Disable the built-in login screen and let your reverse proxy handle authentication:
environment:
- DISABLE_AUTHENTICATION=true # Disable built-in login screen
- PROXY_AUTH_HEADER=X-Forwarded-User # Header containing authenticated username (optional, default shown)
How it works:
- Borg UI reads the authenticated username from HTTP headers set by your reverse proxy
- Users are auto-created on first access as regular users (not admins)
- Admin must manually promote users via Settings > User Management
Supported headers (checked in order):
X-Forwarded-User(default, configurable viaPROXY_AUTH_HEADER)X-Remote-UserRemote-UserX-authentik-username(Authentik)
Supported authentication providers:
| Provider | Header |
|---|---|
| Authentik | X-authentik-username |
| Authelia | X-Remote-User |
| Keycloak | X-Forwarded-User |
| Cloudflare Access | Cf-Access-Authenticated-User-Email |
| Google IAP | X-Goog-Authenticated-User-Email |
| Azure AD | X-MS-CLIENT-PRINCIPAL-NAME |
Authentik Setup
docker-compose.yml:
services:
borg-ui:
image: ainullcode/borg-ui:latest
environment:
- DISABLE_AUTHENTICATION=true
- PROXY_AUTH_HEADER=X-authentik-username
networks:
- internal
# NO ports exposed - only accessible via proxy
authentik-proxy:
image: ghcr.io/goauthentik/proxy:latest
environment:
- AUTHENTIK_HOST=https://auth.example.com
- AUTHENTIK_INSECURE=false
- AUTHENTIK_TOKEN=your-outpost-token
ports:
- "8443:8443"
networks:
- internal
- external
labels:
- "authentik.enabled=true"
- "authentik.upstream=http://borg-ui:8081"
Authentik Application Setup:
- Create new application in Authentik
- Select Proxy Provider
- Set External URL:
https://backups.example.com - Set Internal URL:
http://borg-ui:8081 - Enable Forward auth (single application)
- Set authorization flow and user/group bindings
Cloudflare Access Setup
1. Create Cloudflare Access application:
- Application name: Borg Backups
- Session duration: 24 hours
- Add policies for users/groups
2. Configure Borg UI:
environment:
- DISABLE_AUTHENTICATION=true
- PROXY_AUTH_HEADER=Cf-Access-Authenticated-User-Email
3. Cloudflare Access forwards the user’s email in the Cf-Access-Authenticated-User-Email header.
See Security Guide - Proxy/OIDC Authentication for the full reference including user management, testing, troubleshooting, and switching between auth methods.
Docker Network Isolation
When using proxy authentication, you must ensure Borg UI is only accessible through your authenticated proxy.
Bind to localhost only:
ports:
- "127.0.0.1:8081:8081" # Only accessible via localhost
Block direct access with firewall rules:
# Block external access to port 8081
sudo ufw deny 8081
sudo ufw allow from 127.0.0.1 to any port 8081
Use Docker networks for isolation:
services:
borg-ui:
networks:
- internal
# NO ports exposed - only accessible via proxy network
reverse-proxy:
networks:
- internal
- external
ports:
- "443:443"
networks:
internal:
internal: true # No external access
external:
Why this matters: If Borg UI is directly accessible, anyone can spoof the authentication header (X-Forwarded-User) and impersonate any user. Your reverse proxy must be the only path to the application.
WebSocket and SSE Support
Borg Web UI uses WebSocket and Server-Sent Events (SSE) for real-time updates (backup progress, log streaming, etc.). Your reverse proxy must forward these correctly.
Required headers for Nginx configurations:
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400; # 24 hours - prevents premature disconnection
What these do:
proxy_http_version 1.1— Required for WebSocket upgradeUpgrade/Connectionheaders — Enable WebSocket handshakeproxy_read_timeout 86400— Prevents Nginx from closing long-lived connections (default 60s is too short)
Traefik and Caddy handle WebSocket/SSE automatically with no extra configuration.
Troubleshooting
WebSocket/SSE Timeouts
Symptom: Real-time updates (backup progress, log streaming) stop working or disconnect frequently.
Fix: Increase proxy_read_timeout in Nginx:
proxy_read_timeout 86400; # 24 hours
Headers Not Forwarded
Symptom: Proxy authentication doesn’t work; users see login screen despite DISABLE_AUTHENTICATION=true.
Fix: Verify your proxy sends the correct header:
# Test from the proxy server
curl -H "X-Forwarded-User: testuser" http://localhost:8081/api/auth/me
Check logs:
docker logs borg-web-ui 2>&1 | grep "proxy"
docker logs borg-web-ui 2>&1 | grep "X-Forwarded-User"
502 Bad Gateway
Symptom: Nginx returns 502 when accessing Borg UI.
Fix:
- Verify the container is running:
docker ps | grep borg - Check the port matches:
docker logs borg-web-ui | grep "listening" - If using Docker networks, ensure both containers are on the same network
- Check Nginx can reach the upstream:
curl http://localhost:8081
Mixed Content Warnings
Symptom: Browser console shows mixed content errors when using HTTPS.
Fix: Ensure you forward the protocol header:
proxy_set_header X-Forwarded-Proto $scheme;
Next Steps
- Configuration Guide - Environment variables and volume mounts
- Security Guide - Full security best practices and proxy auth reference
- Cache Configuration - Set up Redis for faster archive browsing