Tag: magento

  • Real client ip for Magento 2

    Real client ip for Magento 2

    How I made Magento 2 see the real client IP behind HAProxy + Varnish + Nginx (with Cloudflare)

    If you run Magento 2 behind multiple layers, it’s common to lose the original client IP and end up with your proxy or load balancer address everywhere (logs, admin actions, rate-limiters, captchas). Here’s the setup that finally gave me the real IP all the way to PHP and Magento:

    • HAProxy: add X-Forwarded-For
    • Varnish: normalize X-Forwarded-For (favor Cloudflare CF-Connecting-IP if present; otherwise keep chain or fall back to client.ip)
    • Nginx: trust the Varnish IP and use X-Forwarded-For to set the real client IP, then pass that header to PHP-FPM

    Architecture

    Client (optionally Cloudflare) -> HAProxy -> Varnish -> Nginx -> PHP-FPM (Magento 2)

    Step 1: HAProxy — add the client IP once

    In your HAProxy frontend that handles HTTP/HTTPS:

    frontend fe_http
        bind :80
        option forwardfor if-none
        default_backend be_varnish
    
    frontend fe_https
        bind :443 ssl crt /etc/haproxy/certs
        option forwardfor if-none
        http-request set-header X-Forwarded-Proto https if { ssl_fc }
        default_backend be_varnish
    
    backend be_varnish
        server varnish 127.0.0.1:6081
    

    Notes:

    • option forwardfor adds X-Forwarded-For with the client source IP and, with if-none, won’t overwrite an existing header (e.g., from Cloudflare).
    • Set X-Forwarded-Proto so downstream knows the original scheme.

    Step 2: Varnish — normalize X-Forwarded-For

    I used this in vcl_recv to prefer Cloudflare’s CF-Connecting-IP when present, otherwise keep what we already have, and finally fall back to client.ip:

    if (req.http.CF-Connecting-IP) {
        set req.http.X-Forwarded-For = req.http.CF-Connecting-IP;
    } else if (req.http.X-Forwarded-For) {
        # keep existing header (CF/HAProxy might have set it)
        set req.http.X-Forwarded-For = req.http.X-Forwarded-For;
    } else {
        set req.http.X-Forwarded-For = client.ip;
    }
    

    Why this order:

    • When using Cloudflare, CF-Connecting-IP is the canonical client address and is safer than trusting a possibly client-spoofed X-Forwarded-For arriving at the edge.
    • If you’re not on Cloudflare, HAProxy will have added X-Forwarded-For, so we preserve it.
    • If neither is set, we fall back to the direct client.ip seen by Varnish.

    Optional in Varnish: ensure X-Forwarded-Proto is forwarded correctly as well (if HAProxy set it, just pass it through).

    Step 3: Nginx — trust Varnish and apply the real IP

    Tell Nginx which upstream IPs are trusted to provide X-Forwarded-For and then use that header as the source of truth. Also, pass the header through to PHP-FPM.

    In http { }:

    # Trust only your proxy chain. At minimum, trust Varnish:
    set_real_ip_from 127.0.0.1;    # or your Varnish host/IP
    real_ip_header X-Forwarded-For;
    real_ip_recursive on;
    
    # Optional: useful for debugging
    log_format main '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent" xff="$http_x_forwarded_for"';
    access_log /var/log/nginx/access.log main;
    

    Inside your PHP location block:

    location ~ \.php$ {
        include fastcgi_params;
    
        # Make sure PHP sees the normalized header
        fastcgi_param HTTP_X_FORWARDED_FOR $http_x_forwarded_for;
    
        # Usual PHP-FPM pass
        fastcgi_pass unix:/run/php/php-fpm.sock;
    }
    

    Critical pieces:

    • set_real_ip_from must include your Varnish IP(s). Only trust known proxies. If Nginx also sits behind HAProxy directly in some paths, add those IPs too.
    • real_ip_header X-Forwarded-For makes Nginx rewrite $remote_addr to the real client IP, using the trusted chain.
    • real_ip_recursive on walks the header from right to left until it finds the first untrusted address, which becomes the real IP.
    • Passing HTTP_X_FORWARDED_FOR to PHP ensures Magento/PHP code that inspects that header can still see the chain (while REMOTE_ADDR becomes the real client IP thanks to real_ip_header).

    Magento 2 impact

    • PHP’s $_SERVER[‘REMOTE_ADDR’] now shows the actual client address (thanks to Nginx real_ip_module).
    • $_SERVER[‘HTTP_X_FORWARDED_FOR’] is available as the normalized chain (thanks to the fastcgi_param line).
    • Admin actions, logs, rate-limiters, and security modules that rely on REMOTE_ADDR behave correctly.
    • If you use Cloudflare, you’ll get the exact visitor IP via CF-Connecting-IP promoted into X-Forwarded-For at the Varnish layer.

    Verification checklist

    • From the shell on your web host:
    • tail -f /var/log/nginx/access.log and confirm $remote_addr shows the real IP and xff shows what you expect.
    • In Varnish:
    • varnishlog -g request -i ReqHeader:X-Forwarded-For and confirm the header holds the correct client IP.
    • In PHP:
    • Create a small phpinfo() or var_dump($_SERVER) page temporarily and confirm REMOTE_ADDR equals the client IP and HTTP_X_FORWARDED_FOR is set.
    • In Magento admin:
    • Check recent logs, orders, or security modules for real client IPs.

    Common pitfalls

    • Trust only your proxies. Never set set_real_ip_from 0.0.0.0/0. That allows clients to spoof X-Forwarded-For.
    • If you’re on Cloudflare and want to trust CF directly in Nginx, you can set real_ip_header CF-Connecting-IP and add all Cloudflare IP ranges to set_real_ip_from. The Varnish normalization shown above is often simpler to maintain.
    • Ensure HAProxy uses option forwardfor if-none so it doesn’t stomp a legitimate X-Forwarded-For coming from an upstream like Cloudflare.
    • Don’t forget to pass HTTP_X_FORWARDED_FOR to PHP-FPM:
      fastcgi_param HTTP_X_FORWARDED_FOR $http_x_forwarded_for;

    That’s it. With HAProxy setting X-Forwarded-For, Varnish normalizing it, and Nginx trusting only the Varnish IP and passing the header to PHP, Magento 2 finally sees the real visitor IP end-to-end.