The Problem
Large-scale web architectures typically use a load balancer or reverse proxy, and the client IP is usually obtained via the X-Forwarded-For header. However, frpc's HTTPS mode can only expose the real client IP through the Proxy Protocol — and Microsoft's web server (Kestrel) does not natively support that protocol. You therefore need Nginx as a middle layer to extract the real client IP.
What Is Proxy Protocol?
When Proxy Protocol is enabled, frpc sends a Proxy Protocol header to the local service immediately after establishing a connection. The local service parses this header to obtain the visitor's real IP. This works for any TCP service that supports the protocol — not just HTTP.
Traditional reverse proxies pass the client IP via the X-Forwarded-For HTTP header, which can be spoofed by a malicious client. Proxy Protocol, on the other hand, captures the IP at the TCP/IP handshake level, making it tamper-resistant and more secure.
Ways frpc Can Obtain the Client IP
- HTTP (unencrypted) — the real client IP can be read from
X-Forwarded-For. - HTTPS — you must use the Proxy Protocol at the transport layer to obtain the real client IP.
Step 1: Edit frpc.ini and Set proxy_protocol_version to v2
[common]
server_addr = your public frps url
server_port = 7000
auth_token = you token
pool_count = 10000
proxy_protocol_version = v2
[web]
type = https
local_port = 443
custom_domains = www.markkulab.net
proxy_protocol_version = v2
Step 2: Configure nginx.conf
upstream frp {
server 34.80.106.95:80; # 这个是frp_server的内网ip和http监听端口
}
server {
listen 80;
server_name www.letgo.com.tw;
server_tokens off;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server
{
listen 80 proxy_protocol;
listen 443 ssl http2 proxy_protocol;
listen [::]:443 ssl http2;
server_name www.letgo.com.tw; # local server ip
set_real_ip_from 172.31.0.1; # frp client ip
real_ip_recursive on;
real_ip_header proxy_protocol;
ssl_certificate /etc/letsencrypt/live/www.letgo.com.tw/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/www.letgo.com.tw/privkey.pem;
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
add_header Strict-Transport-Security "max-age=31536000";
error_page 497 https://$host$request_uri;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_protocol_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://192.168.50.52:8890/; # your local application ip
}
}
Step 3: Read the Client IP from the X-Forwarded-For Header in Your App
public static string GetUserIp(this HttpContext context)
{
var ip = context.Request.Headers["X-Forwarded-For"].FirstOrDefault();
if (!string.IsNullOrWhiteSpace(ip))
{
ip = ip.Replace("::ffff:", "");
}
return ip;
}





























Comments