If you’re self-hosting SafeLine WAF and exposing internal web services to the internet via FRP, you may notice that all incoming requests appear to come from 127.0.0.1
or your internal proxy server. That’s a problem — you lose visibility of the real attacker IPs.
Starting from version 7.5.0 (released Jan 2, 2025), SafeLine now supports proxy_protocol
, which means you can forward real client IP addresses through FRP v2 and have them properly recorded in the SafeLine dashboard.
In this tutorial, we’ll show you how to:
- Configure FRP client (
frpc
) to enable proxy_protocol - Patch SafeLine’s Nginx config to trust real IP headers
- Batch-patch all site configs via a script (with whitelist support)
- Test and verify the results
Scenario Setup
We have the following setup:
- SafeLine and FRP client are deployed on the same internal server:
192.168.2.103
- We want to expose multiple HTTPS subdomains via FRP to the outside world
- Our goal: Make SafeLine log the real external IPs of incoming requests
Step 1: Configure FRP Client (frpc.toml
)
In your FRP client config, just add transport.proxyProtocolVersion = "v2"
to each HTTPS proxy.
root@safeline:~# cat /etc/frp/frpc.toml
[[proxies]]
name = "web1"
type = "https"
localIP = "192.168.2.103"
localPort = 443
subdomain = "web1"
transport.proxyProtocolVersion = "v2"
[[proxies]]
name = "web2"
type = "https"
localIP = "192.168.2.103"
localPort = 443
subdomain = "web2"
transport.proxyProtocolVersion = "v2"
Save and restart the FRP client.
Step 2: Modify the proxy_params Configuration File
SafeLine’s web UI doesn’t currently expose proxy_protocol settings — but you can enable it manually by editing the Nginx config.
Edit this file:
/data/safeline/resources/nginx/proxy_params
Replace contents with:
# Internal IP ranges
set_real_ip_from 192.168.0.0/16; # Covers 192.168.0.0 to 192.168.255.255
set_real_ip_from 172.16.0.0/12; # Covers 172.16.0.0 to 172.31.255.255
set_real_ip_from 10.0.0.0/8; # Covers 10.0.0.0 to 10.255.255.255
# Public IP address of the FRPS server
set_real_ip_from YOUR.FRPS.SERVER.IP;
real_ip_header proxy_protocol;
# Additional proxy headers
proxy_set_header X-Real-IP $realip_remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_headers_hash_max_size 1024;
proxy_headers_hash_bucket_size 128;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_hide_header X-Powered-By;
# Add conditional logic to support both proxy_protocol and non-proxy_protocol connections
set $proxy_x_forwarded_for $proxy_add_x_forwarded_for;
set $proxy_x_real_ip $remote_addr;
if ($proxy_protocol_addr) {
set $proxy_x_forwarded_for "$proxy_protocol_addr, $proxy_x_forwarded_for";
set $proxy_x_real_ip $proxy_protocol_addr;
}
proxy_set_header X-Forwarded-For $proxy_x_forwarded_for;
proxy_set_header X-Real-IP $proxy_x_real_ip;
Step 3: Create the config-proxy_protocol.sh Script
Create a shell script at:
vim /data/safeline/resources/nginx/sites-enabled/config-proxy_protocol.sh
This script will loop through all SafeLine backend config files (e.g., IF_backend_*) and enable or disable proxy_protocol support for each domain. You can also define a whitelist of subdomains that should be excluded.
We’ll cover the script content next.
Create a script at:
/data/safeline/resources/nginx/sites-enabled/config-proxy_protocol.sh
Script contents:
#!/bin/bash
main_domain="example.com"
whitelist_subdomains=("admin" "monitor")
show_help() {
echo "Usage: $0 [true|false]"
echo " true - Add proxy_protocol support"
echo " false - Remove proxy_protocol support"
exit 0
}
case "$1" in
true|false) action=$1 ;;
""|"?") show_help ;;
*) echo "Invalid arg. Use true or false."; exit 1 ;;
esac
modify_config() {
local file=$1
local add_proxy=$2
local changed=false
while IFS= read -r line; do
if [[ $line =~ listen.*:443 ]]; then
if $add_proxy && [[ ! $line =~ proxy_protocol ]]; then
line="${line/ssl http2;/ssl http2 proxy_protocol;}"
changed=true
elif ! $add_proxy && [[ $line =~ proxy_protocol ]]; then
line="${line/ proxy_protocol/}"
changed=true
fi
fi
echo "$line"
done < "$file" > "${file}.tmp"
if $changed; then
mv "${file}.tmp" "$file"
echo "Updated: $file"
else
rm "${file}.tmp"
echo "No change: $file"
fi
}
for file in IF_backend_*; do
[ -f "$file" ] || continue
server_name=$(grep "server_name" "$file" | awk '{print $2}' | tr -d ';n')
subdomain=${server_name%%.*}
if [[ " ${whitelist_subdomains[*]} " =~ " $subdomain " ]]; then
echo "Skipped (whitelisted): $file ($server_name)"
continue
fi
modify_config "$file" $action
done
# Test and reload
echo "Testing Nginx config..."
if docker exec safeline-tengine nginx -t; then
echo "Reloading..."
docker exec safeline-tengine nginx -s reload
else
echo "❌ Config test failed. Not reloading."
exit 1
fi
Make it executable:
chmod +x config-proxy_protocol.sh
Step 4: Run the Script
You can now enable or disable proxy_protocol across all domains.
Enable proxy_protocol
bash config-proxy_protocol.sh true
Disable proxy_protocol
bash config-proxy_protocol.sh false
Help
bash config-proxy_protocol.sh ?
⚠️ About Nginx Warnings
If some domains are excluded (via whitelist), and others use proxy_protocol on the same IP/port, you may see this warning:
nginx: [warn] protocol options redefined for 0.0.0.0:443 in ...
This just means some listeners have proxy_protocol
and others don’t. You can safely ignore this warning. If all domains use proxy_protocol, the warning disappears.
Re-run After Reboot
If you reboot the server or restart SafeLine and the IF_backend_*
files get regenerated, simply re-run the script:
bash config-proxy_protocol.sh true
Step 5: Test the Result
Once everything is in place, visit your public subdomain. Go to SafeLine’s dashboard and check the IP attribution map — if you see your real location showing up, it’s working!
Bonus Tips
- If SafeLine regenerates config on reboot, just re-run the script.
- Always remember to add your FRP server IP in
proxy_params
. - Use the whitelist array to skip
admin
,ops
, or monitoring domains.
Join the SafeLine Community
If this helped, drop a like, share with your devops/security friends, or leave a comment below. Safe web infra is one config away! 💪