Skip to content

Nginx Reverse Proxy

Running CloakProbe behind Nginx provides SSL termination, load balancing, and additional security.

CloakProbe supports two deployment architectures:

1. Cloudflare Mode:
Internet → Cloudflare CDN → Nginx → CloakProbe (mode=cloudflare)
2. Nginx Mode (Direct):
Internet → Nginx → CloakProbe (mode=nginx)

For domains proxied through Cloudflare (orange cloud enabled).

/etc/cloakprobe/cloakprobe.toml
[server]
bind_address = "127.0.0.1"
port = 8080
mode = "cloudflare"
/etc/nginx/sites-available/ip.example.com
server {
listen 80;
server_name ip.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name ip.example.com;
# SSL certificates
ssl_certificate /etc/letsencrypt/live/ip.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ip.example.com/privkey.pem;
# SECURITY: Only allow Cloudflare IPs
include /etc/nginx/snippets/cloudflare-only.conf;
# Disable logging for privacy
access_log off;
error_log /dev/null crit;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
# Client IP (converted by real_ip_header in snippet)
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
}
}

Create a reusable snippet that restricts connections to Cloudflare IPs:

/etc/nginx/snippets/cloudflare-only.conf
# Cloudflare IPv4 ranges
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 131.0.72.0/22;
# Cloudflare IPv6 ranges
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2405:8100::/32;
set_real_ip_from 2a06:98c0::/29;
set_real_ip_from 2c0f:f248::/32;
# Trust CF-Connecting-IP as the real client IP
real_ip_header CF-Connecting-IP;
# DENY all non-Cloudflare IPs
allow 173.245.48.0/20;
allow 103.21.244.0/22;
# ... (add all ranges)
deny all;

Configuration 2: Direct Nginx (No Cloudflare)

Section titled “Configuration 2: Direct Nginx (No Cloudflare)”

For domains that bypass Cloudflare entirely.

/etc/cloakprobe/cloakprobe.toml
[server]
bind_address = "127.0.0.1"
port = 8080
mode = "nginx"
/etc/nginx/sites-available/ip-direct.example.com
server {
listen 80;
listen [::]:80;
server_name ip-direct.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name ip-direct.example.com;
ssl_certificate /etc/letsencrypt/live/ip-direct.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ip-direct.example.com/privkey.pem;
access_log off;
error_log /dev/null crit;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
# SECURITY: Always use $remote_addr
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
# SECURITY: Clear spoofed Cloudflare headers
proxy_set_header CF-Connecting-IP "";
proxy_set_header CF-IPCountry "";
proxy_set_header CF-Ray "";
proxy_set_header CF-Visitor "";
}
}

For dual-stack network detection. This instance only listens on IPv6.

/etc/cloakprobe/ipv6.toml
[server]
bind_address = "::1"
port = 8082
mode = "nginx"
region = "eu-central-v6"
/etc/nginx/sites-available/ip6.example.com
# NO IPv4 listener - IPv6 only!
server {
listen [::]:80 ipv6only=on;
server_name ip6.example.com;
return 301 https://$host$request_uri;
}
server {
listen [::]:443 ssl http2 ipv6only=on;
server_name ip6.example.com;
ssl_certificate /etc/letsencrypt/live/ip6.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ip6.example.com/privkey.pem;
access_log off;
error_log /dev/null crit;
location / {
proxy_pass http://[::1]:8082;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
# Clear spoofed headers
proxy_set_header CF-Connecting-IP "";
}
}
; Only AAAA record, no A record!
ip6.example.com. IN AAAA 2001:db8::1

Use multiple CloakProbe instances to detect client network capabilities:

EndpointProtocolPurpose
ip.example.comIPv4 + IPv6 (via CF)Primary, full features
ip4.example.comIPv4 onlyTest IPv4 connectivity
ip6.example.comIPv6 onlyTest IPv6 connectivity
async function detectNetworkStack() {
const results = { ipv4: null, ipv6: null };
try {
const v4 = await fetch('https://ip4.example.com/api/v1/json');
results.ipv4 = (await v4.json()).ip;
} catch (e) {}
try {
const v6 = await fetch('https://ip6.example.com/api/v1/json');
results.ipv6 = (await v6.json()).ip;
} catch (e) {}
return {
...results,
dualStack: results.ipv4 !== null && results.ipv6 !== null
};
}
Terminal window
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d ip.example.com

For Cloudflare Full (strict) mode, use Cloudflare Origin CA certificates.

In /etc/nginx/nginx.conf:

events {
worker_connections 4096;
use epoll;
multi_accept on;
}
upstream cloakprobe {
server 127.0.0.1:8080;
keepalive 32;
}
Terminal window
# Check if CloakProbe is running
sudo systemctl status cloakprobe
# Check if port is listening
ss -tlnp | grep 8080
Terminal window
# Test connection as nginx user
sudo -u www-data curl http://127.0.0.1:8080/healthz

Ensure real_ip_header CF-Connecting-IP is configured and the snippet is included.