This guide explains how to lock down a self-hosted Nextcloud instance so it’s only accessible to users connected via WireGuard VPN — even when the domain is proxied through Cloudflare.
Architecture #
| Component | Setup |
|---|---|
| Nextcloud | Docker |
| Reverse Proxy | Nginx (host) |
| VPN | WireGuard (wg-easy in Docker) |
| DNS/CDN | Cloudflare (Proxied) |
Why the Standard IP Whitelist Doesn’t Work #
If you try to use xCloud’s IP management panel to restrict access to only your server’s public IP, you’ll hit two problems:
1. Cloudflare masks real client IPs Nginx only sees Cloudflare’s IP ranges, not the actual user’s IP. So traditional allow/deny rules fail — everyone appears to come from Cloudflare.
2. VPN + Cloudflare causes a loopback When a VPN user accesses https://nextcloud.yourdomain.com, the request leaves the server, goes through Cloudflare, and loops back — so Nginx sees the server’s own public IP rather than the WireGuard IP.
This is also why adding the server’s public IP (e.g. 159.195.67.152) to the xCloud IP whitelist returns a “The selected IP address is invalid” error — the platform intentionally blocks self-referencing IPs at the UI level.
Solution #
The fix requires two things:
- Teach Nginx to trust Cloudflare and restore real client IPs
- Add proper allow/deny rules directly in the Nextcloud vHost config
Step 1 — Trust Cloudflare IPs in Nginx #
Create a Cloudflare trust config file so Nginx can read the actual client IP from the CF-Connecting-IP header:
echo "# Cloudflare Trusted IPs" | sudo tee /etc/nginx/conf.d/cloudflare.conf && \
curl -s https://www.cloudflare.com/ips-v4 | sed 's/^/set_real_ip_from /; s/$/;/' | sudo tee -a /etc/nginx/conf.d/cloudflare.conf && \
curl -s https://www.cloudflare.com/ips-v6 | sed 's/^/set_real_ip_from /; s/$/;/' | sudo tee -a /etc/nginx/conf.d/cloudflare.conf && \
echo "real_ip_header CF-Connecting-IP;" | sudo tee -a /etc/nginx/conf.d/cloudflare.conf
This file (/etc/nginx/conf.d/cloudflare.conf) tells Nginx:
- Treat all Cloudflare IP ranges as trusted proxies
- Use the
CF-Connecting-IPheader as the real client IP
Step 2 — Add Access Control to the Nextcloud vHost #
Open your Nextcloud site config:
sudo nano /etc/nginx/sites-available/YOUR-NEXTCLOUD-SITE
Inside the server {} block, add the following:
# WireGuard subnet (direct VPN access)
allow 10.42.44.0/24;
# Localhost
allow 127.0.0.1;
# Server public IP (CRITICAL)
# VPN → Cloudflare → Server loopback makes requests appear from this IP
allow YOUR_SERVER_PUBLIC_IP;
# Block everyone else
deny all;
Replace:
10.42.44.0/24→ your WireGuard subnet (check in wg-easy dashboard)YOUR_SERVER_PUBLIC_IP→ your server’s public IP (e.g.159.195.67.152)
Why allow the server’s own public IP? When a VPN user’s traffic routes through Cloudflare and loops back to the server, Nginx sees the server’s own public IP as the source. Allowing it here handles that case while still blocking all non-VPN internet traffic.
Step 3 — Apply the Changes #
Test and reload Nginx:
sudo nginx -t
sudo systemctl reload nginx
Result #
- Nextcloud is only accessible to WireGuard-connected users
- Cloudflare proxy stays enabled (SSL, DDoS protection, etc.)
- No public internet access leaks through
Maintenance Notes #
Grant temporary access to a non-VPN IP: Add a line before deny all; and reload:
allow x.x.x.x;
Cloudflare IP range updates: Cloudflare rarely changes their IP ranges, but if needed, rerun the Step 1 command to refresh the cloudflare.conf file.
Check your WireGuard subnet: Log into the wg-easy dashboard and confirm the subnet assigned to clients. The default is often 10.8.0.0/24 or 10.42.44.0/24 depending on your setup.
Notes for xCloud Users #
- The xCloud IP Management panel (Site → IP Management) is designed for external client IPs, not for VPN-based access control. For WireGuard restrictions, Nginx-level configuration (as described above) is the correct approach.
- xCloud sites run on Nginx — the config file is located at
/etc/nginx/sites-available/your-site-name. - After editing Nginx config files on an xCloud server, always run
sudo nginx -tbefore reloading to catch any syntax errors.

































