I spent part of today getting Nextcloud properly hardened behind Traefik + Cloudflare tunnel. Seemed simple. Two gotchas nearly wasted an hour.
The Stack
- Nextcloud 29 in Docker on a Proxmox LXC
- Traefik v3 as reverse proxy (separate LXC)
- Cloudflare Tunnel for public access — no open inbound ports
- Domain:
cloud.example.com
Problem 1: Nextcloud Doesn’t Know the Real Client IP
Out of the box, Nextcloud sees Traefik’s IP as every client’s IP. This breaks:
- Brute force protection (bans your proxy instead of attackers)
- Rate limiting
- Admin logs (every request looks like it’s from the same IP)
Fix — set trusted proxies via occ:
docker exec -u 33 nextcloud php occ config:system:set trusted_proxies 0 \
--value=192.168.1.x # your Traefik IP
# If you use Cloudflare Tunnel, add all Cloudflare CIDRs too:
docker exec -u 33 nextcloud php occ config:system:set trusted_proxies 1 \
--value=173.245.48.0/20
# ... (repeat for all Cloudflare CIDRs from cloudflare.com/ips)
docker exec -u 33 nextcloud php occ config:system:set forwarded_for_headers 0 \
--value=HTTP_X_FORWARDED_FOR
Enter fullscreen mode Exit fullscreen mode
Note the -u 33 — run as www-data, not root, or occ refuses.
Problem 2: HSTS Headers Disappear Through Cloudflare Tunnel
Traefik has a built-in stsSeconds middleware:
middlewares:
default-headers:
headers:
browserXssFilter: true
contentTypeNosniff: true
stsSeconds: 31536000
Enter fullscreen mode Exit fullscreen mode
This should send Strict-Transport-Security. It does — when traffic hits Traefik directly. But through Cloudflare Tunnel, Cloudflare terminates TLS before your Traefik sees it. Traefik receives HTTP from the tunnel daemon, so it treats the connection as non-HTTPS and suppresses STS.
Fix: Force it as an explicit custom response header instead of relying on stsSeconds:
middlewares:
default-headers:
headers:
browserXssFilter: true
contentTypeNosniff: true
stsSeconds: 31536000
stsIncludeSubdomains: true
customResponseHeaders:
Strict-Transport-Security: "max-age=31536000; includeSubDomains; preload"
Enter fullscreen mode Exit fullscreen mode
Then apply the middleware to your Nextcloud router:
routers:
nextcloud-https:
entryPoints:
- websecure
middlewares:
- default-headers
rule: Host(`cloud.example.com`)
service: nextcloud
tls:
certResolver: cloudflare
Enter fullscreen mode Exit fullscreen mode
Traefik hot-reloads dynamic config — no restart needed. Verify:
curl -I https://cloud.example.com/login | grep -i strict
# strict-transport-security: max-age=31536000; includeSubDomains; preload ✓
Enter fullscreen mode Exit fullscreen mode
Other Warnings to Clear
Run occ setupchecks to audit everything. Common ones:
Maintenance window not set — without this, heavy background jobs run during your peak hours:
docker exec -u 33 nextcloud php occ config:system:set \
maintenance_window_start --value=1 --type=integer
Enter fullscreen mode Exit fullscreen mode
No default phone region — affects phone number validation in profiles:
docker exec -u 33 nextcloud php occ config:system:set \
default_phone_region --value=CA
Enter fullscreen mode Exit fullscreen mode
Installing Apps via CLI
Skip the web UI for bulk installs. occ app:install pulls from the Nextcloud app store directly:
docker exec -u 33 nextcloud php occ app:install calendar
docker exec -u 33 nextcloud php occ app:install contacts
docker exec -u 33 nextcloud php occ app:install notes
docker exec -u 33 nextcloud php occ app:install tasks
docker exec -u 33 nextcloud php occ app:install deck # Kanban boards
docker exec -u 33 nextcloud php occ app:install spreed # Talk/video
docker exec -u 33 nextcloud php occ app:enable bruteforcesettings
Enter fullscreen mode Exit fullscreen mode
Each installs and enables in one step. Much faster than clicking through Settings → Apps.
Final Security Header Check
After all of the above:
curl -I https://cloud.example.com/login 2>&1 | grep -iE "strict|x-content|x-frame|x-xss"
Enter fullscreen mode Exit fullscreen mode
Expected output:
strict-transport-security: max-age=31536000; includeSubDomains; preload
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
x-xss-protection: 1; mode=block
Enter fullscreen mode Exit fullscreen mode
TL;DR
- Set
trusted_proxies+forwarded_for_headersvia occ — critical if you’re behind any proxy -
stsSecondsalone won’t work through Cloudflare Tunnel — usecustomResponseHeadersto force HSTS -
occ app:installbeats the web UI for batch app installs - Run
occ setupchecks— it tells you exactly what’s wrong
Nextcloud’s occ CLI does most heavy lifting without ever touching the admin UI. Once you’re comfortable with it, setup goes fast.
답글 남기기
댓글을 달기 위해서는 로그인해야합니다.